Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aff0d99842 | ||
|
|
00150118d2 | ||
|
|
332a1eb32b | ||
|
|
db4d46c368 | ||
|
|
a8c8610ca2 | ||
|
|
d09c17d30d | ||
|
|
e3e3ea07db | ||
|
|
22592439da | ||
|
|
dfa9fb4025 | ||
|
|
0e2a68a2d4 | ||
|
|
1eefa65527 | ||
|
|
f9b6af58c9 | ||
|
|
56ecf48b57 | ||
|
|
06fbfc90c5 | ||
|
|
39a4fef506 | ||
|
|
f9fe0bb5f8 | ||
|
|
d22ab387dd |
@@ -104,7 +104,7 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
chapter, err := chapters.Add(story, title, result.Author[5:], result.Source, publishDate, &fictionalDate)
|
||||
chapter, err := chapters.Add(story, title, result.Author[5:], result.Source, publishDate, &fictionalDate, models.ChapterCommentModeDisabled)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
@@ -8,15 +8,13 @@ import (
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"git.aiterp.net/rpdata/api/models/changes"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models/logs"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graph2"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/internal/loader"
|
||||
"git.aiterp.net/rpdata/api/internal/store"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"git.aiterp.net/rpdata/api/models/changes"
|
||||
"git.aiterp.net/rpdata/api/models/logs"
|
||||
"github.com/99designs/gqlgen/handler"
|
||||
)
|
||||
|
||||
@@ -64,6 +62,8 @@ func logListedChanges() {
|
||||
log.Printf("\tTag: (%s,%s)", object.Kind, object.Name)
|
||||
case models.Chapter:
|
||||
log.Printf("\tChapter: %s", object.ID)
|
||||
case models.Comment:
|
||||
log.Printf("\tComment: %s", object.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,6 +80,7 @@ func queryHandler() http.HandlerFunc {
|
||||
|
||||
return fmt.Errorf("The server failed to serve your request due to an internal error, it has been logged")
|
||||
}),
|
||||
handler.ComplexityLimit(300),
|
||||
)
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
package graph2
|
||||
|
||||
import (
|
||||
input "git.aiterp.net/rpdata/api/graph2/input"
|
||||
changes "git.aiterp.net/rpdata/api/models/changes"
|
||||
channels "git.aiterp.net/rpdata/api/models/channels"
|
||||
characters "git.aiterp.net/rpdata/api/models/characters"
|
||||
files "git.aiterp.net/rpdata/api/models/files"
|
||||
logs "git.aiterp.net/rpdata/api/models/logs"
|
||||
posts "git.aiterp.net/rpdata/api/models/posts"
|
||||
stories "git.aiterp.net/rpdata/api/models/stories"
|
||||
)
|
||||
|
||||
func complexity() (cr ComplexityRoot) {
|
||||
subListComplexity := 25
|
||||
bigSublistComplexity := 75
|
||||
hugeSublistComplexity := 100
|
||||
findComplexity := 100
|
||||
listComplexity := 200
|
||||
mutationComplexity := 100
|
||||
|
||||
cr.Query.Character = func(childComplexity int, id *string, nick *string) int {
|
||||
return childComplexity + findComplexity
|
||||
}
|
||||
cr.Query.Characters = func(childComplexity int, filter *characters.Filter) int {
|
||||
return childComplexity + listComplexity
|
||||
}
|
||||
cr.Query.Channel = func(childComplexity int, name string) int {
|
||||
return childComplexity + findComplexity
|
||||
}
|
||||
cr.Query.Channels = func(childComplexity int, filter *channels.Filter) int {
|
||||
return childComplexity + listComplexity
|
||||
}
|
||||
cr.Query.Post = func(childComplexity int, id string) int {
|
||||
return childComplexity + findComplexity
|
||||
}
|
||||
cr.Query.Posts = func(childComplexity int, filter *posts.Filter) int {
|
||||
return childComplexity + listComplexity
|
||||
}
|
||||
cr.Query.UnknownNicks = func(childComplexity int, filter *input.UnknownNicksFilter) int {
|
||||
return childComplexity + listComplexity
|
||||
}
|
||||
cr.Query.Log = func(childComplexity int, id string) int {
|
||||
return childComplexity + findComplexity
|
||||
}
|
||||
cr.Query.Logs = func(childComplexity int, filter *logs.Filter) int {
|
||||
if filter != nil && filter.Open != nil && *filter.Open == true {
|
||||
return childComplexity + findComplexity
|
||||
}
|
||||
|
||||
return childComplexity + listComplexity
|
||||
}
|
||||
cr.Query.Chapter = func(childComplexity int, id string) int {
|
||||
return childComplexity + findComplexity
|
||||
}
|
||||
cr.Query.Comment = func(childComplexity int, id string) int {
|
||||
return childComplexity + findComplexity
|
||||
}
|
||||
cr.Query.Tags = func(childComplexity int) int {
|
||||
return childComplexity + listComplexity
|
||||
}
|
||||
cr.Query.Story = func(childComplexity int, id string) int {
|
||||
return childComplexity + findComplexity
|
||||
}
|
||||
cr.Query.Stories = func(childComplexity int, filter *stories.Filter) int {
|
||||
return childComplexity + listComplexity
|
||||
}
|
||||
cr.Query.File = func(childComplexity int, id string) int {
|
||||
return childComplexity + findComplexity
|
||||
}
|
||||
cr.Query.Files = func(childComplexity int, filter *files.Filter) int {
|
||||
return childComplexity + listComplexity
|
||||
}
|
||||
cr.Query.Changes = func(childComplexity int, filter *changes.Filter) int {
|
||||
return childComplexity + listComplexity
|
||||
}
|
||||
cr.Query.Token = func(childComplexity int) int {
|
||||
return childComplexity + findComplexity
|
||||
}
|
||||
|
||||
cr.Mutation.AddStory = func(childComplexity int, input input.StoryAddInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.AddStoryTag = func(childComplexity int, input input.StoryTagAddInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.RemoveStoryTag = func(childComplexity int, input input.StoryTagRemoveInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.EditStory = func(childComplexity int, input input.StoryEditInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.RemoveStory = func(childComplexity int, input input.StoryRemoveInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.AddChapter = func(childComplexity int, input input.ChapterAddInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.EditChapter = func(childComplexity int, input input.ChapterEditInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.MoveChapter = func(childComplexity int, input input.ChapterMoveInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.RemoveChapter = func(childComplexity int, input input.ChapterRemoveInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.AddComment = func(childComplexity int, input input.CommentAddInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.EditComment = func(childComplexity int, input input.CommentEditInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.RemoveComment = func(childComplexity int, input input.CommentRemoveInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.AddLog = func(childComplexity int, input input.LogAddInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.ImportLog = func(childComplexity int, input input.LogImportInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.EditLog = func(childComplexity int, input input.LogEditInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.RemoveLog = func(childComplexity int, input input.LogRemoveInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.AddPost = func(childComplexity int, input input.PostAddInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.EditPost = func(childComplexity int, input input.PostEditInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.MovePost = func(childComplexity int, input input.PostMoveInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.RemovePost = func(childComplexity int, input input.PostRemoveInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.AddCharacter = func(childComplexity int, input input.CharacterAddInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.AddCharacterNick = func(childComplexity int, input input.CharacterNickInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.RemoveCharacterNick = func(childComplexity int, input input.CharacterNickInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.EditCharacter = func(childComplexity int, input input.CharacterEditInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.RemoveCharacter = func(childComplexity int, input input.CharacterRemoveInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.AddChannel = func(childComplexity int, input input.ChannelAddInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.EditChannel = func(childComplexity int, input input.ChannelEditInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
|
||||
cr.Log.Characters = func(childComplexity int) int {
|
||||
return childComplexity + subListComplexity
|
||||
}
|
||||
cr.Log.Posts = func(childComplexity int, kinds []string) int {
|
||||
return childComplexity + hugeSublistComplexity
|
||||
}
|
||||
|
||||
cr.Story.Chapters = func(childComplexity int) int {
|
||||
return childComplexity + bigSublistComplexity
|
||||
}
|
||||
cr.Story.Tags = func(childComplexity int) int {
|
||||
return childComplexity + subListComplexity
|
||||
}
|
||||
|
||||
cr.Chapter.Comments = func(childComplexity int, limit *int) int {
|
||||
return childComplexity + subListComplexity
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -35,6 +35,13 @@ models:
|
||||
model: git.aiterp.net/rpdata/api/models/logs.Filter
|
||||
LogImporter:
|
||||
model: git.aiterp.net/rpdata/api/models.LogImporter
|
||||
Comment:
|
||||
model: git.aiterp.net/rpdata/api/models.Comment
|
||||
fields:
|
||||
fictionalDate:
|
||||
resolver: true
|
||||
ChapterCommentMode:
|
||||
model: git.aiterp.net/rpdata/api/models.ChapterCommentMode
|
||||
Chapter:
|
||||
model: git.aiterp.net/rpdata/api/models.Chapter
|
||||
fields:
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
func New() graphql.ExecutableSchema {
|
||||
return NewExecutableSchema(Config{
|
||||
Resolvers: &rootResolver{},
|
||||
Complexity: complexity(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -34,6 +35,10 @@ func (r *rootResolver) Log() LogResolver {
|
||||
return &types.LogResolver
|
||||
}
|
||||
|
||||
func (r *rootResolver) Comment() CommentResolver {
|
||||
return &types.CommentResolver
|
||||
}
|
||||
|
||||
func (r *rootResolver) Chapter() ChapterResolver {
|
||||
return &types.ChapterResolver
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"git.aiterp.net/rpdata/api/models/changekeys"
|
||||
"git.aiterp.net/rpdata/api/models/changes"
|
||||
"git.aiterp.net/rpdata/api/models/comments"
|
||||
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/models/stories"
|
||||
@@ -48,7 +49,12 @@ func (r *mutationResolver) AddChapter(ctx context.Context, input input.ChapterAd
|
||||
return models.Chapter{}, errors.New("Story is not open")
|
||||
}
|
||||
|
||||
chapter, err := chapters.Add(story, input.Title, author, input.Source, time.Now(), input.FictionalDate)
|
||||
commentMode := models.ChapterCommentModeDisabled
|
||||
if input.CommentMode != nil {
|
||||
commentMode = *input.CommentMode
|
||||
}
|
||||
|
||||
chapter, err := chapters.Add(story, input.Title, author, input.Source, time.Now(), input.FictionalDate, commentMode)
|
||||
if err != nil {
|
||||
return models.Chapter{}, errors.New("Failed to create chapter: " + err.Error())
|
||||
}
|
||||
@@ -117,7 +123,7 @@ func (r *mutationResolver) EditChapter(ctx context.Context, input input.ChapterE
|
||||
input.FictionalDate = &time.Time{}
|
||||
}
|
||||
|
||||
chapter, err = chapters.Edit(chapter, input.Title, input.Source, input.FictionalDate)
|
||||
chapter, err = chapters.Edit(chapter, input.Title, input.Source, input.FictionalDate, input.CommentMode, input.CommentsLocked)
|
||||
if err != nil {
|
||||
return models.Chapter{}, errors.New("Failed to edit chapter: " + err.Error())
|
||||
}
|
||||
@@ -150,6 +156,11 @@ func (r *mutationResolver) RemoveChapter(ctx context.Context, input input.Chapte
|
||||
return models.Chapter{}, errors.New("Failed to remove chapter: " + err.Error())
|
||||
}
|
||||
|
||||
err = comments.RemoveChapter(chapter)
|
||||
if err != nil {
|
||||
return models.Chapter{}, errors.New("Chapter was removed, but comment removal failed: " + err.Error())
|
||||
}
|
||||
|
||||
go func() {
|
||||
story, err := stories.FindID(chapter.StoryID)
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
package queries
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models/stories"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models/characters"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models/changekeys"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models/changes"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graph2/input"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/models/chapters"
|
||||
"git.aiterp.net/rpdata/api/models/comments"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
)
|
||||
|
||||
// Queries
|
||||
|
||||
func (r *resolver) Comment(ctx context.Context, id string) (models.Comment, error) {
|
||||
return comments.Find(id)
|
||||
}
|
||||
|
||||
// Mutations
|
||||
|
||||
func (r *mutationResolver) AddComment(ctx context.Context, input input.CommentAddInput) (models.Comment, error) {
|
||||
chapter, err := chapters.FindID(input.ChapterID)
|
||||
if err != nil {
|
||||
return models.Comment{}, errors.New("Chapter not found")
|
||||
}
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Permitted("member", "story.edit") {
|
||||
return models.Comment{}, errors.New("Unauthorized")
|
||||
}
|
||||
|
||||
if !chapter.CanComment() {
|
||||
return models.Comment{}, 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 models.Comment{}, errors.New("Character not found")
|
||||
} else if character.Author != token.UserID {
|
||||
return models.Comment{}, errors.New("That is not your character")
|
||||
}
|
||||
|
||||
characterPtr = &character
|
||||
}
|
||||
|
||||
fictionalDate := time.Time{}
|
||||
if input.FictionalDate != nil {
|
||||
fictionalDate = *input.FictionalDate
|
||||
}
|
||||
|
||||
subject := ""
|
||||
if input.Subject != nil {
|
||||
subject = *input.Subject
|
||||
}
|
||||
|
||||
comment, err := comments.Add(chapter, subject, token.UserID, input.Source, input.CharacterName, characterPtr, time.Now(), fictionalDate)
|
||||
if err != nil {
|
||||
return models.Comment{}, errors.New("Failed to add 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", "add", token.UserID, true, changekeys.Many(comment, chapter, models.Story{ID: chapter.StoryID}), comment, chapter, story)
|
||||
}()
|
||||
|
||||
return comment, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) EditComment(ctx context.Context, input input.CommentEditInput) (models.Comment, error) {
|
||||
comment, err := comments.Find(input.CommentID)
|
||||
if err != nil {
|
||||
return models.Comment{}, errors.New("Comment not found")
|
||||
}
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.PermittedUser(comment.Author, "member", "story.edit") {
|
||||
return models.Comment{}, errors.New("You cannot edit this comment")
|
||||
}
|
||||
|
||||
chapter, err := chapters.FindID(comment.ChapterID)
|
||||
if err != nil {
|
||||
return models.Comment{}, errors.New("Comment's chapter not found")
|
||||
}
|
||||
|
||||
if !chapter.CanComment() {
|
||||
return models.Comment{}, 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 models.Comment{}, errors.New("Character not found")
|
||||
} else if character.Author != token.UserID {
|
||||
return models.Comment{}, 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 models.Comment{}, 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
|
||||
}
|
||||
|
||||
func (r *mutationResolver) RemoveComment(ctx context.Context, input input.CommentRemoveInput) (models.Comment, error) {
|
||||
comment, err := comments.Find(input.CommentID)
|
||||
if err != nil {
|
||||
return models.Comment{}, errors.New("Comment not found")
|
||||
}
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.PermittedUser(comment.Author, "member", "story.edit") {
|
||||
return models.Comment{}, errors.New("You cannot remove this comment")
|
||||
}
|
||||
|
||||
chapter, err := chapters.FindID(comment.ChapterID)
|
||||
if err != nil {
|
||||
return models.Comment{}, errors.New("Comment's chapter not found")
|
||||
}
|
||||
|
||||
if !chapter.CanComment() {
|
||||
return models.Comment{}, errors.New("Comments are disabled or locked")
|
||||
}
|
||||
|
||||
err = comments.Remove(comment)
|
||||
if err != nil {
|
||||
return models.Comment{}, 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
|
||||
}
|
||||
@@ -41,6 +41,10 @@ type Query {
|
||||
chapter(id: String!): Chapter!
|
||||
|
||||
|
||||
# Find comment by ID
|
||||
comment(id: String!): Comment!
|
||||
|
||||
|
||||
# Find all distinct tags used in stories
|
||||
tags: [Tag!]!
|
||||
|
||||
@@ -97,6 +101,16 @@ type Mutation {
|
||||
removeChapter(input: ChapterRemoveInput!): Chapter!
|
||||
|
||||
|
||||
"Add a comment to a chapter."
|
||||
addComment(input: CommentAddInput!): Comment!
|
||||
|
||||
"Edit a comment in a chapter."
|
||||
editComment(input: CommentEditInput!): Comment!
|
||||
|
||||
"Remove a comemnt in a chapter."
|
||||
removeComment(input: CommentRemoveInput!): Comment!
|
||||
|
||||
|
||||
# Add a new log
|
||||
addLog(input: LogAddInput!): Log!
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ type Change {
|
||||
objects: [ChangeObject!]!
|
||||
}
|
||||
|
||||
union ChangeObject = Character | Channel | Log | Post | Story | Tag | Chapter
|
||||
union ChangeObject = Character | Channel | Log | Post | Story | Tag | Chapter | Comment
|
||||
|
||||
"""
|
||||
A change key is a key associated with a change, used for filtering
|
||||
|
||||
@@ -20,6 +20,35 @@ type Chapter {
|
||||
|
||||
# The date of edit.
|
||||
editedDate: Date!
|
||||
|
||||
"The comment mode."
|
||||
commentMode: ChapterCommentMode!
|
||||
|
||||
"Whether the author has locket comments."
|
||||
commentsLocked: Boolean!
|
||||
|
||||
"A shorthand for checking whether a logged-in user can comment on this chapter."
|
||||
canComment: Boolean!
|
||||
|
||||
"Get all chapter comments."
|
||||
comments(limit: Int): [Comment!]!
|
||||
}
|
||||
|
||||
"""
|
||||
The possibles modes of comments for a chapter.
|
||||
"""
|
||||
enum ChapterCommentMode {
|
||||
"Comments are disabled and hidden."
|
||||
Disabled
|
||||
|
||||
"Comments should be shown as typical news article comments."
|
||||
Article
|
||||
|
||||
"Comments should be shown in a compact form suitable for many small messages."
|
||||
Chat
|
||||
|
||||
"Comments are going to be shown as omni-tool messages."
|
||||
Message
|
||||
}
|
||||
|
||||
# Input for addChapter mutation
|
||||
@@ -41,6 +70,9 @@ input ChapterAddInput {
|
||||
|
||||
# Optionally, assign a fictional date for the chapter
|
||||
fictionalDate: Date
|
||||
|
||||
"The comment mode for the chapter."
|
||||
commentMode: ChapterCommentMode
|
||||
}
|
||||
|
||||
# Input for editChapter mutation
|
||||
@@ -59,6 +91,12 @@ input ChapterEditInput {
|
||||
|
||||
# Remove the fictional date for a chapter
|
||||
clearFictionalDate: Boolean
|
||||
|
||||
"Set the comment mode for the chapter"
|
||||
commentMode: ChapterCommentMode
|
||||
|
||||
"Set whether the chapter comments are locked"
|
||||
commentsLocked: Boolean
|
||||
}
|
||||
|
||||
# Input for moveChapter mutation
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
"""
|
||||
A Comment represents a comment to a story chapter.
|
||||
"""
|
||||
type Comment {
|
||||
"A unique ID of the change."
|
||||
id: String!
|
||||
|
||||
"subject"
|
||||
subject: String!
|
||||
|
||||
"The chapter ID the comment belongs to."
|
||||
chapterId: String!
|
||||
|
||||
"The comment's author."
|
||||
author: String!
|
||||
|
||||
"The displayed name of the character. This may be the same as character.name, but it does not need to be."
|
||||
characterName: String!
|
||||
|
||||
"The character associated with the comment."
|
||||
character: Character
|
||||
|
||||
"The fictional (IC) date of the comment."
|
||||
fictionalDate: Date
|
||||
|
||||
"The date of creation."
|
||||
createdDate: Date!
|
||||
|
||||
"The date of the last edit."
|
||||
editedDate: Date!
|
||||
|
||||
"The markdown source of the comment."
|
||||
source: String!
|
||||
}
|
||||
|
||||
"""
|
||||
Input for the addComment mutation
|
||||
"""
|
||||
input CommentAddInput {
|
||||
"What chapter to comment on."
|
||||
chapterId: String!
|
||||
|
||||
"The markdown source of the comment."
|
||||
source: String!
|
||||
|
||||
"The name of the character associated with this comment."
|
||||
characterName: String!
|
||||
|
||||
"Optioanlly, a character ID."
|
||||
characterId: String
|
||||
|
||||
"Optional in-universe date of the comment."
|
||||
fictionalDate: Date
|
||||
|
||||
"Optional subject to add to the comment, only shown in some modes."
|
||||
subject: String
|
||||
}
|
||||
|
||||
"""
|
||||
Input for the editComment mutation
|
||||
"""
|
||||
input CommentEditInput {
|
||||
"What comment to edit."
|
||||
commentId: String!
|
||||
|
||||
"Change the markdown source."
|
||||
source: String
|
||||
|
||||
"Change the name of the character associated with this comment."
|
||||
characterName: String
|
||||
|
||||
"Change the character assocaited with this comment."
|
||||
characterId: String
|
||||
|
||||
"Change the optional in-universe date of the comment."
|
||||
fictionalDate: Date
|
||||
|
||||
"Set to clear the fictional date."
|
||||
clearFictionalDate: Boolean
|
||||
|
||||
"Change the optional subject for the comment, only shown in some modes."
|
||||
subject: String
|
||||
}
|
||||
|
||||
"""
|
||||
Input for the removeComment mutation
|
||||
"""
|
||||
input CommentRemoveInput {
|
||||
"What comment to edit."
|
||||
commentId: String!
|
||||
}
|
||||
@@ -2,9 +2,11 @@ package types
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"git.aiterp.net/rpdata/api/models/comments"
|
||||
)
|
||||
|
||||
type chapterResolver struct{}
|
||||
@@ -17,5 +19,22 @@ func (r *chapterResolver) FictionalDate(ctx context.Context, chapter *models.Cha
|
||||
return &chapter.FictionalDate, nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
return comments.ListChapterID(chapter.ID, limitValue)
|
||||
}
|
||||
|
||||
// ChapterResolver is a resolver
|
||||
var ChapterResolver chapterResolver
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/internal/loader"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
)
|
||||
|
||||
type commentResolver struct{}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &character, nil
|
||||
}
|
||||
|
||||
func (r *commentResolver) FictionalDate(ctx context.Context, obj *models.Comment) (*time.Time, error) {
|
||||
if obj.FictionalDate.IsZero() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return &obj.FictionalDate, nil
|
||||
}
|
||||
|
||||
// CommentResolver is a resolver
|
||||
var CommentResolver commentResolver
|
||||
@@ -24,12 +24,14 @@ const (
|
||||
ChangeModelTag ChangeModel = "Tag"
|
||||
// ChangeModelChapter is a value of ChangeModel
|
||||
ChangeModelChapter ChangeModel = "Chapter"
|
||||
// ChangeModelComment is a value of ChangeModel
|
||||
ChangeModelComment ChangeModel = "Comment"
|
||||
)
|
||||
|
||||
// IsValid returns true if the underlying string is one of the correct values.
|
||||
func (e ChangeModel) IsValid() bool {
|
||||
switch e {
|
||||
case ChangeModelCharacter, ChangeModelChannel, ChangeModelLog, ChangeModelPost, ChangeModelStory, ChangeModelTag, ChangeModelChapter:
|
||||
case ChangeModelCharacter, ChangeModelChannel, ChangeModelLog, ChangeModelPost, ChangeModelStory, ChangeModelTag, ChangeModelChapter, ChangeModelComment:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
+5
-1
@@ -19,6 +19,7 @@ type Change struct {
|
||||
Stories []Story `bson:"stories"`
|
||||
Tags []Tag `bson:"tags"`
|
||||
Chapters []Chapter `bson:"chapters"`
|
||||
Comments []Comment `bson:"comments"`
|
||||
}
|
||||
|
||||
// ChangeKey is a key for a change that can be used when subscribing to them.
|
||||
@@ -29,7 +30,7 @@ type ChangeKey struct {
|
||||
|
||||
// Objects makes a combined, mixed array of all the models stored in this change.
|
||||
func (change *Change) Objects() []interface{} {
|
||||
data := make([]interface{}, 0, len(change.Logs)+len(change.Characters)+len(change.Channels)+len(change.Posts)+len(change.Stories)+len(change.Tags)+len(change.Chapters))
|
||||
data := make([]interface{}, 0, len(change.Logs)+len(change.Characters)+len(change.Channels)+len(change.Posts)+len(change.Stories)+len(change.Tags)+len(change.Chapters)+len(change.Comments))
|
||||
|
||||
for _, log := range change.Logs {
|
||||
data = append(data, log)
|
||||
@@ -52,6 +53,9 @@ func (change *Change) Objects() []interface{} {
|
||||
for _, chapter := range change.Chapters {
|
||||
data = append(data, chapter)
|
||||
}
|
||||
for _, comment := range change.Comments {
|
||||
data = append(data, comment)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ func TestMany(t *testing.T) {
|
||||
},
|
||||
},
|
||||
models.Post{ID: "P5"},
|
||||
models.Comment{ID: "SCC1234"},
|
||||
}
|
||||
|
||||
expectations := []models.ChangeKey{
|
||||
@@ -33,6 +34,7 @@ func TestMany(t *testing.T) {
|
||||
{Model: "Character", ID: "C17"},
|
||||
{Model: "Channel", ID: "#Stuff"},
|
||||
{Model: "Post", ID: "P5"},
|
||||
{Model: "Comment", ID: "SCC1234"},
|
||||
}
|
||||
|
||||
results := changekeys.Many(data...)
|
||||
|
||||
@@ -28,6 +28,11 @@ func TestOne(t *testing.T) {
|
||||
models.Log{ID: "2018-10-23_210303325_RedrockAgency", ShortID: "L807"},
|
||||
models.ChangeKey{Model: "Log", ID: "2018-10-23_210303325_RedrockAgency"},
|
||||
},
|
||||
{
|
||||
"Comment_SCC1234",
|
||||
models.Comment{ID: "SCC1234"},
|
||||
models.ChangeKey{Model: "Comment", ID: "SCC1234"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, row := range table {
|
||||
|
||||
@@ -81,6 +81,10 @@ func Submit(model, op, author string, listed bool, keys []models.ChangeKey, obje
|
||||
change.Chapters = append(change.Chapters, object)
|
||||
case []models.Chapter:
|
||||
change.Chapters = append(change.Chapters, object...)
|
||||
case models.Comment:
|
||||
change.Comments = append(change.Comments, object)
|
||||
case []models.Comment:
|
||||
change.Comments = append(change.Comments, object...)
|
||||
default:
|
||||
log.Printf("Warning: unrecognized object in change: %#+v", object)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// ChapterCommentMode represents the kind of tags.
|
||||
type ChapterCommentMode string
|
||||
|
||||
const (
|
||||
// ChapterCommentModeDisabled is a chapter comment mode, see GraphQL documentation.
|
||||
ChapterCommentModeDisabled ChapterCommentMode = "Disabled"
|
||||
|
||||
// ChapterCommentModeArticle is a chapter comment mode, see GraphQL documentation.
|
||||
ChapterCommentModeArticle ChapterCommentMode = "Article"
|
||||
|
||||
// ChapterCommentModeChat is a chapter comment mode, see GraphQL documentation.
|
||||
ChapterCommentModeChat ChapterCommentMode = "Chat"
|
||||
|
||||
// ChapterCommentModeMessage is a chapter comment mode, see GraphQL documentation.
|
||||
ChapterCommentModeMessage ChapterCommentMode = "Message"
|
||||
)
|
||||
|
||||
// UnmarshalGQL unmarshals
|
||||
func (e *ChapterCommentMode) UnmarshalGQL(v interface{}) error {
|
||||
str, ok := v.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("enums must be strings")
|
||||
}
|
||||
|
||||
*e = ChapterCommentMode(str)
|
||||
switch *e {
|
||||
case ChapterCommentModeDisabled, ChapterCommentModeArticle, ChapterCommentModeChat, ChapterCommentModeMessage:
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("%s is not a valid ChapterCommentMode", str)
|
||||
}
|
||||
}
|
||||
|
||||
// IsEnabled returns true if comments are enabled.
|
||||
func (e ChapterCommentMode) IsEnabled() bool {
|
||||
return e != ChapterCommentModeDisabled && len(e) != 0
|
||||
}
|
||||
|
||||
// MarshalGQL turns it into a JSON string
|
||||
func (e ChapterCommentMode) MarshalGQL(w io.Writer) {
|
||||
// Backwards compatibility: Empty value means disabled.
|
||||
if len(e) == 0 {
|
||||
w.Write(disabledChapterCommentModeBytes)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprint(w, "\""+string(e)+"\"")
|
||||
}
|
||||
|
||||
var disabledChapterCommentModeBytes = []byte(`"Disabled"`)
|
||||
@@ -12,4 +12,11 @@ type Chapter struct {
|
||||
CreatedDate time.Time `bson:"createdDate"`
|
||||
FictionalDate time.Time `bson:"fictionalDate,omitempty"`
|
||||
EditedDate time.Time `bson:"editedDate"`
|
||||
CommentMode ChapterCommentMode `bson:"commentMode"`
|
||||
CommentsLocked bool `bson:"commentsLocked"`
|
||||
}
|
||||
|
||||
// CanComment returns true if the chapter can be commented to.
|
||||
func (chapter *Chapter) CanComment() bool {
|
||||
return !chapter.CommentsLocked && chapter.CommentMode.IsEnabled()
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
// Add adds a new chapter.
|
||||
func Add(story models.Story, title, author, source string, createdDate time.Time, finctionalDate *time.Time) (models.Chapter, error) {
|
||||
func Add(story models.Story, title, author, source string, createdDate time.Time, finctionalDate *time.Time, commentMode models.ChapterCommentMode) (models.Chapter, error) {
|
||||
chapter := models.Chapter{
|
||||
ID: makeChapterID(),
|
||||
StoryID: story.ID,
|
||||
@@ -17,6 +17,8 @@ func Add(story models.Story, title, author, source string, createdDate time.Time
|
||||
Source: source,
|
||||
CreatedDate: createdDate,
|
||||
EditedDate: createdDate,
|
||||
CommentMode: commentMode,
|
||||
CommentsLocked: false,
|
||||
}
|
||||
|
||||
if finctionalDate != nil {
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
// Edit edits a chapter, and updates EditedDate. While many Edit functions cheat if there's nothing to
|
||||
// change, this functill will due to EditedDate.
|
||||
func Edit(chapter models.Chapter, title, source *string, fictionalDate *time.Time) (models.Chapter, error) {
|
||||
func Edit(chapter models.Chapter, title, source *string, fictionalDate *time.Time, commentMode *models.ChapterCommentMode, commentsLocked *bool) (models.Chapter, error) {
|
||||
now := time.Now()
|
||||
changes := bson.M{"editedDate": now}
|
||||
|
||||
@@ -28,6 +28,14 @@ func Edit(chapter models.Chapter, title, source *string, fictionalDate *time.Tim
|
||||
changes["fictionalDate"] = *fictionalDate
|
||||
edited.FictionalDate = *fictionalDate
|
||||
}
|
||||
if commentMode != nil && *commentMode != chapter.CommentMode {
|
||||
changes["commentMode"] = *commentMode
|
||||
edited.CommentMode = *commentMode
|
||||
}
|
||||
if commentsLocked != nil && *commentsLocked != chapter.CommentsLocked {
|
||||
changes["commentsLocked"] = *commentsLocked
|
||||
edited.CommentsLocked = *commentsLocked
|
||||
}
|
||||
|
||||
err := collection.UpdateId(chapter.ID, bson.M{"$set": changes})
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
// A Comment is a comment on a chapter.
|
||||
type Comment struct {
|
||||
ID string `bson:"_id"`
|
||||
ChapterID string `bson:"chapterId"`
|
||||
Subject string `bson:"subject"`
|
||||
Author string `bson:"author"`
|
||||
CharacterName string `bson:"characterName"`
|
||||
CharacterID string `bson:"characterId"`
|
||||
FictionalDate time.Time `bson:"fictionalDate"`
|
||||
CreatedDate time.Time `bson:"createdDate"`
|
||||
EditedDate time.Time `bson:"editeddDate"`
|
||||
Source string `bson:"source"`
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package comments
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
)
|
||||
|
||||
// Add adds a comment.
|
||||
func Add(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
|
||||
}
|
||||
|
||||
comment := models.Comment{
|
||||
ID: makeCommentID(),
|
||||
ChapterID: chapter.ID,
|
||||
Subject: subject,
|
||||
Author: author,
|
||||
CharacterName: characterName,
|
||||
CharacterID: characterID,
|
||||
FictionalDate: fictionalDate,
|
||||
CreatedDate: createdDate,
|
||||
EditedDate: createdDate,
|
||||
Source: source,
|
||||
}
|
||||
|
||||
err := collection.Insert(comment)
|
||||
if err != nil {
|
||||
return models.Comment{}, err
|
||||
}
|
||||
|
||||
return comment, nil
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package comments
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"strconv"
|
||||
|
||||
"git.aiterp.net/rpdata/api/internal/store"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"github.com/globalsign/mgo"
|
||||
)
|
||||
|
||||
var collection *mgo.Collection
|
||||
|
||||
func find(query interface{}) (models.Comment, error) {
|
||||
comment := models.Comment{}
|
||||
err := collection.Find(query).One(&comment)
|
||||
|
||||
return comment, err
|
||||
}
|
||||
|
||||
func list(query interface{}, limit int) ([]models.Comment, error) {
|
||||
allocSize := 32
|
||||
if limit >= 0 {
|
||||
allocSize = limit
|
||||
} else {
|
||||
limit = 0
|
||||
}
|
||||
|
||||
comments := make([]models.Comment, 0, allocSize)
|
||||
err := collection.Find(query).Sort("createdDate").Limit(limit).All(&comments)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return comments, nil
|
||||
}
|
||||
|
||||
func makeCommentID() string {
|
||||
result := "SCC"
|
||||
offset := 0
|
||||
data := make([]byte, 48)
|
||||
|
||||
rand.Read(data)
|
||||
for len(result) < 32 {
|
||||
result += strconv.FormatUint(binary.LittleEndian.Uint64(data[offset:]), 36)
|
||||
offset += 8
|
||||
|
||||
if offset >= 48 {
|
||||
rand.Read(data)
|
||||
offset = 0
|
||||
}
|
||||
}
|
||||
|
||||
return result[:32]
|
||||
}
|
||||
|
||||
func init() {
|
||||
store.HandleInit(func(db *mgo.Database) {
|
||||
collection = db.C("story.comments")
|
||||
|
||||
collection.EnsureIndexKey("chapterId")
|
||||
collection.EnsureIndexKey("author")
|
||||
collection.EnsureIndexKey("createdDate")
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package comments
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"github.com/globalsign/mgo/bson"
|
||||
)
|
||||
|
||||
// Edit edits a comment.
|
||||
func Edit(comment models.Comment, source, characterName, characterID, subject *string, fictionalDate *time.Time) (models.Comment, error) {
|
||||
changes := make(bson.M, 6)
|
||||
|
||||
comment.EditedDate = time.Now()
|
||||
changes["editedDate"] = comment.EditedDate
|
||||
|
||||
if source != nil {
|
||||
comment.Source = *source
|
||||
changes["source"] = *source
|
||||
}
|
||||
if characterName != nil {
|
||||
comment.CharacterName = *characterName
|
||||
changes["characterName"] = *characterName
|
||||
}
|
||||
if characterID != nil {
|
||||
comment.CharacterID = *characterID
|
||||
changes["characterId"] = *characterID
|
||||
}
|
||||
if subject != nil {
|
||||
comment.Subject = *subject
|
||||
changes["subject"] = *subject
|
||||
}
|
||||
if fictionalDate != nil {
|
||||
comment.FictionalDate = *fictionalDate
|
||||
changes["fictionalDate"] = *fictionalDate
|
||||
}
|
||||
|
||||
err := collection.UpdateId(comment.ID, bson.M{"$set": changes})
|
||||
if err != nil {
|
||||
return models.Comment{}, err
|
||||
}
|
||||
|
||||
return comment, nil
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package comments
|
||||
|
||||
import (
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"github.com/globalsign/mgo/bson"
|
||||
)
|
||||
|
||||
// Find finds a comment by ID.
|
||||
func Find(id string) (models.Comment, error) {
|
||||
return find(bson.M{"_id": id})
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package comments
|
||||
|
||||
import (
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"github.com/globalsign/mgo/bson"
|
||||
)
|
||||
|
||||
// ListChapterID lists all comments by chapter-ID
|
||||
func ListChapterID(chapterID string, limit int) ([]models.Comment, error) {
|
||||
return list(bson.M{"chapterId": chapterID}, limit)
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package comments
|
||||
|
||||
import (
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"github.com/globalsign/mgo/bson"
|
||||
)
|
||||
|
||||
// Remove removes one comment.
|
||||
func Remove(comment models.Comment) error {
|
||||
return collection.RemoveId(comment.ID)
|
||||
}
|
||||
|
||||
// RemoveChapter removes all comments for the given chapter.
|
||||
func RemoveChapter(chapter models.Chapter) error {
|
||||
_, err := collection.RemoveAll(bson.M{"chapterId": chapter.ID})
|
||||
return err
|
||||
}
|
||||
Reference in New Issue
Block a user