Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
576e1df001 | ||
|
|
7adcdfe1e0 | ||
|
|
6f04da49b8 | ||
|
|
9b705e6514 | ||
|
|
155309693a | ||
|
|
5895e09f37 | ||
|
|
223d9922e9 | ||
|
|
429387c9ac | ||
|
|
a7113d5efe | ||
|
|
a0cb8d7fa8 | ||
|
|
5189d486a5 | ||
|
|
011cb00001 | ||
|
|
7a52504381 | ||
|
|
866a5056be | ||
|
|
e0409a6635 | ||
|
|
bae15de3e8 | ||
|
|
3f5af44dd6 | ||
|
|
4bbb8bb9ae | ||
|
|
87cd5e8e3a | ||
|
|
136302084d | ||
|
|
0122f64fbb | ||
|
|
6bfb9cbe20 | ||
|
|
fca5176579 | ||
|
|
c4f7846269 | ||
|
|
0b6e6b1e9f |
@@ -4,3 +4,4 @@ config.json
|
|||||||
api
|
api
|
||||||
debug
|
debug
|
||||||
build
|
build
|
||||||
|
.vscode
|
||||||
+28
-4
@@ -1,5 +1,6 @@
|
|||||||
|
## 1. Server
|
||||||
# Use Go 1.10
|
# Use Go 1.10
|
||||||
FROM golang:1.10
|
FROM golang:1.10 AS build-server
|
||||||
|
|
||||||
# Load repository into docker
|
# Load repository into docker
|
||||||
WORKDIR /go/src/git.aiterp.net/rpdata/api/
|
WORKDIR /go/src/git.aiterp.net/rpdata/api/
|
||||||
@@ -20,7 +21,30 @@ RUN go build -a -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-wikifile
|
|||||||
RUN go build -a -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-ensurechannels ./cmd/rpdata-ensurechannels
|
RUN go build -a -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-ensurechannels ./cmd/rpdata-ensurechannels
|
||||||
RUN go build -a -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-as2storyimport ./cmd/rpdata-as2storyimport
|
RUN go build -a -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-as2storyimport ./cmd/rpdata-as2storyimport
|
||||||
|
|
||||||
# Move artifacts into final container
|
## 2. React UI
|
||||||
FROM alpine:latest
|
# Use Node.JS 18
|
||||||
COPY --from=0 /binaries/* /usr/local/bin/
|
FROM node:10.5-alpine AS build-id
|
||||||
|
|
||||||
|
# The content is already there
|
||||||
|
WORKDIR /rpdata-ui
|
||||||
|
COPY ./rpdata-ui /rpdata-ui
|
||||||
|
|
||||||
|
# Prepare dependencies
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
# Run build
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
## 3. Regroup
|
||||||
|
# Use alpine linux
|
||||||
|
FROM alpine:3.7
|
||||||
|
|
||||||
|
# Install certificates to enable HTTPS GET requests
|
||||||
|
RUN apk add --no-cache ca-certificates
|
||||||
|
|
||||||
|
# Copy build files into final container
|
||||||
|
COPY --from=build-server /binaries/* /usr/local/bin/
|
||||||
|
COPY --from=build-id /rpdata-ui/build /var/www/
|
||||||
|
|
||||||
|
# TODO: Use proper server instead of grapihql server
|
||||||
CMD ["/usr/local/bin/rpdata-graphiql"]
|
CMD ["/usr/local/bin/rpdata-graphiql"]
|
||||||
|
|||||||
@@ -78,23 +78,37 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
story, err := story.New(result.Name, result.Author, result.Category, false, false, tags, publishDate, fictionalDate)
|
story, err := story.New(result.Name, result.Author[5:], result.Category, false, false, tags, publishDate, fictionalDate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Change the story title
|
||||||
title := result.Name
|
title := result.Name
|
||||||
if strings.HasPrefix(result.Source, "#") {
|
if strings.HasPrefix(result.Source, "#") {
|
||||||
firstNewline := strings.Index(result.Source, "\n")
|
firstNewline := strings.Index(result.Source, "\n")
|
||||||
title = result.Source[1:firstNewline]
|
result.Name = strings.Replace(result.Source[1:firstNewline], "\r", "", -1)
|
||||||
result.Source = result.Source[firstNewline+1:]
|
result.Source = result.Source[firstNewline+1:]
|
||||||
|
|
||||||
|
result.Source = strings.Replace(result.Source, "\r\n", "\n", -1)
|
||||||
|
if strings.HasPrefix(result.Source, "\r") || strings.HasPrefix(result.Source, "\n") {
|
||||||
|
result.Source = result.Source[1:]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
chapter, err := story.AddChapter(title, result.Author, result.Source, publishDate, fictionalDate)
|
chapter, err := story.AddChapter(title, result.Author[5:], result.Source, publishDate, fictionalDate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !result.Unlisted {
|
||||||
|
listed := true
|
||||||
|
err := story.Edit(nil, nil, &listed, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Println(result.ID, "->", story.ID, chapter.ID)
|
fmt.Println(result.ID, "->", story.ID, chapter.ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -8,6 +9,7 @@ import (
|
|||||||
"git.aiterp.net/rpdata/api/internal/session"
|
"git.aiterp.net/rpdata/api/internal/session"
|
||||||
"git.aiterp.net/rpdata/api/internal/store"
|
"git.aiterp.net/rpdata/api/internal/store"
|
||||||
"git.aiterp.net/rpdata/api/loader"
|
"git.aiterp.net/rpdata/api/loader"
|
||||||
|
"git.aiterp.net/rpdata/api/model/change"
|
||||||
"git.aiterp.net/rpdata/api/model/file"
|
"git.aiterp.net/rpdata/api/model/file"
|
||||||
logModel "git.aiterp.net/rpdata/api/model/log"
|
logModel "git.aiterp.net/rpdata/api/model/log"
|
||||||
"git.aiterp.net/rpdata/api/resolver"
|
"git.aiterp.net/rpdata/api/resolver"
|
||||||
@@ -89,6 +91,24 @@ func main() {
|
|||||||
w.Write(json)
|
w.Write(json)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
change.InitializeTimeline()
|
||||||
|
go func() {
|
||||||
|
index := int64(0)
|
||||||
|
|
||||||
|
for {
|
||||||
|
index++
|
||||||
|
ch, err := change.Timeline.Get(context.Background(), index)
|
||||||
|
if err == change.ErrExpired {
|
||||||
|
continue
|
||||||
|
} else if err != nil {
|
||||||
|
log.Println("Timeline:", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Change: time=%s model=%s op=%s id=%s author=%s\n", ch.Time.Local().Format("[Mon Jan 02 15:04 MST]"), ch.Model, ch.Op, ch.ObjectID, ch.Author)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
log.Fatal(http.ListenAndServe(":17000", nil))
|
log.Fatal(http.ListenAndServe(":17000", nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package change
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.aiterp.net/rpdata/api/model/counter"
|
"git.aiterp.net/rpdata/api/model/counter"
|
||||||
@@ -30,8 +31,13 @@ var PublicModels = []string{
|
|||||||
"Post",
|
"Post",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var submitMutex sync.Mutex
|
||||||
|
|
||||||
// Submit submits a change to the history.
|
// Submit submits a change to the history.
|
||||||
func Submit(model, op, author, objectID string, data interface{}) (Change, error) {
|
func Submit(model, op, author, objectID string, data interface{}) (Change, error) {
|
||||||
|
submitMutex.Lock()
|
||||||
|
defer submitMutex.Unlock()
|
||||||
|
|
||||||
index, err := counter.Next("auto_increment", "Change")
|
index, err := counter.Next("auto_increment", "Change")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Change{}, err
|
return Change{}, err
|
||||||
@@ -47,6 +53,8 @@ func Submit(model, op, author, objectID string, data interface{}) (Change, error
|
|||||||
Data: data,
|
Data: data,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Timeline.push(change)
|
||||||
|
|
||||||
err = collection.Insert(&change)
|
err = collection.Insert(&change)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Change{}, err
|
return Change{}, err
|
||||||
@@ -55,6 +63,28 @@ func Submit(model, op, author, objectID string, data interface{}) (Change, error
|
|||||||
return change, err
|
return change, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FindID gets a change by ID
|
||||||
|
func FindID(id int) (Change, error) {
|
||||||
|
change := Change{}
|
||||||
|
err := collection.FindId(id).One(&change)
|
||||||
|
if err != nil {
|
||||||
|
return Change{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return change, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// List gets all changes in ascending order
|
||||||
|
func List() ([]Change, error) {
|
||||||
|
changes := make([]Change, 0, 64)
|
||||||
|
err := collection.Find(nil).Sort("_id").All(&changes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return changes, nil
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
store.HandleInit(func(db *mgo.Database) {
|
store.HandleInit(func(db *mgo.Database) {
|
||||||
collection = db.C("common.history")
|
collection = db.C("common.history")
|
||||||
|
|||||||
@@ -0,0 +1,118 @@
|
|||||||
|
package change
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/globalsign/mgo"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrInvalidIndex is returned when a timeline subscriber tries to get a value
|
||||||
|
// with a negative id
|
||||||
|
var ErrInvalidIndex = errors.New("change.timeline: Invalid ID")
|
||||||
|
|
||||||
|
// ErrWaitRequired is returned when a timeline subscriber requests an entry that
|
||||||
|
// has not yet been pushed
|
||||||
|
var ErrWaitRequired = errors.New("change.timeline: Waiting required")
|
||||||
|
|
||||||
|
// ErrExpired is returned when a timeline subscriber requests a change that happened
|
||||||
|
// too long ago for it to be in the database.
|
||||||
|
var ErrExpired = errors.New("change.timeline: Change has expired")
|
||||||
|
|
||||||
|
type timeline struct {
|
||||||
|
mutex sync.Mutex
|
||||||
|
firstIndex int64
|
||||||
|
currentID int64
|
||||||
|
cache []Change
|
||||||
|
notifyCh chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (timeline *timeline) Get(ctx context.Context, id int64) (Change, error) {
|
||||||
|
change, err := timeline.get(id, false)
|
||||||
|
if err == ErrWaitRequired {
|
||||||
|
if err := timeline.wait(ctx); err != nil {
|
||||||
|
return Change{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return timeline.get(id, true)
|
||||||
|
} else if err == mgo.ErrNotFound {
|
||||||
|
return Change{}, ErrExpired
|
||||||
|
}
|
||||||
|
|
||||||
|
return change, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (timeline *timeline) push(change Change) {
|
||||||
|
timeline.mutex.Lock()
|
||||||
|
timeline.cache = append(timeline.cache, change)
|
||||||
|
if len(timeline.cache) > 128 {
|
||||||
|
copy(timeline.cache, timeline.cache[32:])
|
||||||
|
timeline.cache = timeline.cache[:len(timeline.cache)-32]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only update the currentID if this is more recent than the existing ones
|
||||||
|
if int64(change.ID) > timeline.currentID {
|
||||||
|
timeline.currentID = int64(change.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if timeline.notifyCh != nil {
|
||||||
|
close(timeline.notifyCh)
|
||||||
|
timeline.notifyCh = nil
|
||||||
|
}
|
||||||
|
timeline.mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (timeline *timeline) get(id int64, hasWaited bool) (Change, error) {
|
||||||
|
if id < 0 {
|
||||||
|
return Change{}, ErrInvalidIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
timeline.mutex.Lock()
|
||||||
|
if !hasWaited && id > timeline.currentID {
|
||||||
|
timeline.mutex.Unlock()
|
||||||
|
return Change{}, ErrWaitRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, change := range timeline.cache {
|
||||||
|
if change.ID == int(id) {
|
||||||
|
timeline.mutex.Unlock()
|
||||||
|
return change, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
timeline.mutex.Unlock()
|
||||||
|
|
||||||
|
return FindID(int(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (timeline *timeline) wait(ctx context.Context) error {
|
||||||
|
timeline.mutex.Lock()
|
||||||
|
if timeline.notifyCh == nil {
|
||||||
|
timeline.notifyCh = make(chan struct{})
|
||||||
|
}
|
||||||
|
|
||||||
|
ch := timeline.notifyCh
|
||||||
|
timeline.mutex.Unlock()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
case <-ch:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timeline has operations for subscribing to changes in real time
|
||||||
|
var Timeline = &timeline{}
|
||||||
|
|
||||||
|
// InitializeTimeline fills in the timeline with existing entries
|
||||||
|
func InitializeTimeline() {
|
||||||
|
changes, err := List()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, change := range changes {
|
||||||
|
Timeline.push(change)
|
||||||
|
}
|
||||||
|
}
|
||||||
+18
-10
@@ -86,16 +86,6 @@ func List(limit int) ([]Log, error) {
|
|||||||
return listLog(bson.M{}, limit)
|
return listLog(bson.M{}, limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove removes the log post with this ID. Both the long and short ID is accepted
|
|
||||||
func Remove(id string) error {
|
|
||||||
return logsCollection.Remove(bson.M{
|
|
||||||
"$or": []bson.M{
|
|
||||||
bson.M{"_id": id},
|
|
||||||
bson.M{"shortId": id},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListSearch lists the logs matching the parameters. Empty/zero values means the parameter is ingored when
|
// ListSearch lists the logs matching the parameters. Empty/zero values means the parameter is ingored when
|
||||||
// building the query. This is the old aitelogs2 way, but with the addition of a text search.
|
// building the query. This is the old aitelogs2 way, but with the addition of a text search.
|
||||||
//
|
//
|
||||||
@@ -328,6 +318,24 @@ func (log *Log) UpdateCharacters() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove removes the log and all associated posts from the database
|
||||||
|
func (log *Log) Remove() error {
|
||||||
|
err := logsCollection.Remove(bson.M{"_id": log.ID})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = postCollection.RemoveAll(bson.M{"$or": []bson.M{
|
||||||
|
bson.M{"logId": log.ID},
|
||||||
|
bson.M{"logId": log.ShortID},
|
||||||
|
}})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func findLog(query interface{}) (Log, error) {
|
func findLog(query interface{}) (Log, error) {
|
||||||
log := Log{}
|
log := Log{}
|
||||||
err := logsCollection.Find(query).One(&log)
|
err := logsCollection.Find(query).One(&log)
|
||||||
|
|||||||
+20
-1
@@ -41,7 +41,7 @@ func (chapter *Chapter) Edit(title, source *string, fictionalDate *time.Time) er
|
|||||||
changes["source"] = *source
|
changes["source"] = *source
|
||||||
changed.Source = *source
|
changed.Source = *source
|
||||||
}
|
}
|
||||||
if fictionalDate != nil && *fictionalDate != chapter.FictionalDate {
|
if fictionalDate != nil && !fictionalDate.Equal(chapter.FictionalDate) {
|
||||||
changes["fictionalDate"] = *fictionalDate
|
changes["fictionalDate"] = *fictionalDate
|
||||||
changed.FictionalDate = *fictionalDate
|
changed.FictionalDate = *fictionalDate
|
||||||
}
|
}
|
||||||
@@ -61,6 +61,25 @@ func (chapter *Chapter) Remove() error {
|
|||||||
return chapterCollection.RemoveId(chapter.ID)
|
return chapterCollection.RemoveId(chapter.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FindChapterID finds a chapter by its own ID
|
||||||
|
func FindChapterID(id string) (Chapter, error) {
|
||||||
|
chapter := Chapter{}
|
||||||
|
err := chapterCollection.FindId(id).One(&chapter)
|
||||||
|
|
||||||
|
return chapter, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListChapterStoryID lists all chapters for the story ID
|
||||||
|
func ListChapterStoryID(storyID string) ([]Chapter, error) {
|
||||||
|
chapters := make([]Chapter, 0, 8)
|
||||||
|
err := chapterCollection.Find(bson.M{"storyId": storyID}).Sort("createdDate").All(&chapters)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return chapters, nil
|
||||||
|
}
|
||||||
|
|
||||||
// makeChapterID makes a random chapter ID that's 24 characters long
|
// makeChapterID makes a random chapter ID that's 24 characters long
|
||||||
func makeChapterID() string {
|
func makeChapterID() string {
|
||||||
result := "SC"
|
result := "SC"
|
||||||
|
|||||||
+11
-6
@@ -84,23 +84,23 @@ func (story *Story) Edit(name, category *string, listed, open *bool, fictionalDa
|
|||||||
changes := bson.M{}
|
changes := bson.M{}
|
||||||
changed := *story
|
changed := *story
|
||||||
|
|
||||||
if name != nil && *name == story.Name {
|
if name != nil && *name != story.Name {
|
||||||
changes["name"] = *name
|
changes["name"] = *name
|
||||||
changed.Name = *name
|
changed.Name = *name
|
||||||
}
|
}
|
||||||
if category != nil && *category == story.Category {
|
if category != nil && *category != story.Category {
|
||||||
changes["category"] = *category
|
changes["category"] = *category
|
||||||
changed.Name = *category
|
changed.Name = *category
|
||||||
}
|
}
|
||||||
if listed != nil && *listed == story.Listed {
|
if listed != nil && *listed != story.Listed {
|
||||||
changes["listed"] = *listed
|
changes["listed"] = *listed
|
||||||
changed.Listed = *listed
|
changed.Listed = *listed
|
||||||
}
|
}
|
||||||
if open != nil && *open == story.Open {
|
if open != nil && *open != story.Open {
|
||||||
changes["open"] = *open
|
changes["open"] = *open
|
||||||
changed.Open = *open
|
changed.Open = *open
|
||||||
}
|
}
|
||||||
if fictionalDate != nil && *fictionalDate == story.FictionalDate {
|
if fictionalDate != nil && !fictionalDate.Equal(story.FictionalDate) {
|
||||||
changes["fictionalDate"] = *fictionalDate
|
changes["fictionalDate"] = *fictionalDate
|
||||||
changed.FictionalDate = *fictionalDate
|
changed.FictionalDate = *fictionalDate
|
||||||
}
|
}
|
||||||
@@ -124,6 +124,11 @@ func (story *Story) Remove() error {
|
|||||||
return storyCollection.RemoveId(story.ID)
|
return storyCollection.RemoveId(story.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Chapters calls ListChapterStoryID with the story's ID:
|
||||||
|
func (story *Story) Chapters() ([]Chapter, error) {
|
||||||
|
return ListChapterStoryID(story.ID)
|
||||||
|
}
|
||||||
|
|
||||||
// AddChapter adds a chapter to the story. This does not enforce the `Open` setting, but it will log a warning if it
|
// AddChapter adds a chapter to the story. This does not enforce the `Open` setting, but it will log a warning if it
|
||||||
// occurs
|
// occurs
|
||||||
func (story *Story) AddChapter(title, author, source string, createdDate, finctionalDate time.Time) (Chapter, error) {
|
func (story *Story) AddChapter(title, author, source string, createdDate, finctionalDate time.Time) (Chapter, error) {
|
||||||
@@ -215,7 +220,7 @@ func List(author string, tags []Tag, earliest, latest time.Time, limit int) ([]S
|
|||||||
}
|
}
|
||||||
|
|
||||||
stories := make([]Story, 0, 128)
|
stories := make([]Story, 0, 128)
|
||||||
err := storyCollection.Find(query).Limit(limit).One(&stories)
|
err := storyCollection.Find(query).Limit(limit).All(&stories)
|
||||||
|
|
||||||
return stories, err
|
return stories, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
package story
|
package story
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/globalsign/mgo/bson"
|
||||||
|
)
|
||||||
|
|
||||||
// A Tag associates a story with other content, like other stories, logs and more.
|
// A Tag associates a story with other content, like other stories, logs and more.
|
||||||
type Tag struct {
|
type Tag struct {
|
||||||
Kind string `bson:"kind"`
|
Kind string `bson:"kind"`
|
||||||
@@ -10,3 +17,20 @@ type Tag struct {
|
|||||||
func (tag *Tag) Equal(other Tag) bool {
|
func (tag *Tag) Equal(other Tag) bool {
|
||||||
return tag.Kind == other.Kind && tag.Name == other.Name
|
return tag.Kind == other.Kind && tag.Name == other.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListTags lists all tags
|
||||||
|
func ListTags() ([]Tag, error) {
|
||||||
|
tags := make([]Tag, 0, 64)
|
||||||
|
err := storyCollection.Find(bson.M{"listed": true, "tags": bson.M{"$ne": nil}}).Distinct("tags", &tags)
|
||||||
|
|
||||||
|
sort.Slice(tags, func(i, j int) bool {
|
||||||
|
kindCmp := strings.Compare(tags[i].Kind, tags[j].Kind)
|
||||||
|
if kindCmp != 0 {
|
||||||
|
return kindCmp < 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Compare(tags[i].Name, tags[j].Name) < 0
|
||||||
|
})
|
||||||
|
|
||||||
|
return tags, err
|
||||||
|
}
|
||||||
|
|||||||
+52
-15
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
|
|
||||||
"git.aiterp.net/rpdata/api/internal/session"
|
"git.aiterp.net/rpdata/api/internal/session"
|
||||||
|
"git.aiterp.net/rpdata/api/model/change"
|
||||||
"git.aiterp.net/rpdata/api/model/channel"
|
"git.aiterp.net/rpdata/api/model/channel"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -45,58 +46,92 @@ func (r *QueryResolver) Channels(ctx context.Context, args *ChannelsArgs) ([]*Ch
|
|||||||
return resolvers, nil
|
return resolvers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChannelAddEditInput is input for the addChannel mutation
|
// ChannelAddArgs is input for the addChannel mutation
|
||||||
type ChannelAddEditInput struct {
|
type ChannelAddArgs struct {
|
||||||
|
Input *struct {
|
||||||
Name string
|
Name string
|
||||||
Logged *bool
|
Logged *bool
|
||||||
Hub *bool
|
Hub *bool
|
||||||
EventName *string
|
EventName *string
|
||||||
LocationName *string
|
LocationName *string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddChannel resolves the addChannel mutation
|
// AddChannel resolves the addChannel mutation
|
||||||
func (r *MutationResolver) AddChannel(ctx context.Context, args struct{ Input *ChannelAddEditInput }) (*ChannelResolver, error) {
|
func (r *MutationResolver) AddChannel(ctx context.Context, args *ChannelAddArgs) (*ChannelResolver, error) {
|
||||||
|
input := args.Input
|
||||||
|
|
||||||
user := session.FromContext(ctx).User()
|
user := session.FromContext(ctx).User()
|
||||||
if user == nil || !user.Permitted("channel.add") {
|
if user == nil || !user.Permitted("channel.add") {
|
||||||
return nil, ErrUnauthorized
|
return nil, ErrUnauthorized
|
||||||
}
|
}
|
||||||
|
|
||||||
logged := args.Input.Logged != nil && *args.Input.Logged
|
logged := input.Logged != nil && *input.Logged
|
||||||
hub := args.Input.Hub != nil && *args.Input.Hub
|
|
||||||
|
hub := input.Hub != nil && *input.Hub
|
||||||
|
|
||||||
eventName := ""
|
eventName := ""
|
||||||
if args.Input.EventName != nil {
|
if input.EventName != nil {
|
||||||
eventName = *args.Input.EventName
|
eventName = *input.EventName
|
||||||
}
|
|
||||||
locationName := ""
|
|
||||||
if args.Input.LocationName != nil {
|
|
||||||
locationName = *args.Input.LocationName
|
|
||||||
}
|
}
|
||||||
|
|
||||||
channel, err := channel.New(args.Input.Name, logged, hub, eventName, locationName)
|
locationName := ""
|
||||||
|
if input.LocationName != nil {
|
||||||
|
locationName = *input.LocationName
|
||||||
|
}
|
||||||
|
|
||||||
|
channel, err := channel.New(input.Name, logged, hub, eventName, locationName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
go change.Submit("Channel", "add", user.ID, channel.Name, map[string]interface{}{
|
||||||
|
"logged": channel.Logged,
|
||||||
|
"hub": channel.Hub,
|
||||||
|
"location": input.LocationName,
|
||||||
|
"event": input.EventName,
|
||||||
|
})
|
||||||
|
|
||||||
return &ChannelResolver{C: channel}, nil
|
return &ChannelResolver{C: channel}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ChannelEditArgs is input for the editChannel mutation
|
||||||
|
type ChannelEditArgs struct {
|
||||||
|
Input *struct {
|
||||||
|
Name string
|
||||||
|
Logged *bool
|
||||||
|
Hub *bool
|
||||||
|
EventName *string
|
||||||
|
LocationName *string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// EditChannel resolves the editChannel mutation
|
// EditChannel resolves the editChannel mutation
|
||||||
func (r *MutationResolver) EditChannel(ctx context.Context, args struct{ Input *ChannelAddEditInput }) (*ChannelResolver, error) {
|
func (r *MutationResolver) EditChannel(ctx context.Context, args *ChannelEditArgs) (*ChannelResolver, error) {
|
||||||
|
input := args.Input
|
||||||
|
|
||||||
user := session.FromContext(ctx).User()
|
user := session.FromContext(ctx).User()
|
||||||
if user == nil || !user.Permitted("channel.edit") {
|
if user == nil || !user.Permitted("channel.edit") {
|
||||||
return nil, ErrUnauthorized
|
return nil, ErrUnauthorized
|
||||||
}
|
}
|
||||||
|
|
||||||
channel, err := channel.FindName(args.Input.Name)
|
channel, err := channel.FindName(input.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = channel.Edit(args.Input.Logged, args.Input.Hub, args.Input.EventName, args.Input.LocationName)
|
err = channel.Edit(input.Logged, input.Hub, input.EventName, input.LocationName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
go change.Submit("Channel", "edit", user.ID, channel.Name, map[string]interface{}{
|
||||||
|
"logged": input.Logged,
|
||||||
|
"hub": input.Hub,
|
||||||
|
"location": input.LocationName,
|
||||||
|
"event": input.EventName,
|
||||||
|
})
|
||||||
|
|
||||||
return &ChannelResolver{C: channel}, nil
|
return &ChannelResolver{C: channel}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,6 +152,8 @@ func (r *MutationResolver) RemoveChannel(ctx context.Context, args ChannelArgs)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
go change.Submit("Channel", "remove", user.ID, channel.Name, nil)
|
||||||
|
|
||||||
return &ChannelResolver{C: channel}, nil
|
return &ChannelResolver{C: channel}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,203 @@
|
|||||||
|
package resolver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.aiterp.net/rpdata/api/internal/session"
|
||||||
|
"git.aiterp.net/rpdata/api/model/change"
|
||||||
|
"git.aiterp.net/rpdata/api/model/story"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ChapterResolver for the Chapter graphql type
|
||||||
|
type ChapterResolver struct{ C story.Chapter }
|
||||||
|
|
||||||
|
// ChapterArgs is args for chapter query
|
||||||
|
type ChapterArgs struct {
|
||||||
|
ID string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chapter implements the chapter query
|
||||||
|
func (r *QueryResolver) Chapter(ctx context.Context, args *ChapterArgs) (*ChapterResolver, error) {
|
||||||
|
chapter, err := story.FindChapterID(args.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ChapterResolver{C: chapter}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddChapterArgs is args for the addChapter mutation
|
||||||
|
type AddChapterArgs struct {
|
||||||
|
Input *struct {
|
||||||
|
StoryID string
|
||||||
|
Title string
|
||||||
|
Author *string
|
||||||
|
Source string
|
||||||
|
FictionalDate *string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddChapter implements the addChapter mutation
|
||||||
|
func (r *MutationResolver) AddChapter(ctx context.Context, args *AddChapterArgs) (*ChapterResolver, error) {
|
||||||
|
input := args.Input
|
||||||
|
|
||||||
|
user := session.FromContext(ctx).User()
|
||||||
|
if user == nil || !user.Permitted("member", "chapter.add") {
|
||||||
|
return nil, ErrUnauthorized
|
||||||
|
}
|
||||||
|
|
||||||
|
story, err := story.FindID(input.StoryID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
author := user.ID
|
||||||
|
if input.Author != nil {
|
||||||
|
author = *input.Author
|
||||||
|
|
||||||
|
if user.ID != author && !user.Permitted("chapter.add") {
|
||||||
|
return nil, ErrPermissionDenied
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fictionalDate := time.Time{}
|
||||||
|
if input.FictionalDate != nil {
|
||||||
|
fictionalDate, err = time.Parse(time.RFC3339Nano, *input.FictionalDate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
chapter, err := story.AddChapter(input.Title, author, input.Source, time.Now(), fictionalDate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
go change.Submit("Chapter", "add", user.ID, chapter.ID, map[string]interface{}{
|
||||||
|
"title": input.Title,
|
||||||
|
"author": author,
|
||||||
|
"fictionalDate": fictionalDate,
|
||||||
|
})
|
||||||
|
|
||||||
|
return &ChapterResolver{C: chapter}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EditChapterArgs is args for the editChapter mutation
|
||||||
|
type EditChapterArgs struct {
|
||||||
|
Input *struct {
|
||||||
|
ID string
|
||||||
|
Title *string
|
||||||
|
Source *string
|
||||||
|
FictionalDate *string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EditChapter implements the editChapter mutation
|
||||||
|
func (r *MutationResolver) EditChapter(ctx context.Context, args *EditChapterArgs) (*ChapterResolver, error) {
|
||||||
|
input := args.Input
|
||||||
|
|
||||||
|
user := session.FromContext(ctx).User()
|
||||||
|
if user == nil || !user.Permitted("member", "chapter.edit") {
|
||||||
|
return nil, ErrUnauthorized
|
||||||
|
}
|
||||||
|
|
||||||
|
chapter, err := story.FindChapterID(input.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if chapter.Author != user.ID && !user.Permitted("chapter.edit") {
|
||||||
|
return nil, ErrPermissionDenied
|
||||||
|
}
|
||||||
|
|
||||||
|
var fictionalDate *time.Time
|
||||||
|
if input.FictionalDate != nil {
|
||||||
|
date, err := time.Parse(time.RFC3339Nano, *input.FictionalDate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fictionalDate = &date
|
||||||
|
}
|
||||||
|
|
||||||
|
err = chapter.Edit(input.Title, input.Source, fictionalDate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
go change.Submit("Chapter", "edit", user.ID, chapter.ID, map[string]interface{}{
|
||||||
|
"title": input.Title,
|
||||||
|
"source": input.Source,
|
||||||
|
"fictionalDate": fictionalDate,
|
||||||
|
})
|
||||||
|
|
||||||
|
return &ChapterResolver{C: chapter}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteChapterArgs is args for the addChapter mutation
|
||||||
|
type DeleteChapterArgs struct {
|
||||||
|
ID string
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveChapter implements the removeChapter mutation
|
||||||
|
func (r *MutationResolver) RemoveChapter(ctx context.Context, args *DeleteChapterArgs) (*ChapterResolver, error) {
|
||||||
|
user := session.FromContext(ctx).User()
|
||||||
|
if user == nil || !user.Permitted("member", "chapter.edit") {
|
||||||
|
return nil, ErrUnauthorized
|
||||||
|
}
|
||||||
|
|
||||||
|
chapter, err := story.FindChapterID(args.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = chapter.Remove()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
go change.Submit("Chapter", "remove", user.ID, chapter.ID, nil)
|
||||||
|
|
||||||
|
return &ChapterResolver{C: chapter}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID resolves Chapter.id
|
||||||
|
func (r *ChapterResolver) ID() string {
|
||||||
|
return r.C.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// StoryID resolves Chapter.storyId
|
||||||
|
func (r *ChapterResolver) StoryID() string {
|
||||||
|
return r.C.StoryID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Title resolves Chapter.title
|
||||||
|
func (r *ChapterResolver) Title() string {
|
||||||
|
return r.C.Title
|
||||||
|
}
|
||||||
|
|
||||||
|
// Author resolves Chapter.author
|
||||||
|
func (r *ChapterResolver) Author() string {
|
||||||
|
return r.C.Author
|
||||||
|
}
|
||||||
|
|
||||||
|
// Source resolves Chapter.source
|
||||||
|
func (r *ChapterResolver) Source() string {
|
||||||
|
return r.C.Source
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedDate resolves Chapter.createdDate
|
||||||
|
func (r *ChapterResolver) CreatedDate() string {
|
||||||
|
return r.C.CreatedDate.Format(time.RFC3339Nano)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FictionalDate resolves Chapter.fictionalDate
|
||||||
|
func (r *ChapterResolver) FictionalDate() string {
|
||||||
|
return r.C.FictionalDate.Format(time.RFC3339Nano)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EditedDate resolves Chapter.editedDate
|
||||||
|
func (r *ChapterResolver) EditedDate() string {
|
||||||
|
return r.C.EditedDate.Format(time.RFC3339Nano)
|
||||||
|
}
|
||||||
+34
-24
@@ -54,7 +54,7 @@ type CharactersArgs struct {
|
|||||||
Author *string
|
Author *string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Characters resolves
|
// Characters resolves the characters query
|
||||||
func (r *QueryResolver) Characters(ctx context.Context, args *CharactersArgs) ([]*CharacterResolver, error) {
|
func (r *QueryResolver) Characters(ctx context.Context, args *CharactersArgs) ([]*CharacterResolver, error) {
|
||||||
var chars []character.Character
|
var chars []character.Character
|
||||||
var err error
|
var err error
|
||||||
@@ -91,7 +91,7 @@ func (r *QueryResolver) Characters(ctx context.Context, args *CharactersArgs) ([
|
|||||||
return resolvers, nil
|
return resolvers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddCharacterInput is args for mutation addCharacter
|
// AddCharacterInput is args for the addCharacter mutation
|
||||||
type AddCharacterInput struct {
|
type AddCharacterInput struct {
|
||||||
Nick string
|
Nick string
|
||||||
Name string
|
Name string
|
||||||
@@ -102,24 +102,26 @@ type AddCharacterInput struct {
|
|||||||
|
|
||||||
// AddCharacter resolves the addCharacter mutation
|
// AddCharacter resolves the addCharacter mutation
|
||||||
func (r *MutationResolver) AddCharacter(ctx context.Context, args struct{ Input *AddCharacterInput }) (*CharacterResolver, error) {
|
func (r *MutationResolver) AddCharacter(ctx context.Context, args struct{ Input *AddCharacterInput }) (*CharacterResolver, error) {
|
||||||
|
input := args.Input
|
||||||
user := session.FromContext(ctx).User()
|
user := session.FromContext(ctx).User()
|
||||||
if user == nil || !user.Permitted("member", "character.add") {
|
if user == nil || !user.Permitted("member", "character.add") {
|
||||||
return nil, ErrUnauthorized
|
return nil, ErrUnauthorized
|
||||||
}
|
}
|
||||||
|
|
||||||
nick := args.Input.Nick
|
nick := input.Nick
|
||||||
name := args.Input.Name
|
|
||||||
|
name := input.Name
|
||||||
|
|
||||||
shortName := ""
|
shortName := ""
|
||||||
if args.Input.ShortName != nil {
|
if input.ShortName != nil {
|
||||||
shortName = *args.Input.ShortName
|
shortName = *input.ShortName
|
||||||
} else {
|
} else {
|
||||||
shortName = strings.SplitN(args.Input.Name, " ", 2)[0]
|
shortName = strings.SplitN(input.Name, " ", 2)[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
author := user.ID
|
author := user.ID
|
||||||
if args.Input.Author != nil {
|
if input.Author != nil {
|
||||||
author = *args.Input.Author
|
author = *input.Author
|
||||||
|
|
||||||
if author != user.ID && !user.Permitted("character.add") {
|
if author != user.ID && !user.Permitted("character.add") {
|
||||||
return nil, ErrPermissionDenied
|
return nil, ErrPermissionDenied
|
||||||
@@ -127,8 +129,8 @@ func (r *MutationResolver) AddCharacter(ctx context.Context, args struct{ Input
|
|||||||
}
|
}
|
||||||
|
|
||||||
description := ""
|
description := ""
|
||||||
if args.Input.Description != nil {
|
if input.Description != nil {
|
||||||
description = *args.Input.Description
|
description = *input.Description
|
||||||
}
|
}
|
||||||
|
|
||||||
character, err := character.New(nick, name, shortName, author, description)
|
character, err := character.New(nick, name, shortName, author, description)
|
||||||
@@ -155,12 +157,14 @@ type CharacterNickInput struct {
|
|||||||
|
|
||||||
// AddCharacterNick resolves the addCharacterNick mutation
|
// AddCharacterNick resolves the addCharacterNick mutation
|
||||||
func (r *MutationResolver) AddCharacterNick(ctx context.Context, args struct{ Input *CharacterNickInput }) (*CharacterResolver, error) {
|
func (r *MutationResolver) AddCharacterNick(ctx context.Context, args struct{ Input *CharacterNickInput }) (*CharacterResolver, error) {
|
||||||
|
input := args.Input
|
||||||
|
|
||||||
user := session.FromContext(ctx).User()
|
user := session.FromContext(ctx).User()
|
||||||
if user == nil || !user.Permitted("member") {
|
if user == nil || !user.Permitted("member") {
|
||||||
return nil, ErrUnauthorized
|
return nil, ErrUnauthorized
|
||||||
}
|
}
|
||||||
|
|
||||||
character, err := character.FindID(args.Input.ID)
|
character, err := character.FindID(input.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -168,13 +172,13 @@ func (r *MutationResolver) AddCharacterNick(ctx context.Context, args struct{ In
|
|||||||
return nil, ErrPermissionDenied
|
return nil, ErrPermissionDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
err = character.AddNick(args.Input.Nick)
|
err = character.AddNick(input.Nick)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
go change.Submit("Character", "add.nick", user.ID, character.ID, map[string]interface{}{
|
go change.Submit("Character", "add.nick", user.ID, character.ID, map[string]interface{}{
|
||||||
"nick": args.Input.Nick,
|
"nick": input.Nick,
|
||||||
})
|
})
|
||||||
|
|
||||||
log.ScheduleCharacterUpdate()
|
log.ScheduleCharacterUpdate()
|
||||||
@@ -184,12 +188,14 @@ func (r *MutationResolver) AddCharacterNick(ctx context.Context, args struct{ In
|
|||||||
|
|
||||||
// RemoveCharacterNick resolves the removeCharacterNick mutation
|
// RemoveCharacterNick resolves the removeCharacterNick mutation
|
||||||
func (r *MutationResolver) RemoveCharacterNick(ctx context.Context, args struct{ Input *CharacterNickInput }) (*CharacterResolver, error) {
|
func (r *MutationResolver) RemoveCharacterNick(ctx context.Context, args struct{ Input *CharacterNickInput }) (*CharacterResolver, error) {
|
||||||
|
input := args.Input
|
||||||
|
|
||||||
user := session.FromContext(ctx).User()
|
user := session.FromContext(ctx).User()
|
||||||
if user == nil || !user.Permitted("member") {
|
if user == nil || !user.Permitted("member") {
|
||||||
return nil, ErrUnauthorized
|
return nil, ErrUnauthorized
|
||||||
}
|
}
|
||||||
|
|
||||||
character, err := character.FindID(args.Input.ID)
|
character, err := character.FindID(input.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -197,13 +203,13 @@ func (r *MutationResolver) RemoveCharacterNick(ctx context.Context, args struct{
|
|||||||
return nil, ErrPermissionDenied
|
return nil, ErrPermissionDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
err = character.RemoveNick(args.Input.Nick)
|
err = character.RemoveNick(input.Nick)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
go change.Submit("Character", "remove.nick", user.ID, character.ID, map[string]interface{}{
|
go change.Submit("Character", "remove.nick", user.ID, character.ID, map[string]interface{}{
|
||||||
"nick": args.Input.Nick,
|
"nick": input.Nick,
|
||||||
})
|
})
|
||||||
|
|
||||||
log.ScheduleCharacterUpdate()
|
log.ScheduleCharacterUpdate()
|
||||||
@@ -221,12 +227,14 @@ type CharacterEditInput struct {
|
|||||||
|
|
||||||
// EditCharacter resolves the editCharacter mutation
|
// EditCharacter resolves the editCharacter mutation
|
||||||
func (r *MutationResolver) EditCharacter(ctx context.Context, args struct{ Input *CharacterEditInput }) (*CharacterResolver, error) {
|
func (r *MutationResolver) EditCharacter(ctx context.Context, args struct{ Input *CharacterEditInput }) (*CharacterResolver, error) {
|
||||||
|
input := args.Input
|
||||||
|
|
||||||
user := session.FromContext(ctx).User()
|
user := session.FromContext(ctx).User()
|
||||||
if user == nil || !user.Permitted("member") {
|
if user == nil || !user.Permitted("member") {
|
||||||
return nil, ErrUnauthorized
|
return nil, ErrUnauthorized
|
||||||
}
|
}
|
||||||
|
|
||||||
character, err := character.FindID(args.Input.ID)
|
character, err := character.FindID(input.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -235,16 +243,18 @@ func (r *MutationResolver) EditCharacter(ctx context.Context, args struct{ Input
|
|||||||
}
|
}
|
||||||
|
|
||||||
name := ""
|
name := ""
|
||||||
if args.Input.Name != nil {
|
if input.Name != nil {
|
||||||
name = *args.Input.Name
|
name = *input.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
shortName := ""
|
shortName := ""
|
||||||
if args.Input.ShortName != nil {
|
if input.ShortName != nil {
|
||||||
shortName = *args.Input.ShortName
|
shortName = *input.ShortName
|
||||||
}
|
}
|
||||||
|
|
||||||
description := ""
|
description := ""
|
||||||
if args.Input.Description != nil {
|
if input.Description != nil {
|
||||||
description = *args.Input.Description
|
description = *input.Description
|
||||||
}
|
}
|
||||||
|
|
||||||
err = character.Edit(name, shortName, description)
|
err = character.Edit(name, shortName, description)
|
||||||
|
|||||||
+9
-5
@@ -60,21 +60,25 @@ func (r *QueryResolver) Files(ctx context.Context, args *FilesArgs) ([]*FileReso
|
|||||||
return resolvers, nil
|
return resolvers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FileEditInput is an input for the editFile mutation
|
// FileEditArgs is args for the editFile mutation
|
||||||
type FileEditInput struct {
|
type FileEditArgs struct {
|
||||||
|
Input *struct {
|
||||||
ID string
|
ID string
|
||||||
Name *string
|
Name *string
|
||||||
Public *bool
|
Public *bool
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// EditFile resolves the editFile mutation
|
// EditFile resolves the editFile mutation
|
||||||
func (r *MutationResolver) EditFile(ctx context.Context, args *struct{ Input FileEditInput }) (*FileResolver, error) {
|
func (r *MutationResolver) EditFile(ctx context.Context, args *FileEditArgs) (*FileResolver, error) {
|
||||||
|
input := args.Input
|
||||||
|
|
||||||
user := session.FromContext(ctx).User()
|
user := session.FromContext(ctx).User()
|
||||||
if user == nil || !user.Permitted("member") {
|
if user == nil || !user.Permitted("member") {
|
||||||
return nil, ErrUnauthorized
|
return nil, ErrUnauthorized
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err := file.FindID(args.Input.ID)
|
file, err := file.FindID(input.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -82,7 +86,7 @@ func (r *MutationResolver) EditFile(ctx context.Context, args *struct{ Input Fil
|
|||||||
return nil, ErrUnauthorized
|
return nil, ErrUnauthorized
|
||||||
}
|
}
|
||||||
|
|
||||||
err = file.Edit(args.Input.Name, args.Input.Public)
|
err = file.Edit(input.Name, input.Public)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
+56
-43
@@ -44,18 +44,20 @@ func (r *QueryResolver) Log(ctx context.Context, args *LogArgs) (*LogResolver, e
|
|||||||
return &LogResolver{L: l}, nil
|
return &LogResolver{L: l}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LogQueryInput is an input
|
// LogQueryArgs is args for the logs query
|
||||||
type LogQueryInput struct {
|
type LogQueryArgs struct {
|
||||||
|
Input *struct {
|
||||||
Search *string
|
Search *string
|
||||||
Characters *[]string
|
Characters *[]string
|
||||||
Channels *[]string
|
Channels *[]string
|
||||||
Events *[]string
|
Events *[]string
|
||||||
Open *bool
|
Open *bool
|
||||||
Limit *int32
|
Limit *int32
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logs lists logs
|
// Logs resolves the logs query
|
||||||
func (r *QueryResolver) Logs(ctx context.Context, args *struct{ Input *LogQueryInput }) ([]*LogResolver, error) {
|
func (r *QueryResolver) Logs(ctx context.Context, args *LogQueryArgs) ([]*LogResolver, error) {
|
||||||
var logs []log.Log
|
var logs []log.Log
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
@@ -65,28 +67,34 @@ func (r *QueryResolver) Logs(ctx context.Context, args *struct{ Input *LogQueryI
|
|||||||
// Parse input
|
// Parse input
|
||||||
limit := 100
|
limit := 100
|
||||||
search := ""
|
search := ""
|
||||||
|
|
||||||
if input.Search != nil {
|
if input.Search != nil {
|
||||||
search = *input.Search
|
search = *input.Search
|
||||||
limit = 0
|
limit = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
channels := []string(nil)
|
channels := []string(nil)
|
||||||
if input.Channels != nil {
|
if input.Channels != nil {
|
||||||
channels = *input.Channels
|
channels = *input.Channels
|
||||||
limit = 0
|
limit = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
characters := []string(nil)
|
characters := []string(nil)
|
||||||
if input.Characters != nil {
|
if input.Characters != nil {
|
||||||
characters = *input.Characters
|
characters = *input.Characters
|
||||||
limit = 0
|
limit = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
events := []string(nil)
|
events := []string(nil)
|
||||||
if input.Events != nil {
|
if input.Events != nil {
|
||||||
events = *input.Events
|
events = *input.Events
|
||||||
limit = 0
|
limit = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if input.Limit != nil {
|
if input.Limit != nil {
|
||||||
limit = int(*input.Limit)
|
limit = int(*input.Limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
open := input.Open != nil && *input.Open == true
|
open := input.Open != nil && *input.Open == true
|
||||||
|
|
||||||
logs, err = log.ListSearch(search, channels, characters, events, open, limit)
|
logs, err = log.ListSearch(search, channels, characters, events, open, limit)
|
||||||
@@ -108,18 +116,22 @@ func (r *QueryResolver) Logs(ctx context.Context, args *struct{ Input *LogQueryI
|
|||||||
return resolvers, nil
|
return resolvers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LogAddInput is an input
|
// LogAddArgs is args for the addLog mutation
|
||||||
type LogAddInput struct {
|
type LogAddArgs struct {
|
||||||
|
Input *struct {
|
||||||
Date string
|
Date string
|
||||||
Channel string
|
Channel string
|
||||||
Title *string
|
Title *string
|
||||||
Open *bool
|
Open *bool
|
||||||
Event *string
|
Event *string
|
||||||
Description *string
|
Description *string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddLog resolves the addLog mutation
|
// AddLog resolves the addLog mutation
|
||||||
func (r *MutationResolver) AddLog(ctx context.Context, args *struct{ Input LogAddInput }) (*LogResolver, error) {
|
func (r *MutationResolver) AddLog(ctx context.Context, args *LogAddArgs) (*LogResolver, error) {
|
||||||
|
input := args.Input
|
||||||
|
|
||||||
user := session.FromContext(ctx).User()
|
user := session.FromContext(ctx).User()
|
||||||
if user == nil || !user.Permitted("log.add") {
|
if user == nil || !user.Permitted("log.add") {
|
||||||
return nil, ErrUnauthorized
|
return nil, ErrUnauthorized
|
||||||
@@ -131,25 +143,28 @@ func (r *MutationResolver) AddLog(ctx context.Context, args *struct{ Input LogAd
|
|||||||
}
|
}
|
||||||
|
|
||||||
title := ""
|
title := ""
|
||||||
if args.Input.Title != nil {
|
if input.Title != nil {
|
||||||
title = *args.Input.Title
|
title = *input.Title
|
||||||
}
|
}
|
||||||
event := ""
|
|
||||||
if args.Input.Event != nil {
|
|
||||||
event = *args.Input.Event
|
|
||||||
}
|
|
||||||
description := ""
|
|
||||||
if args.Input.Description != nil {
|
|
||||||
description = *args.Input.Description
|
|
||||||
}
|
|
||||||
open := args.Input.Open != nil && *args.Input.Open == true
|
|
||||||
|
|
||||||
log, err := log.New(date, args.Input.Channel, title, event, description, open)
|
event := ""
|
||||||
|
if input.Event != nil {
|
||||||
|
event = *input.Event
|
||||||
|
}
|
||||||
|
|
||||||
|
description := ""
|
||||||
|
if input.Description != nil {
|
||||||
|
description = *input.Description
|
||||||
|
}
|
||||||
|
|
||||||
|
open := input.Open != nil && *input.Open == true
|
||||||
|
|
||||||
|
log, err := log.New(date, input.Channel, title, event, description, open)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
change.Submit("Log", "add", user.ID, log.ID, map[string]interface{}{
|
go change.Submit("Log", "add", user.ID, log.ID, map[string]interface{}{
|
||||||
"channel": log.Channel,
|
"channel": log.Channel,
|
||||||
"title": log.Title,
|
"title": log.Title,
|
||||||
"event": log.Event,
|
"event": log.Event,
|
||||||
@@ -160,41 +175,45 @@ func (r *MutationResolver) AddLog(ctx context.Context, args *struct{ Input LogAd
|
|||||||
return &LogResolver{L: log}, nil
|
return &LogResolver{L: log}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LogEditInput is an input
|
// LogEditArgs is an input
|
||||||
type LogEditInput struct {
|
type LogEditArgs struct {
|
||||||
|
Input *struct {
|
||||||
ID string
|
ID string
|
||||||
Title *string
|
Title *string
|
||||||
Event *string
|
Event *string
|
||||||
Description *string
|
Description *string
|
||||||
Open *bool
|
Open *bool
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// EditLog resolves the addLog mutation
|
// EditLog resolves the editLog mutation
|
||||||
func (r *MutationResolver) EditLog(ctx context.Context, args *struct{ Input LogEditInput }) (*LogResolver, error) {
|
func (r *MutationResolver) EditLog(ctx context.Context, args *LogEditArgs) (*LogResolver, error) {
|
||||||
|
input := args.Input
|
||||||
|
|
||||||
user := session.FromContext(ctx).User()
|
user := session.FromContext(ctx).User()
|
||||||
if user == nil || !user.Permitted("log.edit") {
|
if user == nil || !user.Permitted("log.edit") {
|
||||||
return nil, ErrUnauthorized
|
return nil, ErrUnauthorized
|
||||||
}
|
}
|
||||||
|
|
||||||
l, err := log.FindID(args.Input.ID)
|
log, err := log.FindID(input.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = l.Edit(args.Input.Title, args.Input.Event, args.Input.Description, args.Input.Open)
|
err = log.Edit(input.Title, input.Event, input.Description, input.Open)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
change.Submit("Log", "edit", user.ID, l.ID, map[string]interface{}{
|
go change.Submit("Log", "edit", user.ID, log.ID, map[string]interface{}{
|
||||||
"channel": l.Channel,
|
"channel": log.Channel,
|
||||||
"title": args.Input.Title,
|
"title": input.Title,
|
||||||
"event": args.Input.Event,
|
"event": input.Event,
|
||||||
"description": args.Input.Description,
|
"description": input.Description,
|
||||||
"open": args.Input.Open,
|
"open": input.Open,
|
||||||
})
|
})
|
||||||
|
|
||||||
return &LogResolver{L: l}, nil
|
return &LogResolver{L: log}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveLog resolves the removeLog mutation
|
// RemoveLog resolves the removeLog mutation
|
||||||
@@ -204,25 +223,19 @@ func (r *MutationResolver) RemoveLog(ctx context.Context, args *struct{ ID strin
|
|||||||
return nil, ErrUnauthorized
|
return nil, ErrUnauthorized
|
||||||
}
|
}
|
||||||
|
|
||||||
l, err := log.FindID(args.ID)
|
log, err := log.FindID(args.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = log.Remove(args.ID)
|
err = log.Remove()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
change.Submit("Log", "add", user.ID, l.ID, map[string]interface{}{
|
go change.Submit("Log", "remove", user.ID, log.ID, nil)
|
||||||
"channel": l.Channel,
|
|
||||||
"title": l.Title,
|
|
||||||
"event": l.Event,
|
|
||||||
"description": l.Description,
|
|
||||||
"open": l.Open,
|
|
||||||
})
|
|
||||||
|
|
||||||
return &LogResolver{L: l}, nil
|
return &LogResolver{L: log}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ID resolves Log.id
|
// ID resolves Log.id
|
||||||
|
|||||||
+37
-25
@@ -47,38 +47,42 @@ func (r *QueryResolver) Posts(ctx context.Context, args *PostsArgs) ([]*PostReso
|
|||||||
return resolvers, nil
|
return resolvers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PostAddInput is an input
|
// PostAddArgs is args for addPost mutation
|
||||||
type PostAddInput struct {
|
type PostAddArgs struct {
|
||||||
|
Input *struct {
|
||||||
LogID string
|
LogID string
|
||||||
Time string
|
Time string
|
||||||
Kind string
|
Kind string
|
||||||
Nick string
|
Nick string
|
||||||
Text string
|
Text string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddPost resolves the addPost mutation
|
// AddPost resolves the addPost mutation
|
||||||
func (r *MutationResolver) AddPost(ctx context.Context, args struct{ Input *PostAddInput }) (*PostResolver, error) {
|
func (r *MutationResolver) AddPost(ctx context.Context, args *PostAddArgs) (*PostResolver, error) {
|
||||||
|
input := args.Input
|
||||||
|
|
||||||
user := session.FromContext(ctx).User()
|
user := session.FromContext(ctx).User()
|
||||||
if user == nil || !user.Permitted("post.add") {
|
if user == nil || !user.Permitted("post.add") {
|
||||||
return nil, ErrUnauthorized
|
return nil, ErrUnauthorized
|
||||||
}
|
}
|
||||||
|
|
||||||
postTime, err := time.Parse(time.RFC3339Nano, args.Input.Time)
|
postTime, err := time.Parse(time.RFC3339Nano, input.Time)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log, err := log.FindID(args.Input.LogID)
|
log, err := log.FindID(input.LogID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
post, err := log.NewPost(postTime, args.Input.Kind, args.Input.Nick, args.Input.Text)
|
post, err := log.NewPost(postTime, input.Kind, input.Nick, input.Text)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
change.Submit("Post", "add", user.ID, post.ID, map[string]interface{}{
|
go change.Submit("Post", "add", user.ID, post.ID, map[string]interface{}{
|
||||||
"logId": post.LogID,
|
"logId": post.LogID,
|
||||||
"time": post.Time,
|
"time": post.Time,
|
||||||
"kind": post.Kind,
|
"kind": post.Kind,
|
||||||
@@ -92,17 +96,21 @@ func (r *MutationResolver) AddPost(ctx context.Context, args struct{ Input *Post
|
|||||||
return &PostResolver{P: post}, nil
|
return &PostResolver{P: post}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PostEditInput is an input
|
// PostEditArgs is args for the editPost mutation
|
||||||
type PostEditInput struct {
|
type PostEditArgs struct {
|
||||||
|
Input *struct {
|
||||||
ID string
|
ID string
|
||||||
Time *string
|
Time *string
|
||||||
Kind *string
|
Kind *string
|
||||||
Nick *string
|
Nick *string
|
||||||
Text *string
|
Text *string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// EditPost resolves the editPost mutation
|
// EditPost resolves the editPost mutation
|
||||||
func (r *MutationResolver) EditPost(ctx context.Context, args struct{ Input *PostEditInput }) (*PostResolver, error) {
|
func (r *MutationResolver) EditPost(ctx context.Context, args *PostEditArgs) (*PostResolver, error) {
|
||||||
|
input := args.Input
|
||||||
|
|
||||||
user := session.FromContext(ctx).User()
|
user := session.FromContext(ctx).User()
|
||||||
if user == nil || !user.Permitted("post.edit") {
|
if user == nil || !user.Permitted("post.edit") {
|
||||||
return nil, ErrUnauthorized
|
return nil, ErrUnauthorized
|
||||||
@@ -110,7 +118,7 @@ func (r *MutationResolver) EditPost(ctx context.Context, args struct{ Input *Pos
|
|||||||
|
|
||||||
postTime := (*time.Time)(nil)
|
postTime := (*time.Time)(nil)
|
||||||
if args.Input.Time != nil {
|
if args.Input.Time != nil {
|
||||||
t, err := time.Parse(time.RFC3339Nano, *args.Input.Time)
|
t, err := time.Parse(time.RFC3339Nano, *input.Time)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -118,21 +126,21 @@ func (r *MutationResolver) EditPost(ctx context.Context, args struct{ Input *Pos
|
|||||||
postTime = &t
|
postTime = &t
|
||||||
}
|
}
|
||||||
|
|
||||||
post, err := log.FindPostID(args.Input.ID)
|
post, err := log.FindPostID(input.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = post.Edit(postTime, args.Input.Kind, args.Input.Nick, args.Input.Text)
|
err = post.Edit(postTime, input.Kind, input.Nick, input.Text)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
change.Submit("Post", "edit", user.ID, post.ID, map[string]interface{}{
|
go change.Submit("Post", "edit", user.ID, post.ID, map[string]interface{}{
|
||||||
"time": postTime,
|
"time": postTime,
|
||||||
"kind": args.Input.Kind,
|
"kind": input.Kind,
|
||||||
"nick": args.Input.Nick,
|
"nick": input.Nick,
|
||||||
"text": args.Input.Text,
|
"text": input.Text,
|
||||||
})
|
})
|
||||||
|
|
||||||
go log.UpdateCharacters(post.LogID)
|
go log.UpdateCharacters(post.LogID)
|
||||||
@@ -140,32 +148,36 @@ func (r *MutationResolver) EditPost(ctx context.Context, args struct{ Input *Pos
|
|||||||
return &PostResolver{P: post}, nil
|
return &PostResolver{P: post}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PostMoveInput is an input
|
// PostMoveArgs is args for movePost mutation
|
||||||
type PostMoveInput struct {
|
type PostMoveArgs struct {
|
||||||
|
Input *struct {
|
||||||
ID string
|
ID string
|
||||||
ToPosition int32
|
ToPosition int32
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MovePost resolves the movePost mutation
|
// MovePost resolves the movePost mutation
|
||||||
func (r *MutationResolver) MovePost(ctx context.Context, args struct{ Input *PostMoveInput }) (*PostResolver, error) {
|
func (r *MutationResolver) MovePost(ctx context.Context, args *PostMoveArgs) (*PostResolver, error) {
|
||||||
|
input := args.Input
|
||||||
|
|
||||||
user := session.FromContext(ctx).User()
|
user := session.FromContext(ctx).User()
|
||||||
if user == nil || !user.Permitted("post.move") {
|
if user == nil || !user.Permitted("post.move") {
|
||||||
return nil, ErrUnauthorized
|
return nil, ErrUnauthorized
|
||||||
}
|
}
|
||||||
|
|
||||||
post, err := log.FindPostID(args.Input.ID)
|
post, err := log.FindPostID(input.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = post.Move(int(args.Input.ToPosition))
|
err = post.Move(int(input.ToPosition))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
change.Submit("Post", "move", user.ID, post.ID, map[string]interface{}{
|
go change.Submit("Post", "move", user.ID, post.ID, map[string]interface{}{
|
||||||
"logId": post.LogID,
|
"logId": post.LogID,
|
||||||
"targetIndex": args.Input.ToPosition,
|
"targetIndex": input.ToPosition,
|
||||||
})
|
})
|
||||||
|
|
||||||
return &PostResolver{P: post}, nil
|
return &PostResolver{P: post}, nil
|
||||||
@@ -188,7 +200,7 @@ func (r *MutationResolver) RemovePost(ctx context.Context, args PostRemoveArgs)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
change.Submit("Post", "remove", user.ID, post.ID, map[string]interface{}{
|
go change.Submit("Post", "remove", user.ID, post.ID, map[string]interface{}{
|
||||||
"logId": post.LogID,
|
"logId": post.LogID,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1,401 @@
|
|||||||
package resolver
|
package resolver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.aiterp.net/rpdata/api/internal/session"
|
||||||
|
"git.aiterp.net/rpdata/api/model/change"
|
||||||
|
"git.aiterp.net/rpdata/api/model/story"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StoryResolver for the Story graphql type
|
||||||
|
type StoryResolver struct{ S story.Story }
|
||||||
|
|
||||||
|
// StoryArgs is args for story query
|
||||||
|
type StoryArgs struct {
|
||||||
|
ID string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Story implements the story query
|
||||||
|
func (r *QueryResolver) Story(ctx context.Context, args *StoryArgs) (*StoryResolver, error) {
|
||||||
|
story, err := story.FindID(args.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &StoryResolver{S: story}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StoriesArg is args for stories query
|
||||||
|
type StoriesArg struct {
|
||||||
|
Input *struct {
|
||||||
|
Author *string
|
||||||
|
Tags *[]TagInput
|
||||||
|
EarliestFictionalDate *string
|
||||||
|
LatestFictionalDate *string
|
||||||
|
Limit *int32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stories implements the stories query
|
||||||
|
func (r *QueryResolver) Stories(ctx context.Context, args *StoriesArg) ([]*StoryResolver, error) {
|
||||||
|
input := args.Input
|
||||||
|
|
||||||
|
author := ""
|
||||||
|
if input.Author != nil {
|
||||||
|
author = *input.Author
|
||||||
|
}
|
||||||
|
|
||||||
|
tags := make([]story.Tag, 0, 8)
|
||||||
|
if input.Tags != nil {
|
||||||
|
for _, tagInput := range *input.Tags {
|
||||||
|
tags = append(tags, story.Tag{
|
||||||
|
Kind: tagInput.Kind,
|
||||||
|
Name: tagInput.Name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
earliest := time.Time{}
|
||||||
|
err := error(nil)
|
||||||
|
if input.EarliestFictionalDate != nil {
|
||||||
|
earliest, err = time.Parse(time.RFC3339Nano, *input.EarliestFictionalDate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
latest := time.Time{}
|
||||||
|
if input.LatestFictionalDate != nil {
|
||||||
|
latest, err = time.Parse(time.RFC3339Nano, *input.LatestFictionalDate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
limit := 30
|
||||||
|
if input.Limit != nil {
|
||||||
|
limit = int(*input.Limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
stories, err := story.List(author, tags, earliest, latest, limit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resolvers := make([]*StoryResolver, len(stories))
|
||||||
|
for i, story := range stories {
|
||||||
|
resolvers[i] = &StoryResolver{S: story}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolvers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StoryAddArgs is args for the addStory mutation
|
||||||
|
type StoryAddArgs struct {
|
||||||
|
Input *struct {
|
||||||
|
Name string
|
||||||
|
Category string
|
||||||
|
Author *string
|
||||||
|
Open *bool
|
||||||
|
Listed *bool
|
||||||
|
Tags *[]TagInput
|
||||||
|
FictionalDate *string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddStory implements the addStory mutation
|
||||||
|
func (r *MutationResolver) AddStory(ctx context.Context, args *StoryAddArgs) (*StoryResolver, error) {
|
||||||
|
input := args.Input
|
||||||
|
|
||||||
|
user := session.FromContext(ctx).User()
|
||||||
|
if user == nil || !user.Permitted("member", "story.add") {
|
||||||
|
return nil, ErrUnauthorized
|
||||||
|
}
|
||||||
|
|
||||||
|
author := user.ID
|
||||||
|
if input.Author != nil {
|
||||||
|
author = *input.Author
|
||||||
|
|
||||||
|
if user.ID != author && !user.Permitted("story.add") {
|
||||||
|
return nil, ErrPermissionDenied
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
listed := (input.Listed != nil && *input.Listed == true)
|
||||||
|
|
||||||
|
open := (input.Open != nil && *input.Open == true)
|
||||||
|
|
||||||
|
tags := make([]story.Tag, 0, 8)
|
||||||
|
if input.Tags != nil {
|
||||||
|
for _, tagInput := range *input.Tags {
|
||||||
|
tags = append(tags, story.Tag{
|
||||||
|
Kind: tagInput.Kind,
|
||||||
|
Name: tagInput.Name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fictionalDate := time.Time{}
|
||||||
|
if input.FictionalDate != nil {
|
||||||
|
date, err := time.Parse(time.RFC3339Nano, *input.FictionalDate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fictionalDate = date
|
||||||
|
}
|
||||||
|
|
||||||
|
story, err := story.New(input.Name, author, input.Category, listed, open, tags, time.Now(), fictionalDate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
go change.Submit("Story", "add", user.ID, story.ID, map[string]interface{}{
|
||||||
|
"name": input.Name,
|
||||||
|
"category": input.Category,
|
||||||
|
"author": input.Author,
|
||||||
|
})
|
||||||
|
|
||||||
|
return &StoryResolver{S: story}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StoryTagAddArgs is args for the addStoryTag mutation
|
||||||
|
type StoryTagAddArgs struct {
|
||||||
|
Input *struct {
|
||||||
|
ID string
|
||||||
|
Tag TagInput
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddStoryTag implements the addStoryTag mutation
|
||||||
|
func (r *MutationResolver) AddStoryTag(ctx context.Context, args *StoryTagAddArgs) (*StoryResolver, error) {
|
||||||
|
input := args.Input
|
||||||
|
tag := story.Tag{Kind: input.Tag.Kind, Name: input.Tag.Name}
|
||||||
|
|
||||||
|
user := session.FromContext(ctx).User()
|
||||||
|
if user == nil || !user.Permitted("member", "story.edit") {
|
||||||
|
return nil, ErrUnauthorized
|
||||||
|
}
|
||||||
|
|
||||||
|
story, err := story.FindID(input.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if story.Author != user.ID && !user.Permitted("story.edit") {
|
||||||
|
return nil, ErrPermissionDenied
|
||||||
|
}
|
||||||
|
|
||||||
|
err = story.AddTag(tag)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
go change.Submit("Story", "add.tag", user.ID, story.ID, map[string]interface{}{
|
||||||
|
"kind": tag.Kind,
|
||||||
|
"name": tag.Name,
|
||||||
|
})
|
||||||
|
|
||||||
|
return &StoryResolver{S: story}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StoryTagRemoveArgs is args for the removeStoryTag mutation
|
||||||
|
type StoryTagRemoveArgs struct {
|
||||||
|
Input *struct {
|
||||||
|
ID string
|
||||||
|
Tag TagInput
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveStoryTag implements the removeStoryTag mutation
|
||||||
|
func (r *MutationResolver) RemoveStoryTag(ctx context.Context, args *StoryTagRemoveArgs) (*StoryResolver, error) {
|
||||||
|
input := args.Input
|
||||||
|
tag := story.Tag{Kind: input.Tag.Kind, Name: input.Tag.Name}
|
||||||
|
|
||||||
|
user := session.FromContext(ctx).User()
|
||||||
|
if user == nil || !user.Permitted("member", "story.edit") {
|
||||||
|
return nil, ErrUnauthorized
|
||||||
|
}
|
||||||
|
|
||||||
|
story, err := story.FindID(input.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if story.Author != user.ID && !user.Permitted("story.edit") {
|
||||||
|
return nil, ErrPermissionDenied
|
||||||
|
}
|
||||||
|
|
||||||
|
err = story.RemoveTag(tag)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
go change.Submit("Story", "remove.tag", user.ID, story.ID, map[string]interface{}{
|
||||||
|
"kind": tag.Kind,
|
||||||
|
"name": tag.Name,
|
||||||
|
})
|
||||||
|
|
||||||
|
return &StoryResolver{S: story}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StoryEditArgs is args for the addStory mutation
|
||||||
|
type StoryEditArgs struct {
|
||||||
|
Input *struct {
|
||||||
|
ID string
|
||||||
|
Name *string
|
||||||
|
Category *string
|
||||||
|
Author *string
|
||||||
|
Open *bool
|
||||||
|
Listed *bool
|
||||||
|
FictionalDate *string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EditStory implements the editStory mutation
|
||||||
|
func (r *MutationResolver) EditStory(ctx context.Context, args *StoryEditArgs) (*StoryResolver, error) {
|
||||||
|
input := args.Input
|
||||||
|
|
||||||
|
user := session.FromContext(ctx).User()
|
||||||
|
if user == nil || !user.Permitted("member", "story.edit") {
|
||||||
|
return nil, ErrUnauthorized
|
||||||
|
}
|
||||||
|
|
||||||
|
story, err := story.FindID(input.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if story.Author != user.ID && !user.Permitted("story.edit") {
|
||||||
|
return nil, ErrPermissionDenied
|
||||||
|
}
|
||||||
|
|
||||||
|
var fictionalDate *time.Time
|
||||||
|
if input.FictionalDate != nil {
|
||||||
|
date, err := time.Parse(time.RFC3339Nano, *input.FictionalDate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fictionalDate = &date
|
||||||
|
}
|
||||||
|
|
||||||
|
err = story.Edit(input.Name, input.Category, input.Listed, input.Open, fictionalDate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
go change.Submit("Story", "edit", user.ID, story.ID, map[string]interface{}{
|
||||||
|
"name": input.Name,
|
||||||
|
"category": input.Category,
|
||||||
|
"author": input.Author,
|
||||||
|
"open": input.Open,
|
||||||
|
"listed": input.Listed,
|
||||||
|
"fictionalDate": input.FictionalDate,
|
||||||
|
})
|
||||||
|
|
||||||
|
return &StoryResolver{S: story}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StoryRemoveArgs is args for the removeStory mutation
|
||||||
|
type StoryRemoveArgs struct {
|
||||||
|
ID string
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveStory implements the removeStory mutation
|
||||||
|
func (r *MutationResolver) RemoveStory(ctx context.Context, args *StoryRemoveArgs) (*StoryResolver, error) {
|
||||||
|
user := session.FromContext(ctx).User()
|
||||||
|
if user == nil || !user.Permitted("member", "story.edit") {
|
||||||
|
return nil, ErrUnauthorized
|
||||||
|
}
|
||||||
|
|
||||||
|
story, err := story.FindID(args.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if story.Author != user.ID && !user.Permitted("story.remove") {
|
||||||
|
return nil, ErrPermissionDenied
|
||||||
|
}
|
||||||
|
|
||||||
|
err = story.Remove()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
go change.Submit("Story", "remove", user.ID, story.ID, nil)
|
||||||
|
|
||||||
|
return &StoryResolver{S: story}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID resolves Story.id
|
||||||
|
func (r *StoryResolver) ID() string {
|
||||||
|
return r.S.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Author resolves Story.author
|
||||||
|
func (r *StoryResolver) Author() string {
|
||||||
|
return r.S.Author
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name resolves Story.name
|
||||||
|
func (r *StoryResolver) Name() string {
|
||||||
|
return r.S.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Category resolves Story.category
|
||||||
|
func (r *StoryResolver) Category() string {
|
||||||
|
return r.S.Category
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open resolves Story.open
|
||||||
|
func (r *StoryResolver) Open() bool {
|
||||||
|
return r.S.Open
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listed resolves Story.listed
|
||||||
|
func (r *StoryResolver) Listed() bool {
|
||||||
|
return r.S.Listed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tags resolves Story.tags
|
||||||
|
func (r *StoryResolver) Tags() []*TagResolver {
|
||||||
|
resolvers := make([]*TagResolver, len(r.S.Tags))
|
||||||
|
for i, tag := range r.S.Tags {
|
||||||
|
resolvers[i] = &TagResolver{T: tag}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolvers
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chapters resolves Story.chapters
|
||||||
|
func (r *StoryResolver) Chapters() ([]*ChapterResolver, error) {
|
||||||
|
chapters, err := r.S.Chapters()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resolvers := make([]*ChapterResolver, len(chapters))
|
||||||
|
for i, chapter := range chapters {
|
||||||
|
resolvers[i] = &ChapterResolver{C: chapter}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolvers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatedDate resolves Story.createdDate
|
||||||
|
func (r *StoryResolver) CreatedDate() string {
|
||||||
|
return r.S.CreatedDate.Format(time.RFC3339Nano)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FictionalDate resolves Story.fictionalDate
|
||||||
|
func (r *StoryResolver) FictionalDate() string {
|
||||||
|
return r.S.FictionalDate.Format(time.RFC3339Nano)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatedDate resolves Story.updatedDate
|
||||||
|
func (r *StoryResolver) UpdatedDate() string {
|
||||||
|
return r.S.UpdatedDate.Format(time.RFC3339Nano)
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package resolver
|
||||||
|
|
||||||
|
import "git.aiterp.net/rpdata/api/model/story"
|
||||||
|
|
||||||
|
// TagResolver for the Tag graphql type
|
||||||
|
type TagResolver struct{ T story.Tag }
|
||||||
|
|
||||||
|
// TagInput resolves the TagInput input
|
||||||
|
type TagInput struct {
|
||||||
|
Kind string
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tags implements the tags query
|
||||||
|
func (r *QueryResolver) Tags() ([]*TagResolver, error) {
|
||||||
|
tags, err := story.ListTags()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resolvers := make([]*TagResolver, len(tags))
|
||||||
|
for i, tag := range tags {
|
||||||
|
resolvers[i] = &TagResolver{T: tag}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolvers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kind resolves Tag.kind
|
||||||
|
func (r *TagResolver) Kind() string {
|
||||||
|
return r.T.Kind
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name resolves Tag.name
|
||||||
|
func (r *TagResolver) Name() string {
|
||||||
|
return r.T.Name
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# User Interface
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
This is a UI created with `create-react-app`, so the process of getting started on a new host is navigating to the directory and running these commands.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
## Release
|
||||||
|
|
||||||
|
This is built and packaged with the parent project.
|
||||||
Generated
+12036
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"name": "rpdata-ui",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"ajv": "^6.5.2",
|
||||||
|
"apollo-boost": "^0.1.10",
|
||||||
|
"graphql": "^0.13.2",
|
||||||
|
"graphql-tag": "^2.9.2",
|
||||||
|
"pluralize": "^7.0.0",
|
||||||
|
"react": "^16.4.1",
|
||||||
|
"react-apollo": "^2.1.9",
|
||||||
|
"react-dom": "^16.4.1",
|
||||||
|
"react-router": "^4.3.1",
|
||||||
|
"react-router-dom": "^4.3.1",
|
||||||
|
"react-scripts": "1.1.4"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "react-scripts start",
|
||||||
|
"build": "react-scripts build",
|
||||||
|
"test": "react-scripts test --env=jsdom",
|
||||||
|
"eject": "react-scripts eject"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@playlyfe/gql": "^2.6.0"
|
||||||
|
},
|
||||||
|
"proxy": {
|
||||||
|
"/graphql": {
|
||||||
|
"target": "http://127.0.0.1:17000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 62 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1,40 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
<meta name="theme-color" content="#000000">
|
||||||
|
<!--
|
||||||
|
manifest.json provides metadata used when your web app is added to the
|
||||||
|
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
|
||||||
|
-->
|
||||||
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
|
||||||
|
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
||||||
|
<!--
|
||||||
|
Notice the use of %PUBLIC_URL% in the tags above.
|
||||||
|
It will be replaced with the URL of the `public` folder during the build.
|
||||||
|
Only files inside the `public` folder can be referenced from the HTML.
|
||||||
|
|
||||||
|
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||||
|
work correctly both with client-side routing and a non-root public URL.
|
||||||
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
|
-->
|
||||||
|
<title>Aite RP</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>
|
||||||
|
You need to enable JavaScript to run this app.
|
||||||
|
</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
<!--
|
||||||
|
This HTML file is a template.
|
||||||
|
If you open it directly in the browser, you will see an empty page.
|
||||||
|
|
||||||
|
You can add webfonts, meta tags, or analytics to this file.
|
||||||
|
The build step will place the bundled scripts into the <body> tag.
|
||||||
|
|
||||||
|
To begin the development, run `npm start` or `yarn start`.
|
||||||
|
To create a production bundle, use `npm run build` or `yarn build`.
|
||||||
|
-->
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"short_name": "Aite RP",
|
||||||
|
"name": "Aite RP Website (aiterp.net)",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "favicon.ico",
|
||||||
|
"sizes": "64x64 32x32 24x24 16x16",
|
||||||
|
"type": "image/x-icon"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": "./index.html",
|
||||||
|
"display": "standalone",
|
||||||
|
"theme_color": "#000000",
|
||||||
|
"background_color": "#000000"
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import React, { Component } from 'react'
|
||||||
|
import { Route, Router, Redirect, Switch } from "react-router-dom";
|
||||||
|
|
||||||
|
import StoryRoot from './routes/story/StoryRoot';
|
||||||
|
import LogsIndex from './routes/logs/LogsIndex';
|
||||||
|
|
||||||
|
class App extends Component {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="App">
|
||||||
|
<Switch>
|
||||||
|
<Redirect exact path="/" to="/story/" />
|
||||||
|
<Route path="/story/" component={StoryRoot}/>
|
||||||
|
<Route path="/logs/" component={LogsIndex}/>
|
||||||
|
</Switch>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
|
|
||||||
|
/* See ./common/Menu.js to change the top navbar */
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { ApolloClient } from "apollo-client"
|
||||||
|
import { HttpLink } from "apollo-link-http"
|
||||||
|
import { InMemoryCache } from "apollo-cache-inmemory"
|
||||||
|
|
||||||
|
const client = new ApolloClient({
|
||||||
|
link: new HttpLink({
|
||||||
|
uri: "/graphql"
|
||||||
|
}),
|
||||||
|
|
||||||
|
cache: new InMemoryCache(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export default client
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
/* /story/ */
|
||||||
|
.theme-story .color-menu {
|
||||||
|
color: #789;
|
||||||
|
}
|
||||||
|
.theme-story .MenuLink.selected {
|
||||||
|
background-color: rgba(34, 187, 255, 0.125);
|
||||||
|
}
|
||||||
|
.theme-story .MenuLink:hover, .theme-story .Menu > .head-menu > a:hover {
|
||||||
|
background-color: rgba(34, 187, 255, 0.25);
|
||||||
|
}
|
||||||
|
.theme-story .color-primary {
|
||||||
|
color: #1CF;
|
||||||
|
}
|
||||||
|
.theme-story .color-text {
|
||||||
|
color: #ABC;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* /logs/ */
|
||||||
|
.theme-logs .color-menu {
|
||||||
|
color: #998;
|
||||||
|
}
|
||||||
|
.theme-logs .MenuLink.selected {
|
||||||
|
background-color: rgba(255, 187, 34, 0.125);
|
||||||
|
}
|
||||||
|
.theme-logs .MenuLink:hover, .theme-logs .Menu > .head-menu > a:hover {
|
||||||
|
background-color: rgba(255, 187, 34, 0.25);
|
||||||
|
}
|
||||||
|
.theme-logs .color-primary {
|
||||||
|
color: #FC1;
|
||||||
|
}
|
||||||
|
.theme-logs .color-text {
|
||||||
|
color: #CCB;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* /data/ */
|
||||||
|
.theme-data .color-menu {
|
||||||
|
color: #898;
|
||||||
|
}
|
||||||
|
.theme-data .MenuLink.selected {
|
||||||
|
background-color: rgba(35, 255, 128, 0.125);
|
||||||
|
}
|
||||||
|
.theme-data .MenuLink:hover, .theme-data .Menu > .head-menu > a:hover {
|
||||||
|
background-color: rgba(35, 255, 128, 0.25);
|
||||||
|
}
|
||||||
|
.theme-data .color-primary {
|
||||||
|
color: #1F8;
|
||||||
|
}
|
||||||
|
.theme-data .color-text {
|
||||||
|
color: #ACB;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tag colors */
|
||||||
|
.color-tag-event {
|
||||||
|
color: #B77;
|
||||||
|
}
|
||||||
|
.color-tag-location {
|
||||||
|
color: #7B7;
|
||||||
|
}
|
||||||
|
.color-tag-organization {
|
||||||
|
color: #B7B;
|
||||||
|
}
|
||||||
|
.color-tag-series {
|
||||||
|
color: #BB7;
|
||||||
|
}
|
||||||
|
.color-tag-character {
|
||||||
|
color: #37B;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Danger */
|
||||||
|
.color-danger {
|
||||||
|
color: #FA3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MenuLink:hover, .MenuLink:hover .text, .Menu > .head-menu > a:hover {
|
||||||
|
color: #EEE !important;
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import './css/Background.css';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Background renders a full-size image that detects whether the source is
|
||||||
|
* landscape or portrait. It's a react-ification of AiteStory's background.
|
||||||
|
*/
|
||||||
|
export default class Background extends Component {
|
||||||
|
/**
|
||||||
|
* @param {HTMLImageElement} img
|
||||||
|
*/
|
||||||
|
onRef(img) {
|
||||||
|
if (img == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (img.complete && typeof(img.naturalHeight) !== 'undefined' && img.naturalHeight !== 0) {
|
||||||
|
const width = img.clientWidth
|
||||||
|
const height = img.clientHeight
|
||||||
|
|
||||||
|
if(width > (height * 1.50)) {
|
||||||
|
img.className += ' landscape'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
img.onload = function () {
|
||||||
|
const width = img.clientWidth
|
||||||
|
const height = img.clientHeight
|
||||||
|
|
||||||
|
if(width > (height * 1.50)) {
|
||||||
|
img.className += ' landscape'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const src = this.props.src || "/assets/images/bg.png"
|
||||||
|
const opacity = this.props.opacity || 0.25
|
||||||
|
|
||||||
|
return <img style={{filter: `brightness(${(Number(opacity)) * 100}%)`}} ref={this.onRef.bind(this)} className="Background" src={src} />
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
|
import './css/Menu.css';
|
||||||
|
|
||||||
|
export default class Menu extends Component {
|
||||||
|
render() {
|
||||||
|
const { children } = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<nav className="Menu">
|
||||||
|
<h1 className="color-primary">Aite RP</h1>
|
||||||
|
<div className="head-menu">
|
||||||
|
<HeadMenuLink to="/story/">Story</HeadMenuLink>
|
||||||
|
<HeadMenuLink to="/logs/">Logs</HeadMenuLink>
|
||||||
|
</div>
|
||||||
|
<div className="content">
|
||||||
|
{ children }
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MenuLink({ icon, children, tagType, to, onClick }) {
|
||||||
|
const isSelected = window.location.pathname === to
|
||||||
|
const className = isSelected ? "selected" : "not-selected"
|
||||||
|
const textClass = isSelected ? "color-primary" : `color-tag-${tagType||"none"}`
|
||||||
|
|
||||||
|
const body = (
|
||||||
|
<div className={`MenuLink color-menu ${className}`} onClick={onClick}>
|
||||||
|
<div className="icon color-primary">{icon}</div>
|
||||||
|
<div className={`text ${textClass}`}>{children}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
if (to == null) {
|
||||||
|
return body
|
||||||
|
} else {
|
||||||
|
return <Link to={to}>{body}</Link>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MenuGap() {
|
||||||
|
return <div className="MenuGap"></div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MenuHeader({children}) {
|
||||||
|
return <div className="MenuHeader color-menu">{children}</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function HeadMenuLink({to, children}) {
|
||||||
|
const className = (window.location.pathname.startsWith(to) ? "color-primary" : "color-menu")
|
||||||
|
|
||||||
|
return <Link className={className} to={to}>{children}</Link>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MenuBlurb({children}) {
|
||||||
|
return <div className="MenuBlurb">{children}</div>
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
img.Background {
|
||||||
|
/* Set rules to fill background */
|
||||||
|
width: 100%;
|
||||||
|
/* Set up proportionate scaling */
|
||||||
|
min-height: 100%;
|
||||||
|
height: auto;
|
||||||
|
/* Set up positioning */
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: -1;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-drag: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
img.Background.landscape {
|
||||||
|
/* Set rules to fill background */
|
||||||
|
height: 100%;
|
||||||
|
/* Set up proportionate scaling */
|
||||||
|
min-width: 100%;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
.Menu {
|
||||||
|
position: sticky;
|
||||||
|
width: 28ch;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Menu > .head-menu {
|
||||||
|
padding: 0 2ch;
|
||||||
|
padding-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Menu > .head-menu > a {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.25em 1ch;
|
||||||
|
|
||||||
|
font-size: 0.75em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Menu > h1 {
|
||||||
|
padding: 1em 0 0em 0;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 2.5em;
|
||||||
|
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Menu > .content {
|
||||||
|
margin-top: 1em;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MenuLink {
|
||||||
|
width: 100%;
|
||||||
|
padding-left: 0.5ch;
|
||||||
|
font-size: 1.1em;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MenuLink .icon {
|
||||||
|
display: inline-block;
|
||||||
|
width: 3ch;
|
||||||
|
padding: 0.15em 0.125em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MenuLink .text {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MenuGap {
|
||||||
|
padding: 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MenuHeader {
|
||||||
|
opacity: 0.5;
|
||||||
|
user-select: none;
|
||||||
|
padding: 0.125em 1.5ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.MenuBlurb {
|
||||||
|
opacity: 0.5;
|
||||||
|
padding: 0.25em 1.5ch;
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-family: sans-serif;
|
||||||
|
background-color: black;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import React from "react";
|
||||||
|
import ReactDOM from "react-dom";
|
||||||
|
import { BrowserRouter } from "react-router-dom"
|
||||||
|
import { ApolloProvider } from "react-apollo";
|
||||||
|
|
||||||
|
import client from "./client";
|
||||||
|
import App from "./App"
|
||||||
|
import registerServiceWorker from "./registerServiceWorker"
|
||||||
|
|
||||||
|
import "./index.css"
|
||||||
|
import "./colors.css";
|
||||||
|
|
||||||
|
ReactDOM.render((
|
||||||
|
<ApolloProvider client={client}>
|
||||||
|
<BrowserRouter>
|
||||||
|
<App />
|
||||||
|
</BrowserRouter>
|
||||||
|
</ApolloProvider>
|
||||||
|
), document.getElementById('root'))
|
||||||
|
|
||||||
|
registerServiceWorker();
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
// In production, we register a service worker to serve assets from local cache.
|
||||||
|
|
||||||
|
// This lets the app load faster on subsequent visits in production, and gives
|
||||||
|
// it offline capabilities. However, it also means that developers (and users)
|
||||||
|
// will only see deployed updates on the "N+1" visit to a page, since previously
|
||||||
|
// cached resources are updated in the background.
|
||||||
|
|
||||||
|
// To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
|
||||||
|
// This link also includes instructions on opting out of this behavior.
|
||||||
|
|
||||||
|
const isLocalhost = Boolean(
|
||||||
|
window.location.hostname === 'localhost' ||
|
||||||
|
// [::1] is the IPv6 localhost address.
|
||||||
|
window.location.hostname === '[::1]' ||
|
||||||
|
// 127.0.0.1/8 is considered localhost for IPv4.
|
||||||
|
window.location.hostname.match(
|
||||||
|
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
export default function register() {
|
||||||
|
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
|
||||||
|
// The URL constructor is available in all browsers that support SW.
|
||||||
|
const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
|
||||||
|
if (publicUrl.origin !== window.location.origin) {
|
||||||
|
// Our service worker won't work if PUBLIC_URL is on a different origin
|
||||||
|
// from what our page is served on. This might happen if a CDN is used to
|
||||||
|
// serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
|
||||||
|
|
||||||
|
if (isLocalhost) {
|
||||||
|
// This is running on localhost. Lets check if a service worker still exists or not.
|
||||||
|
checkValidServiceWorker(swUrl);
|
||||||
|
|
||||||
|
// Add some additional logging to localhost, pointing developers to the
|
||||||
|
// service worker/PWA documentation.
|
||||||
|
navigator.serviceWorker.ready.then(() => {
|
||||||
|
console.log(
|
||||||
|
'This web app is being served cache-first by a service ' +
|
||||||
|
'worker. To learn more, visit https://goo.gl/SC7cgQ'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Is not local host. Just register service worker
|
||||||
|
registerValidSW(swUrl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerValidSW(swUrl) {
|
||||||
|
navigator.serviceWorker
|
||||||
|
.register(swUrl)
|
||||||
|
.then(registration => {
|
||||||
|
registration.onupdatefound = () => {
|
||||||
|
const installingWorker = registration.installing;
|
||||||
|
installingWorker.onstatechange = () => {
|
||||||
|
if (installingWorker.state === 'installed') {
|
||||||
|
if (navigator.serviceWorker.controller) {
|
||||||
|
// At this point, the old content will have been purged and
|
||||||
|
// the fresh content will have been added to the cache.
|
||||||
|
// It's the perfect time to display a "New content is
|
||||||
|
// available; please refresh." message in your web app.
|
||||||
|
console.log('New content is available; please refresh.');
|
||||||
|
} else {
|
||||||
|
// At this point, everything has been precached.
|
||||||
|
// It's the perfect time to display a
|
||||||
|
// "Content is cached for offline use." message.
|
||||||
|
console.log('Content is cached for offline use.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error during service worker registration:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkValidServiceWorker(swUrl) {
|
||||||
|
// Check if the service worker can be found. If it can't reload the page.
|
||||||
|
fetch(swUrl)
|
||||||
|
.then(response => {
|
||||||
|
// Ensure service worker exists, and that we really are getting a JS file.
|
||||||
|
if (
|
||||||
|
response.status === 404 ||
|
||||||
|
response.headers.get('content-type').indexOf('javascript') === -1
|
||||||
|
) {
|
||||||
|
// No service worker found. Probably a different app. Reload the page.
|
||||||
|
navigator.serviceWorker.ready.then(registration => {
|
||||||
|
registration.unregister().then(() => {
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Service worker found. Proceed as normal.
|
||||||
|
registerValidSW(swUrl);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
console.log(
|
||||||
|
'No internet connection found. App is running in offline mode.'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unregister() {
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
navigator.serviceWorker.ready.then(registration => {
|
||||||
|
registration.unregister();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import Background from '../../common/Background';
|
||||||
|
import LogsMenu from './LogsMenu';
|
||||||
|
|
||||||
|
export default class LogsIndex extends Component {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="StoryIndex theme-logs">
|
||||||
|
<Background src="https://wiki.aiterp.net/images/c/ce/Ehanis_Tioran_-_Portrait_-_Resized.png" />
|
||||||
|
<LogsMenu />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import Menu, { MenuHeader, MenuLink, MenuGap } from '../../common/Menu';
|
||||||
|
|
||||||
|
export default class LogsMenu extends Component {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Menu>
|
||||||
|
<MenuHeader>Logs</MenuHeader>
|
||||||
|
<MenuLink to="/logs/" icon="R">Recent</MenuLink>
|
||||||
|
<MenuGap />
|
||||||
|
<MenuHeader>Options</MenuHeader>
|
||||||
|
<MenuLink icon="+">Create Log</MenuLink>
|
||||||
|
<MenuLink icon="I">Import Log</MenuLink>
|
||||||
|
</Menu>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import React, { Component } from "react";
|
||||||
|
import Menu, { MenuHeader, MenuLink, MenuGap } from "../../common/Menu";
|
||||||
|
import pluralize from "pluralize"
|
||||||
|
|
||||||
|
export default class StoryMenu extends Component {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Menu>
|
||||||
|
<MenuHeader>Story</MenuHeader>
|
||||||
|
<MenuLink to="/story/" icon="A">All</MenuLink>
|
||||||
|
<MenuGap />
|
||||||
|
<MenuHeader>By Category</MenuHeader>
|
||||||
|
<CategoryLinks categories={this.props.categories} />
|
||||||
|
<MenuGap />
|
||||||
|
<MenuHeader>By Tag</MenuHeader>
|
||||||
|
<MenuLink to="/story/tag/Location/Miner's%20Respite" icon="L" tagType="location">Miner's Respite</MenuLink>
|
||||||
|
<MenuLink to="/story/tag/Chracter/Ilyna%20T'Rea" icon="C" tagType="character">Ilyna T'Rea</MenuLink>
|
||||||
|
<MenuLink to="/story/tag/Organization/Redrock%20Agency" icon="O" tagType="organization">Redrock Agency</MenuLink>
|
||||||
|
<MenuLink to="/story/tag/Series/Yellow%20Haze" icon="S" tagType="series">Yellow Haze</MenuLink>
|
||||||
|
<MenuLink to="/story/tag/Event/Surveying%20The%20Ice%20World" icon="E" tagType="event">Surveying the Ice World</MenuLink>
|
||||||
|
<MenuLink to="/story/tag/" icon="T">All Tags</MenuLink>
|
||||||
|
<MenuGap />
|
||||||
|
<MenuHeader>Your Content</MenuHeader>
|
||||||
|
<MenuLink to="/story/authored/" icon="A">Listed</MenuLink>
|
||||||
|
<MenuLink to="/story/open/" icon="O">Open</MenuLink>
|
||||||
|
<MenuLink to="/story/unlisted/" icon="U">Unlisted</MenuLink>
|
||||||
|
<MenuGap />
|
||||||
|
<MenuHeader>Options</MenuHeader>
|
||||||
|
<MenuLink icon="+">Create Story</MenuLink>
|
||||||
|
</Menu>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function CategoryLinks({categories}) {
|
||||||
|
return categories.map(category => {
|
||||||
|
const icon = category.charAt(0).toUpperCase()
|
||||||
|
const label = pluralize(category)
|
||||||
|
|
||||||
|
return <MenuLink to={`/story/category/${category}`} icon={icon}>{label}</MenuLink>
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import { Query } from 'react-apollo';
|
||||||
|
|
||||||
|
import Background from '../../common/Background';
|
||||||
|
import StoryMenu from './StoryMenu';
|
||||||
|
|
||||||
|
import INDEX_QUERY from "./gql/StoryIndex";
|
||||||
|
|
||||||
|
export class StoryIndex extends Component {
|
||||||
|
render() {
|
||||||
|
const categories = this.props.categoryType.enumValues.map(ev => ev.name).sort()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="StoryIndex theme-story">
|
||||||
|
<Background />
|
||||||
|
<StoryMenu categories={categories} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default () => (
|
||||||
|
<Query query={INDEX_QUERY}>
|
||||||
|
{({ loading, error, data }) => {
|
||||||
|
if (loading) return 'Loading...';
|
||||||
|
if (error) return `Error! ${error.message}`;
|
||||||
|
|
||||||
|
return <StoryIndex {...data} />
|
||||||
|
}}
|
||||||
|
</Query>
|
||||||
|
);
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import gql from "graphql-tag"
|
||||||
|
|
||||||
|
export default gql`
|
||||||
|
query StoryIndex {
|
||||||
|
categoryType: __type(name: "StoryCategory") {
|
||||||
|
enumValues {
|
||||||
|
name
|
||||||
|
description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stories(input: {}) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
author
|
||||||
|
open
|
||||||
|
tags {
|
||||||
|
kind
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
+42
-1
@@ -36,6 +36,21 @@ type Query {
|
|||||||
files(includePublic: Boolean, mimeTypes: [String!]): [File!]!
|
files(includePublic: Boolean, mimeTypes: [String!]): [File!]!
|
||||||
|
|
||||||
|
|
||||||
|
# Find story by ID
|
||||||
|
story(id: String!): Story
|
||||||
|
|
||||||
|
# Find stories
|
||||||
|
stories(input: StoriesQueryInput!): [Story!]!
|
||||||
|
|
||||||
|
|
||||||
|
# Find story chapter by ID
|
||||||
|
chapter(id: String!): Chapter
|
||||||
|
|
||||||
|
|
||||||
|
# Find all distinct tags used in stories
|
||||||
|
tags: [Tag!]!
|
||||||
|
|
||||||
|
|
||||||
# Find current session
|
# Find current session
|
||||||
session: Session!
|
session: Session!
|
||||||
}
|
}
|
||||||
@@ -95,7 +110,33 @@ type Mutation {
|
|||||||
editFile(input: EditFileInput!): File!
|
editFile(input: EditFileInput!): File!
|
||||||
|
|
||||||
# Remove a file
|
# Remove a file
|
||||||
removeFile(id: String!): File
|
removeFile(id: String!): File!
|
||||||
|
|
||||||
|
|
||||||
|
# Add a story
|
||||||
|
addStory(input: StoryAddInput!): Story!
|
||||||
|
|
||||||
|
# Add a story tag
|
||||||
|
addStoryTag(input: StoryTagAddInput!): Story!
|
||||||
|
|
||||||
|
# Remove a story tag
|
||||||
|
removeStoryTag(input: StoryTagRemoveInput!): Story!
|
||||||
|
|
||||||
|
# Edit a story
|
||||||
|
editStory(input: StoryEditInput!): Story!
|
||||||
|
|
||||||
|
# Remove a story
|
||||||
|
removeStory(id: String!): Story!
|
||||||
|
|
||||||
|
|
||||||
|
# Add a chapter to a story
|
||||||
|
addChapter(input: ChapterAddInput!): Chapter!
|
||||||
|
|
||||||
|
# Edit a chapter
|
||||||
|
editChapter(input: ChapterEditInput!): Chapter!
|
||||||
|
|
||||||
|
# Remove a chapter
|
||||||
|
removeChapter(id: String!): Chapter!
|
||||||
|
|
||||||
|
|
||||||
# Log in
|
# Log in
|
||||||
|
|||||||
@@ -21,3 +21,39 @@ type Chapter {
|
|||||||
# The date of edit.
|
# The date of edit.
|
||||||
editedDate: String!
|
editedDate: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Input for addChapter mutation
|
||||||
|
input ChapterAddInput {
|
||||||
|
# The story to add a chapter to
|
||||||
|
storyId: String!
|
||||||
|
|
||||||
|
# The title to give the chapter. It can not be left empty
|
||||||
|
title: String!
|
||||||
|
|
||||||
|
# The author to set for the chapter. It will use the logged in user ID, and only
|
||||||
|
# users with appropriate permissions can post as another author. Even then, it's only
|
||||||
|
# for importing/moving content. This author name will not be used when logging the
|
||||||
|
# submission
|
||||||
|
author: String
|
||||||
|
|
||||||
|
# The markdown source for the content
|
||||||
|
source: String!
|
||||||
|
|
||||||
|
# Optionally, assign a fictional date for the chapter
|
||||||
|
fictionalDate: String
|
||||||
|
}
|
||||||
|
|
||||||
|
# Input for editChapter mutation
|
||||||
|
input ChapterEditInput {
|
||||||
|
# The chapter to edit
|
||||||
|
id: String!
|
||||||
|
|
||||||
|
# Change the chapter's title
|
||||||
|
title: String
|
||||||
|
|
||||||
|
# Change the source
|
||||||
|
source: String
|
||||||
|
|
||||||
|
# Set the fictional date for a chapter
|
||||||
|
fictionalDate: String
|
||||||
|
}
|
||||||
@@ -20,7 +20,10 @@ type Story {
|
|||||||
category: StoryCategory!
|
category: StoryCategory!
|
||||||
|
|
||||||
# The tags for this tory
|
# The tags for this tory
|
||||||
tags: [Tags!]!
|
tags: [Tag!]!
|
||||||
|
|
||||||
|
# The chapters of this story
|
||||||
|
chapters: [Chapter!]!
|
||||||
|
|
||||||
# The date the story was created (RFC3339 format).
|
# The date the story was created (RFC3339 format).
|
||||||
createdDate: String!
|
createdDate: String!
|
||||||
@@ -29,7 +32,94 @@ type Story {
|
|||||||
fictionalDate: String!
|
fictionalDate: String!
|
||||||
|
|
||||||
# The date of the last major update to the story (RFC3339 format). This being bumped means a new chapter has been added.
|
# The date of the last major update to the story (RFC3339 format). This being bumped means a new chapter has been added.
|
||||||
fictionalDate: String!
|
updatedDate: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
input StoriesQueryInput {
|
||||||
|
# What author to query for
|
||||||
|
author: String
|
||||||
|
|
||||||
|
# What tags to query for
|
||||||
|
tags: [TagInput!]
|
||||||
|
|
||||||
|
# The earliest fictionalDate
|
||||||
|
earliestFictionalDate: String
|
||||||
|
|
||||||
|
# The latest fictionalDate
|
||||||
|
latestFictionalDate: String
|
||||||
|
|
||||||
|
# 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 (RFC3339 format).
|
||||||
|
fictionalDate: String
|
||||||
|
}
|
||||||
|
|
||||||
|
# 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 (RFC3339 format).
|
||||||
|
fictionalDate: String
|
||||||
|
}
|
||||||
|
|
||||||
|
# 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
|
# Possible values for Story.category
|
||||||
|
|||||||
@@ -7,6 +7,15 @@ type Tag {
|
|||||||
name: String!
|
name: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# A Tag is a means of associating stories that have details in common with one another.
|
||||||
|
input TagInput {
|
||||||
|
# The tag's kind
|
||||||
|
kind: TagKind!
|
||||||
|
|
||||||
|
# The tag's name
|
||||||
|
name: String!
|
||||||
|
}
|
||||||
|
|
||||||
# Allowed values for Tag.kind
|
# Allowed values for Tag.kind
|
||||||
enum TagKind {
|
enum TagKind {
|
||||||
# An organization is a catch all term for in-universe corporations, teams, groups, cults, forces, etc...
|
# An organization is a catch all term for in-universe corporations, teams, groups, cults, forces, etc...
|
||||||
|
|||||||
Reference in New Issue
Block a user