Gisle Aune
6 years ago
11 changed files with 429 additions and 0 deletions
-
11graph2/gqlgen.yml
-
4graph2/graph.go
-
31graph2/queries/story.go
-
7graph2/schema/root.gql
-
151graph2/schema/types/Story.gql
-
27graph2/types/story.go
-
59models/stories/db.go
-
11models/stories/find.go
-
67models/stories/list.go
-
43models/story-category.go
-
18models/story.go
@ -0,0 +1,31 @@ |
|||||
|
package queries |
||||
|
|
||||
|
import ( |
||||
|
"context" |
||||
|
"errors" |
||||
|
|
||||
|
"git.aiterp.net/rpdata/api/internal/auth" |
||||
|
"git.aiterp.net/rpdata/api/models" |
||||
|
"git.aiterp.net/rpdata/api/models/stories" |
||||
|
) |
||||
|
|
||||
|
func (r *resolver) Story(ctx context.Context, id string) (models.Story, error) { |
||||
|
return stories.FindID(id) |
||||
|
} |
||||
|
|
||||
|
func (r *resolver) 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 |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return stories.List(filter) |
||||
|
} |
@ -0,0 +1,151 @@ |
|||||
|
# A Story is a piece of content that, unlike wiki articles, are not something that should be ordered by time instead of title. It can |
||||
|
# contain multiple chapters by one or more autohrs |
||||
|
type Story { |
||||
|
# The story's ID |
||||
|
id: String! |
||||
|
|
||||
|
# The story's name, which will show up in the lists. |
||||
|
name: String! |
||||
|
|
||||
|
# The story's author |
||||
|
author: String! |
||||
|
|
||||
|
# Whether other users may add/edit chapters to the story. |
||||
|
open: Boolean! |
||||
|
|
||||
|
# Whether users without a direct link can find this story. |
||||
|
listed: Boolean! |
||||
|
|
||||
|
# The category of the story |
||||
|
category: StoryCategory! |
||||
|
|
||||
|
# The tags for this tory |
||||
|
tags: [Tag!]! |
||||
|
|
||||
|
# The chapters of this story |
||||
|
chapters: [Chapter!]! |
||||
|
|
||||
|
# The date the story was created. |
||||
|
createdDate: Date! |
||||
|
|
||||
|
# The date the story is set in. |
||||
|
fictionalDate: Date |
||||
|
|
||||
|
# The date of the last major update to the story. This being bumped means a new chapter has been added. |
||||
|
updatedDate: Date! |
||||
|
} |
||||
|
|
||||
|
# Filter for stories query. |
||||
|
input StoriesFilter { |
||||
|
# What author to query for |
||||
|
author: String |
||||
|
|
||||
|
# What category to query for |
||||
|
category: StoryCategory |
||||
|
|
||||
|
# What tags to query for |
||||
|
tags: [TagInput!] |
||||
|
|
||||
|
# If true, it will only show unlisted page you have access to |
||||
|
unlisted: Boolean |
||||
|
|
||||
|
# Whether the page is open for additions by other users |
||||
|
open: Boolean |
||||
|
|
||||
|
# The earliest fictionalDate |
||||
|
earliestFictionalDate: Date |
||||
|
|
||||
|
# The latest fictionalDate |
||||
|
latestFictionalDate: Date |
||||
|
|
||||
|
# The max amount of stories to get (default: 30) |
||||
|
limit: Int |
||||
|
} |
||||
|
|
||||
|
# Input for the addStory mutation |
||||
|
input StoryAddInput { |
||||
|
# Set the name of the story, which will show up as the title, and as a suggestion for |
||||
|
# the first chapter being added. |
||||
|
name: String! |
||||
|
|
||||
|
# Set the category for the new story. |
||||
|
category: StoryCategory! |
||||
|
|
||||
|
# Add the story under another name. This requires the story.add permission, and should not be |
||||
|
# abused. The action will be logged with the actual creator's user ID. |
||||
|
author: String |
||||
|
|
||||
|
# Allow other users to post chapters to the story. |
||||
|
open: Boolean |
||||
|
|
||||
|
# Allow other users to see this page in any lists or search results. |
||||
|
listed: Boolean |
||||
|
|
||||
|
# Set which tags the story should have. |
||||
|
tags: [TagInput!] |
||||
|
|
||||
|
# Set the fictional date of the story. |
||||
|
fictionalDate: Date |
||||
|
} |
||||
|
|
||||
|
# Input for the editStory mutation |
||||
|
input StoryEditInput { |
||||
|
# What story to edit |
||||
|
id: String! |
||||
|
|
||||
|
# Set the name of the story, which will show up as the title, and as a suggestion for |
||||
|
# the first chapter being added. |
||||
|
name: String |
||||
|
|
||||
|
# Set the category for the new story. |
||||
|
category: StoryCategory |
||||
|
|
||||
|
# Add the story under another name. This requires the story.add permission, and should not be |
||||
|
# abused. The action will be logged with the actual creator's user ID. |
||||
|
author: String |
||||
|
|
||||
|
# Change whether to allow others to add chapters |
||||
|
open: Boolean |
||||
|
|
||||
|
# Change whether to show this story in the list and search results |
||||
|
listed: Boolean |
||||
|
|
||||
|
# Set the fictional date of the story. |
||||
|
fictionalDate: Date |
||||
|
} |
||||
|
|
||||
|
# Input for the addStoryTag mutation |
||||
|
input StoryTagAddInput { |
||||
|
# What story to add the tag to. |
||||
|
id: String! |
||||
|
|
||||
|
# The tag to add. |
||||
|
tag: TagInput! |
||||
|
} |
||||
|
|
||||
|
# Input for the removeStoryTag mutation |
||||
|
input StoryTagRemoveInput { |
||||
|
# What story to remove the tag from. |
||||
|
id: String! |
||||
|
|
||||
|
# The tag to remove. |
||||
|
tag: TagInput! |
||||
|
} |
||||
|
|
||||
|
# Possible values for Story.category |
||||
|
enum StoryCategory { |
||||
|
# General information |
||||
|
Info |
||||
|
|
||||
|
# News stories |
||||
|
News |
||||
|
|
||||
|
# Description and content of a document or item |
||||
|
Document |
||||
|
|
||||
|
# Information about something going on in the background that may or may not inform RP |
||||
|
Background |
||||
|
|
||||
|
# A short story |
||||
|
Story |
||||
|
} |
@ -0,0 +1,27 @@ |
|||||
|
package types |
||||
|
|
||||
|
import ( |
||||
|
"context" |
||||
|
"time" |
||||
|
|
||||
|
"git.aiterp.net/rpdata/api/models/chapters" |
||||
|
|
||||
|
"git.aiterp.net/rpdata/api/models" |
||||
|
) |
||||
|
|
||||
|
type storyResolver struct{} |
||||
|
|
||||
|
func (r *storyResolver) FictionalDate(ctx context.Context, story *models.Story) (*time.Time, error) { |
||||
|
if story.FictionalDate.IsZero() { |
||||
|
return nil, nil |
||||
|
} |
||||
|
|
||||
|
return &story.FictionalDate, nil |
||||
|
} |
||||
|
|
||||
|
func (r *storyResolver) Chapters(ctx context.Context, story *models.Story) ([]models.Chapter, error) { |
||||
|
return chapters.ListStoryID(story.ID) |
||||
|
} |
||||
|
|
||||
|
// StoryResolver is a resolver
|
||||
|
var StoryResolver storyResolver |
@ -0,0 +1,59 @@ |
|||||
|
package stories |
||||
|
|
||||
|
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.Story, error) { |
||||
|
story := models.Story{} |
||||
|
err := collection.Find(query).One(&story) |
||||
|
|
||||
|
return story, err |
||||
|
} |
||||
|
|
||||
|
func list(query interface{}, limit int) ([]models.Story, error) { |
||||
|
stories := make([]models.Story, 0, 64) |
||||
|
err := collection.Find(query).Limit(limit).Sort("-updatedDate").All(&stories) |
||||
|
|
||||
|
return stories, err |
||||
|
} |
||||
|
|
||||
|
// makeStoryID makes a random story ID that's 16 characters long
|
||||
|
func makeStoryID() string { |
||||
|
result := "S" |
||||
|
offset := 0 |
||||
|
data := make([]byte, 32) |
||||
|
|
||||
|
rand.Read(data) |
||||
|
for len(result) < 16 { |
||||
|
result += strconv.FormatUint(binary.LittleEndian.Uint64(data[offset:]), 36) |
||||
|
offset += 8 |
||||
|
|
||||
|
if offset >= 32 { |
||||
|
rand.Read(data) |
||||
|
offset = 0 |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return result[:16] |
||||
|
} |
||||
|
|
||||
|
func init() { |
||||
|
store.HandleInit(func(db *mgo.Database) { |
||||
|
collection = db.C("story.stories") |
||||
|
|
||||
|
collection.EnsureIndexKey("tags") |
||||
|
collection.EnsureIndexKey("author") |
||||
|
collection.EnsureIndexKey("updatedDate") |
||||
|
collection.EnsureIndexKey("fictionalDate") |
||||
|
collection.EnsureIndexKey("listed") |
||||
|
}) |
||||
|
} |
@ -0,0 +1,11 @@ |
|||||
|
package stories |
||||
|
|
||||
|
import ( |
||||
|
"git.aiterp.net/rpdata/api/models" |
||||
|
"github.com/globalsign/mgo/bson" |
||||
|
) |
||||
|
|
||||
|
// FindID finds a story by ID
|
||||
|
func FindID(id string) (models.Story, error) { |
||||
|
return find(bson.M{"_id": id}) |
||||
|
} |
@ -0,0 +1,67 @@ |
|||||
|
package stories |
||||
|
|
||||
|
import ( |
||||
|
"time" |
||||
|
|
||||
|
"git.aiterp.net/rpdata/api/models" |
||||
|
"github.com/globalsign/mgo/bson" |
||||
|
) |
||||
|
|
||||
|
// Filter for stories.List
|
||||
|
type Filter struct { |
||||
|
Author *string |
||||
|
Tags []models.Tag |
||||
|
EarliestFictionalDate time.Time |
||||
|
LatestFictionalDate time.Time |
||||
|
Category *models.StoryCategory |
||||
|
Open *bool |
||||
|
Unlisted *bool |
||||
|
Limit int |
||||
|
} |
||||
|
|
||||
|
// List lists stories by any non-zero criteria passed with it.
|
||||
|
func List(filter *Filter) ([]models.Story, error) { |
||||
|
query := bson.M{"listed": true} |
||||
|
limit := 0 |
||||
|
|
||||
|
if filter != nil { |
||||
|
if filter.Author != nil { |
||||
|
query["author"] = *filter.Author |
||||
|
} |
||||
|
|
||||
|
if len(filter.Tags) > 0 { |
||||
|
query["tags"] = bson.M{"$in": filter.Tags} |
||||
|
} |
||||
|
|
||||
|
if !filter.EarliestFictionalDate.IsZero() && !filter.LatestFictionalDate.IsZero() { |
||||
|
query["fictionalDate"] = bson.M{ |
||||
|
"$gte": filter.EarliestFictionalDate, |
||||
|
"$lt": filter.LatestFictionalDate, |
||||
|
} |
||||
|
} else if !filter.LatestFictionalDate.IsZero() { |
||||
|
query["fictionalDate"] = bson.M{ |
||||
|
"$lt": filter.LatestFictionalDate, |
||||
|
} |
||||
|
} else if !filter.EarliestFictionalDate.IsZero() { |
||||
|
query["fictionalDate"] = bson.M{ |
||||
|
"$gte": filter.EarliestFictionalDate, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if filter.Category != nil { |
||||
|
query["category"] = *filter.Category |
||||
|
} |
||||
|
|
||||
|
if filter.Open != nil { |
||||
|
query["open"] = *filter.Open |
||||
|
} |
||||
|
|
||||
|
if filter.Unlisted != nil { |
||||
|
query["listed"] = !*filter.Unlisted |
||||
|
} |
||||
|
|
||||
|
limit = filter.Limit |
||||
|
} |
||||
|
|
||||
|
return list(query, limit) |
||||
|
} |
@ -0,0 +1,43 @@ |
|||||
|
package models |
||||
|
|
||||
|
import ( |
||||
|
"fmt" |
||||
|
"io" |
||||
|
) |
||||
|
|
||||
|
// StoryCategory represents the category of a story.
|
||||
|
type StoryCategory string |
||||
|
|
||||
|
const ( |
||||
|
// StoryCategoryInfo is a story category, see GraphQL documentation.
|
||||
|
StoryCategoryInfo StoryCategory = "Info" |
||||
|
// StoryCategoryNews is a story category, see GraphQL documentation.
|
||||
|
StoryCategoryNews StoryCategory = "News" |
||||
|
// StoryCategoryDocument is a story category, see GraphQL documentation.
|
||||
|
StoryCategoryDocument StoryCategory = "Document" |
||||
|
// StoryCategoryBackground is a story category, see GraphQL documentation.
|
||||
|
StoryCategoryBackground StoryCategory = "Background" |
||||
|
// StoryCategoryStory is a story category, see GraphQL documentation.
|
||||
|
StoryCategoryStory StoryCategory = "Story" |
||||
|
) |
||||
|
|
||||
|
// UnmarshalGQL unmarshals
|
||||
|
func (e *StoryCategory) UnmarshalGQL(v interface{}) error { |
||||
|
str, ok := v.(string) |
||||
|
if !ok { |
||||
|
return fmt.Errorf("enums must be strings") |
||||
|
} |
||||
|
|
||||
|
*e = StoryCategory(str) |
||||
|
switch *e { |
||||
|
case StoryCategoryInfo, StoryCategoryNews, StoryCategoryDocument, StoryCategoryBackground, StoryCategoryStory: |
||||
|
return nil |
||||
|
default: |
||||
|
return fmt.Errorf("%s is not a valid StoryCategory", str) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// MarshalGQL turns it into a JSON string
|
||||
|
func (e StoryCategory) MarshalGQL(w io.Writer) { |
||||
|
fmt.Fprint(w, "\""+string(e)+"\"") |
||||
|
} |
@ -0,0 +1,18 @@ |
|||||
|
package models |
||||
|
|
||||
|
import "time" |
||||
|
|
||||
|
// A Story is user content that does not have a wiki-suitable format. Documents, new stories, short stories, and so on.
|
||||
|
// The story model is a container for multiple chapters this time, in contrast to the previous version.
|
||||
|
type Story struct { |
||||
|
ID string `bson:"_id"` |
||||
|
Author string `bson:"author"` |
||||
|
Name string `bson:"name"` |
||||
|
Category StoryCategory `bson:"category"` |
||||
|
Open bool `bson:"open"` |
||||
|
Listed bool `bson:"listed"` |
||||
|
Tags []Tag `bson:"tags"` |
||||
|
CreatedDate time.Time `bson:"createdDate"` |
||||
|
FictionalDate time.Time `bson:"fictionalDate,omitempty"` |
||||
|
UpdatedDate time.Time `bson:"updatedDate"` |
||||
|
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue