Compare commits
55
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a5410dff2c | ||
|
|
7e3dc49635 | ||
|
|
aff0d99842 | ||
|
|
00150118d2 | ||
|
|
332a1eb32b | ||
|
|
db4d46c368 | ||
|
|
a8c8610ca2 | ||
|
|
d09c17d30d | ||
|
|
e3e3ea07db | ||
|
|
22592439da | ||
|
|
dfa9fb4025 | ||
|
|
0e2a68a2d4 | ||
|
|
1eefa65527 | ||
|
|
f9b6af58c9 | ||
|
|
56ecf48b57 | ||
|
|
06fbfc90c5 | ||
|
|
39a4fef506 | ||
|
|
f9fe0bb5f8 | ||
|
|
d22ab387dd | ||
|
|
53a86c0b9c | ||
|
|
e0b93c6b4b | ||
|
|
1ea04f1577 | ||
|
|
17fd3c60f8 | ||
|
|
c5b62a42db | ||
|
|
fa0926c311 | ||
|
|
d21efc1c7f | ||
|
|
852030b141 | ||
|
|
f7f0b76379 | ||
|
|
2711654816 | ||
|
|
b60897a597 | ||
|
|
80e6c048c9 | ||
|
|
6310cbbc1e | ||
|
|
89a1c2366c | ||
|
|
ac2b79e88a | ||
|
|
31239330df | ||
|
|
a58535fcb9 | ||
|
|
2a91b6f9bc | ||
|
|
caa7727c02 | ||
|
|
047c9784ae | ||
|
|
d038c1cfea | ||
|
|
7e812bd615 | ||
|
|
947765884e | ||
|
|
ffec008820 | ||
|
|
138b3e3eab | ||
|
|
929abfdbe4 | ||
|
|
a8cd595876 | ||
|
|
9ed3b32b42 | ||
|
|
6bae52d652 | ||
|
|
cee370c2ac | ||
|
|
9affdbd20e | ||
|
|
cf545cde3b | ||
|
|
b414cf118f | ||
|
|
4979b02104 | ||
|
|
b7d5059d60 | ||
|
|
9fbdc767ad |
+12
-17
@@ -1,34 +1,29 @@
|
||||
## 1. Build Server
|
||||
# Use Go 1.10
|
||||
FROM golang:1.11 AS build-server
|
||||
# Use Go 1.12
|
||||
FROM golang:1.12 AS build-server
|
||||
|
||||
# Load repository into docker
|
||||
WORKDIR /go/src/git.aiterp.net/rpdata/api/
|
||||
WORKDIR /project/
|
||||
COPY . .
|
||||
RUN rm -rf ./vendor
|
||||
|
||||
# Load build tools and dependencies
|
||||
RUN go get -u github.com/golang/dep/cmd/dep
|
||||
RUN go get -u github.com/vektah/gorunpkg
|
||||
RUN go get -u github.com/jteeuwen/go-bindata/...
|
||||
RUN dep ensure
|
||||
# Install build tools and dependencies
|
||||
RUN go generate ./...
|
||||
|
||||
# Build server and tools (CGO disabled to make them 100% static)
|
||||
ENV CGO_ENABLED 0
|
||||
RUN go build -a -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-server ./cmd/rpdata-server
|
||||
RUN go build -a -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-lb2charimport ./cmd/rpdata-lb2charimport
|
||||
RUN go build -a -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-lb2logimport ./cmd/rpdata-lb2logimport
|
||||
# TODO: RUN go build -a -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-wikifileimport ./cmd/rpdata-wikifileimport
|
||||
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 -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-server ./cmd/rpdata-server
|
||||
RUN go build -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-lb2charimport ./cmd/rpdata-lb2charimport
|
||||
RUN go build -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-lb2logimport ./cmd/rpdata-lb2logimport
|
||||
# TODO: RUN go build -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-wikifileimport ./cmd/rpdata-wikifileimport
|
||||
RUN go build -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-ensurechannels ./cmd/rpdata-ensurechannels
|
||||
RUN go build -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-as2storyimport ./cmd/rpdata-as2storyimport
|
||||
|
||||
## 2. Distribute
|
||||
# Use alpine linux
|
||||
FROM alpine:3.7
|
||||
|
||||
# Install certificates to enable HTTPS GET requests
|
||||
RUN apk add --no-cache ca-certificates
|
||||
# Install certificates to enable HTTPS GET requests, and tzdata for log imports.
|
||||
RUN apk add --no-cache ca-certificates tzdata
|
||||
|
||||
# Copy build files into final container
|
||||
COPY --from=build-server /binaries/* /usr/local/bin/
|
||||
|
||||
Generated
-254
@@ -1,254 +0,0 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/99designs/gqlgen"
|
||||
packages = [
|
||||
".",
|
||||
"cmd",
|
||||
"codegen",
|
||||
"codegen/templates",
|
||||
"complexity",
|
||||
"graphql",
|
||||
"graphql/introspection",
|
||||
"handler",
|
||||
"internal/gopath"
|
||||
]
|
||||
revision = "636435b68700211441303f1a5ed92f3768ba5774"
|
||||
version = "v0.5.1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/agnivade/levenshtein"
|
||||
packages = ["."]
|
||||
revision = "3d21ba515fe27b856f230847e856431ae1724adc"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/dgrijalva/jwt-go"
|
||||
packages = ["."]
|
||||
revision = "06ea1031745cb8b3dab3f6a236daf2b0aa468b7e"
|
||||
version = "v3.2.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/dustin/go-humanize"
|
||||
packages = ["."]
|
||||
revision = "bb3d318650d48840a39aa21a027c6630e198e626"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/globalsign/mgo"
|
||||
packages = [
|
||||
".",
|
||||
"bson",
|
||||
"internal/json",
|
||||
"internal/sasl",
|
||||
"internal/scram"
|
||||
]
|
||||
revision = "f76e4f9da92ecd56e3be26f5ba92580af1ef97b4"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/go-ini/ini"
|
||||
packages = ["."]
|
||||
revision = "ace140f73450505f33e8b8418216792275ae82a7"
|
||||
version = "v1.35.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/go-sql-driver/mysql"
|
||||
packages = ["."]
|
||||
revision = "d523deb1b23d913de5bdada721a6071e71283618"
|
||||
version = "v1.4.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/gorilla/websocket"
|
||||
packages = ["."]
|
||||
revision = "66b9c49e59c6c48f0ffce28c2d8b8a5678502c6d"
|
||||
version = "v1.4.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/graph-gophers/dataloader"
|
||||
packages = ["."]
|
||||
revision = "78139374585c29dcb97b8f33089ed11959e4be59"
|
||||
version = "v5"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/graph-gophers/graphql-go"
|
||||
packages = [
|
||||
".",
|
||||
"errors",
|
||||
"internal/common",
|
||||
"internal/exec",
|
||||
"internal/exec/packer",
|
||||
"internal/exec/resolvable",
|
||||
"internal/exec/selected",
|
||||
"internal/query",
|
||||
"internal/schema",
|
||||
"internal/validation",
|
||||
"introspection",
|
||||
"log",
|
||||
"relay",
|
||||
"trace"
|
||||
]
|
||||
revision = "9ebf33af539ab8cb832c7107bc0a978ca8dbc0de"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/hashicorp/golang-lru"
|
||||
packages = [
|
||||
".",
|
||||
"simplelru"
|
||||
]
|
||||
revision = "20f1fb78b0740ba8c3cb143a61e86ba5c8669768"
|
||||
version = "v0.5.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/jmoiron/sqlx"
|
||||
packages = [
|
||||
".",
|
||||
"reflectx"
|
||||
]
|
||||
revision = "0dae4fefe7c0e190f7b5a78dac28a1c82cc8d849"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/minio/minio-go"
|
||||
packages = [
|
||||
".",
|
||||
"pkg/credentials",
|
||||
"pkg/encrypt",
|
||||
"pkg/s3signer",
|
||||
"pkg/s3utils",
|
||||
"pkg/set"
|
||||
]
|
||||
revision = "3d2d02921f0510e9d1f66ef77a265b8dddd36992"
|
||||
version = "6.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/mitchellh/go-homedir"
|
||||
packages = ["."]
|
||||
revision = "b8bc1bf767474819792c23f32d8286a45736f1c6"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/opentracing/opentracing-go"
|
||||
packages = [
|
||||
".",
|
||||
"ext",
|
||||
"log"
|
||||
]
|
||||
revision = "1949ddbfd147afd4d964a9f00b24eb291e0e7c38"
|
||||
version = "v1.0.2"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/pkg/errors"
|
||||
packages = ["."]
|
||||
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
|
||||
version = "v0.8.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/sirupsen/logrus"
|
||||
packages = ["."]
|
||||
revision = "c155da19408a8799da419ed3eeb0cb5db0ad5dbc"
|
||||
version = "v1.0.5"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/urfave/cli"
|
||||
packages = ["."]
|
||||
revision = "cfb38830724cc34fedffe9a2a29fb54fa9169cd1"
|
||||
version = "v1.20.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/vektah/gqlparser"
|
||||
packages = [
|
||||
".",
|
||||
"ast",
|
||||
"gqlerror",
|
||||
"lexer",
|
||||
"parser",
|
||||
"validator",
|
||||
"validator/rules"
|
||||
]
|
||||
revision = "14e83ae06ec152e6d0afb9766a00e0c0918aa8fc"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = [
|
||||
"argon2",
|
||||
"blake2b",
|
||||
"ssh/terminal"
|
||||
]
|
||||
revision = "d6449816ce06963d9d136eee5a56fca5b0616e7e"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/net"
|
||||
packages = [
|
||||
"context",
|
||||
"idna",
|
||||
"lex/httplex"
|
||||
]
|
||||
revision = "a35a21de978d84ffc92f010a153705b170b2f9d1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/sys"
|
||||
packages = [
|
||||
"unix",
|
||||
"windows"
|
||||
]
|
||||
revision = "2f57af4873d00d535c5c9028850aa2152e6a5566"
|
||||
|
||||
[[projects]]
|
||||
name = "golang.org/x/text"
|
||||
packages = [
|
||||
"collate",
|
||||
"collate/build",
|
||||
"internal/colltab",
|
||||
"internal/gen",
|
||||
"internal/tag",
|
||||
"internal/triegen",
|
||||
"internal/ucd",
|
||||
"language",
|
||||
"secure/bidirule",
|
||||
"transform",
|
||||
"unicode/bidi",
|
||||
"unicode/cldr",
|
||||
"unicode/norm",
|
||||
"unicode/rangetable"
|
||||
]
|
||||
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
|
||||
version = "v0.3.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/tools"
|
||||
packages = [
|
||||
"go/ast/astutil",
|
||||
"go/buildutil",
|
||||
"go/internal/cgo",
|
||||
"go/loader",
|
||||
"imports",
|
||||
"internal/fastwalk"
|
||||
]
|
||||
revision = "677d2ff680c188ddb7dcd2bfa6bc7d3f2f2f75b2"
|
||||
|
||||
[[projects]]
|
||||
name = "google.golang.org/appengine"
|
||||
packages = ["cloudsql"]
|
||||
revision = "b1f26356af11148e710935ed1ac8a7f5702c7612"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
name = "gopkg.in/yaml.v2"
|
||||
packages = ["."]
|
||||
revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
|
||||
version = "v2.2.1"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "3c9430afa8b9260026925c8f496af934e2e573a2bd4587bb634fd12a20ce6c2c"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
#
|
||||
# [prune]
|
||||
# non-go = false
|
||||
# go-tests = true
|
||||
# unused-packages = true
|
||||
|
||||
required = [ "github.com/99designs/gqlgen" ]
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/globalsign/mgo"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/minio/minio-go"
|
||||
version = "6.0.0"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/graph-gophers/graphql-go"
|
||||
@@ -104,7 +104,7 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
chapter, err := chapters.Add(story, title, result.Author[5:], result.Source, publishDate, &fictionalDate)
|
||||
chapter, err := chapters.Add(story, title, result.Author[5:], result.Source, publishDate, &fictionalDate, models.ChapterCommentModeDisabled)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
l, err = logs.UpdateCharacters(l)
|
||||
l, err = logs.UpdateCharacters(l, nil)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
@@ -8,12 +8,13 @@ import (
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models/logs"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graph2"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/internal/loader"
|
||||
"git.aiterp.net/rpdata/api/internal/store"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"git.aiterp.net/rpdata/api/models/changes"
|
||||
"git.aiterp.net/rpdata/api/models/logs"
|
||||
"github.com/99designs/gqlgen/handler"
|
||||
)
|
||||
|
||||
@@ -35,19 +36,51 @@ func main() {
|
||||
log.Println("Characters updated")
|
||||
}()
|
||||
|
||||
go logListedChanges()
|
||||
|
||||
log.Fatal(http.ListenAndServe(":8081", nil))
|
||||
}
|
||||
|
||||
func logListedChanges() {
|
||||
sub := changes.Subscribe(context.Background(), nil, true)
|
||||
|
||||
for change := range sub {
|
||||
log.Printf("Change: Author=%#+v Model=%#+v Op=%#+v", change.Author, change.Model, change.Op)
|
||||
for _, object := range change.Objects() {
|
||||
switch object := object.(type) {
|
||||
case models.Log:
|
||||
log.Printf("\tLog: %s (%s)", object.ID, object.ShortID)
|
||||
case models.Character:
|
||||
log.Printf("\tCharacter: %s", object.ID)
|
||||
case models.Channel:
|
||||
log.Printf("\tChannel: %s", object.Name)
|
||||
case models.Post:
|
||||
log.Printf("\tPost: %s", object.ID)
|
||||
case models.Story:
|
||||
log.Printf("\tStory: %s", object.ID)
|
||||
case models.Tag:
|
||||
log.Printf("\tTag: (%s,%s)", object.Kind, object.Name)
|
||||
case models.Chapter:
|
||||
log.Printf("\tChapter: %s", object.ID)
|
||||
case models.Comment:
|
||||
log.Printf("\tComment: %s", object.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("Change subscription closed.")
|
||||
}
|
||||
|
||||
func queryHandler() http.HandlerFunc {
|
||||
handler := handler.GraphQL(
|
||||
graph2.New(),
|
||||
handler.RecoverFunc(func(ctx context.Context, err interface{}) error {
|
||||
// send this panic somewhere
|
||||
log.Println(err)
|
||||
log.Println(string(debug.Stack()))
|
||||
|
||||
return fmt.Errorf("shit")
|
||||
return fmt.Errorf("The server failed to serve your request due to an internal error, it has been logged")
|
||||
}),
|
||||
handler.ComplexityLimit(300),
|
||||
)
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
"git.aiterp.net/rpdata/api/internal/config"
|
||||
"git.aiterp.net/rpdata/api/internal/store"
|
||||
"git.aiterp.net/rpdata/api/model/file"
|
||||
"git.aiterp.net/rpdata/api/models/files"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -33,7 +33,7 @@ func main() {
|
||||
store.Init()
|
||||
|
||||
for _, info := range data.Query.Allimages {
|
||||
existing, err := file.FindName("wiki", info.Name, info.User)
|
||||
existing, err := files.FindName("wiki", info.Name, info.User)
|
||||
if err == nil && existing.ID != "" {
|
||||
log.Println("Skipping", info.Name, "because it already exists")
|
||||
continue
|
||||
@@ -45,7 +45,7 @@ func main() {
|
||||
continue
|
||||
}
|
||||
|
||||
file, err := file.Insert(info.Name, "wiki", info.Mime, info.User, ts, info.Size, info.URL)
|
||||
file, err := files.Insert(info.Name, "wiki", info.Mime, info.User, ts, info.Size, info.URL)
|
||||
if err != nil {
|
||||
log.Println("Skipping", info.Name, "error:", err)
|
||||
continue
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
module git.aiterp.net/rpdata/api
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/99designs/gqlgen v0.8.0
|
||||
github.com/agnivade/levenshtein v1.0.1
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4
|
||||
github.com/globalsign/mgo v0.0.0-20180403085842-f76e4f9da92e
|
||||
github.com/go-ini/ini v1.35.0
|
||||
github.com/go-sql-driver/mysql v1.4.0
|
||||
github.com/gorilla/websocket v1.4.0
|
||||
github.com/graph-gophers/dataloader v0.0.0-20180104184831-78139374585c
|
||||
github.com/hashicorp/golang-lru v0.5.0
|
||||
github.com/jmoiron/sqlx v0.0.0-20180614180643-0dae4fefe7c0
|
||||
github.com/minio/minio-go v0.0.0-20180409193742-3d2d02921f05
|
||||
github.com/mitchellh/go-homedir v0.0.0-20161203194507-b8bc1bf76747
|
||||
github.com/opentracing/opentracing-go v1.0.2
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/pmezard/go-difflib v1.0.0
|
||||
github.com/sirupsen/logrus v1.0.5
|
||||
github.com/stretchr/testify v1.3.0
|
||||
github.com/urfave/cli v1.20.0
|
||||
github.com/vektah/gqlparser v1.1.0
|
||||
golang.org/x/crypto v0.0.0-20180411161317-d6449816ce06
|
||||
golang.org/x/net v0.0.0-20180416171110-a35a21de978d
|
||||
golang.org/x/sys v0.0.0-20180416112224-2f57af4873d0
|
||||
golang.org/x/text v0.3.0
|
||||
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6
|
||||
google.golang.org/appengine v1.1.0
|
||||
gopkg.in/yaml.v2 v2.2.2
|
||||
)
|
||||
@@ -0,0 +1,90 @@
|
||||
github.com/99designs/gqlgen v0.5.1 h1:cRsbpZgX83PrXb0/hj5EkNdmaVN4l/Eii81Z8LCexgY=
|
||||
github.com/99designs/gqlgen v0.5.1/go.mod h1:KSQDfLlTTGmzlRgLGm6HeKKKo598l5E2svEM6Nz2Jnw=
|
||||
github.com/99designs/gqlgen v0.8.0 h1:ZBteuSgeeFwn+mztjDqQZQdVa9pqslmPnDeCovcnc0Y=
|
||||
github.com/99designs/gqlgen v0.8.0/go.mod h1:st7qHA6ssU3uRZkmv+wzrzgX4srvIqEIdE5iuRW8GhE=
|
||||
github.com/99designs/gqlgen v0.8.3 h1:I6bMglXNKkn4KlvkSMzqZw53e1N2FF9Gud4NmsOxqiA=
|
||||
github.com/99designs/gqlgen v0.8.3/go.mod h1:aLyJw9xUgdJxZ8EqNQxo2pGFhXXJ/hq8t7J4yn8TgI4=
|
||||
github.com/agnivade/levenshtein v1.0.0 h1:q+77q31bLT5jhN3BHKA1276nUEdbz7XjDa0o5dRKcZ0=
|
||||
github.com/agnivade/levenshtein v1.0.0/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
|
||||
github.com/agnivade/levenshtein v1.0.1 h1:3oJU7J3FGFmyhn8KHjmVaZCN5hxTr7GxgRue+sxIXdQ=
|
||||
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/globalsign/mgo v0.0.0-20180403085842-f76e4f9da92e h1:hmrbHva/wKD6X4M7pgHVbg/KfVV1wwgr1NEkQaVD3rU=
|
||||
github.com/globalsign/mgo v0.0.0-20180403085842-f76e4f9da92e/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||
github.com/go-chi/chi v3.3.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-ini/ini v1.35.0 h1:D/my3+xOfqZMkJpciRcyqU7XMBUgiZa9qXjZIa8uv2k=
|
||||
github.com/go-ini/ini v1.35.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk=
|
||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/mux v1.6.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/graph-gophers/dataloader v0.0.0-20180104184831-78139374585c h1:94S+uoVVMpQAEOrqGjCDyUdML4dJDkh6aC4MYmXECg4=
|
||||
github.com/graph-gophers/dataloader v0.0.0-20180104184831-78139374585c/go.mod h1:jk4jk0c5ZISbKaMe8WsVopGB5/15GvGHMdMdPtwlRp4=
|
||||
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/jmoiron/sqlx v0.0.0-20180614180643-0dae4fefe7c0 h1:5B0uxl2lzNRVkJVg+uGHxWtRt4C0Wjc6kJKo5XYx8xE=
|
||||
github.com/jmoiron/sqlx v0.0.0-20180614180643-0dae4fefe7c0/go.mod h1:IiEW3SEiiErVyFdH8NTuWjSifiEQKUoyK3LNqr2kCHU=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/minio/minio-go v0.0.0-20180409193742-3d2d02921f05 h1:YEyFCqmHcmbcTLqZ7yEAg7VIUat6PuDzGbIctZy888k=
|
||||
github.com/minio/minio-go v0.0.0-20180409193742-3d2d02921f05/go.mod h1:7guKYtitv8dktvNUGrhzmNlA5wrAABTQXCoesZdFQO8=
|
||||
github.com/mitchellh/go-homedir v0.0.0-20161203194507-b8bc1bf76747 h1:eQox4Rh4ewJF+mqYPxCkmBAirRnPaHEB26UkNuPyjlk=
|
||||
github.com/mitchellh/go-homedir v0.0.0-20161203194507-b8bc1bf76747/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
|
||||
github.com/opentracing/opentracing-go v1.0.2 h1:3jA2P6O1F9UOrWVpwrIo17pu01KWvNWg4X946/Y5Zwg=
|
||||
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
||||
github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
|
||||
github.com/sirupsen/logrus v1.0.5/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/vektah/dataloaden v0.2.0/go.mod h1:vxM6NuRlgiR0M6wbVTJeKp9vQIs81ZMfCYO+4yq/jbE=
|
||||
github.com/vektah/gqlparser v0.0.0-20180831041411-14e83ae06ec1 h1:FYOXUtr3sYR9shto7Q/aQ1B0Onyk77aws9wGOORiz+I=
|
||||
github.com/vektah/gqlparser v0.0.0-20180831041411-14e83ae06ec1/go.mod h1:K4QdSSpS2XiHHwzb18kWh3iBljB8rLC8okGXsnQy3Nc=
|
||||
github.com/vektah/gqlparser v1.1.0 h1:3668p2gUlO+PiS81x957Rpr3/FPRWG6cxgCXAvTS1hw=
|
||||
github.com/vektah/gqlparser v1.1.0/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
|
||||
github.com/vektah/gqlparser v1.1.2 h1:ZsyLGn7/7jDNI+y4SEhI4yAxRChlv15pUHMjijT+e68=
|
||||
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
|
||||
golang.org/x/crypto v0.0.0-20180411161317-d6449816ce06 h1:EOqG0JqGlLr+punVB69jvWCv/ErZKGlC7PMdyHfv+Bc=
|
||||
golang.org/x/crypto v0.0.0-20180411161317-d6449816ce06/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/net v0.0.0-20180404174746-b3c676e531a6/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180416171110-a35a21de978d h1:O2P57H5Cc+d+DJos+iweraI9rmzMYwV+45vkZdDY0Oo=
|
||||
golang.org/x/net v0.0.0-20180416171110-a35a21de978d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sys v0.0.0-20180416112224-2f57af4873d0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20180911133044-677d2ff680c1 h1:dzEuQYa6+a3gROnSlgly5ERUm4SZKJt+dh+4iSbO+bI=
|
||||
golang.org/x/tools v0.0.0-20180911133044-677d2ff680c1/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6 h1:iZgcI2DDp6zW5v9Z/5+f0NuqoxNdmzg4hivjk2WLXpY=
|
||||
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
sourcegraph.com/sourcegraph/appdash v0.0.0-20180110180208-2cc67fd64755/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
|
||||
sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67/go.mod h1:L5q+DGLGOQFpo1snNEkLOJT2d1YTW66rWNzatr3He1k=
|
||||
@@ -0,0 +1,182 @@
|
||||
package graph2
|
||||
|
||||
import (
|
||||
input "git.aiterp.net/rpdata/api/graph2/input"
|
||||
changes "git.aiterp.net/rpdata/api/models/changes"
|
||||
channels "git.aiterp.net/rpdata/api/models/channels"
|
||||
characters "git.aiterp.net/rpdata/api/models/characters"
|
||||
files "git.aiterp.net/rpdata/api/models/files"
|
||||
logs "git.aiterp.net/rpdata/api/models/logs"
|
||||
posts "git.aiterp.net/rpdata/api/models/posts"
|
||||
stories "git.aiterp.net/rpdata/api/models/stories"
|
||||
)
|
||||
|
||||
func complexity() (cr ComplexityRoot) {
|
||||
subListComplexity := 25
|
||||
bigSublistComplexity := 75
|
||||
hugeSublistComplexity := 100
|
||||
findComplexity := 100
|
||||
listComplexity := 200
|
||||
mutationComplexity := 100
|
||||
|
||||
cr.Query.Character = func(childComplexity int, id *string, nick *string) int {
|
||||
return childComplexity + findComplexity
|
||||
}
|
||||
cr.Query.Characters = func(childComplexity int, filter *characters.Filter) int {
|
||||
return childComplexity + listComplexity
|
||||
}
|
||||
cr.Query.Channel = func(childComplexity int, name string) int {
|
||||
return childComplexity + findComplexity
|
||||
}
|
||||
cr.Query.Channels = func(childComplexity int, filter *channels.Filter) int {
|
||||
return childComplexity + listComplexity
|
||||
}
|
||||
cr.Query.Post = func(childComplexity int, id string) int {
|
||||
return childComplexity + findComplexity
|
||||
}
|
||||
cr.Query.Posts = func(childComplexity int, filter *posts.Filter) int {
|
||||
return childComplexity + listComplexity
|
||||
}
|
||||
cr.Query.UnknownNicks = func(childComplexity int, filter *input.UnknownNicksFilter) int {
|
||||
return childComplexity + listComplexity
|
||||
}
|
||||
cr.Query.Log = func(childComplexity int, id string) int {
|
||||
return childComplexity + findComplexity
|
||||
}
|
||||
cr.Query.Logs = func(childComplexity int, filter *logs.Filter) int {
|
||||
if filter != nil && filter.Open != nil && *filter.Open == true {
|
||||
return childComplexity + findComplexity
|
||||
}
|
||||
|
||||
return childComplexity + listComplexity
|
||||
}
|
||||
cr.Query.Chapter = func(childComplexity int, id string) int {
|
||||
return childComplexity + findComplexity
|
||||
}
|
||||
cr.Query.Comment = func(childComplexity int, id string) int {
|
||||
return childComplexity + findComplexity
|
||||
}
|
||||
cr.Query.Tags = func(childComplexity int) int {
|
||||
return childComplexity + listComplexity
|
||||
}
|
||||
cr.Query.Story = func(childComplexity int, id string) int {
|
||||
return childComplexity + findComplexity
|
||||
}
|
||||
cr.Query.Stories = func(childComplexity int, filter *stories.Filter) int {
|
||||
return childComplexity + listComplexity
|
||||
}
|
||||
cr.Query.File = func(childComplexity int, id string) int {
|
||||
return childComplexity + findComplexity
|
||||
}
|
||||
cr.Query.Files = func(childComplexity int, filter *files.Filter) int {
|
||||
return childComplexity + listComplexity
|
||||
}
|
||||
cr.Query.Changes = func(childComplexity int, filter *changes.Filter) int {
|
||||
return childComplexity + listComplexity
|
||||
}
|
||||
cr.Query.Token = func(childComplexity int) int {
|
||||
return childComplexity + findComplexity
|
||||
}
|
||||
|
||||
cr.Mutation.AddStory = func(childComplexity int, input input.StoryAddInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.AddStoryTag = func(childComplexity int, input input.StoryTagAddInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.RemoveStoryTag = func(childComplexity int, input input.StoryTagRemoveInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.EditStory = func(childComplexity int, input input.StoryEditInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.RemoveStory = func(childComplexity int, input input.StoryRemoveInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.AddChapter = func(childComplexity int, input input.ChapterAddInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.EditChapter = func(childComplexity int, input input.ChapterEditInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.MoveChapter = func(childComplexity int, input input.ChapterMoveInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.RemoveChapter = func(childComplexity int, input input.ChapterRemoveInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.AddComment = func(childComplexity int, input input.CommentAddInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.EditComment = func(childComplexity int, input input.CommentEditInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.RemoveComment = func(childComplexity int, input input.CommentRemoveInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.AddLog = func(childComplexity int, input input.LogAddInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.ImportLog = func(childComplexity int, input input.LogImportInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.EditLog = func(childComplexity int, input input.LogEditInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.RemoveLog = func(childComplexity int, input input.LogRemoveInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.AddPost = func(childComplexity int, input input.PostAddInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.EditPost = func(childComplexity int, input input.PostEditInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.MovePost = func(childComplexity int, input input.PostMoveInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.RemovePost = func(childComplexity int, input input.PostRemoveInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.AddCharacter = func(childComplexity int, input input.CharacterAddInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.AddCharacterNick = func(childComplexity int, input input.CharacterNickInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.RemoveCharacterNick = func(childComplexity int, input input.CharacterNickInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.EditCharacter = func(childComplexity int, input input.CharacterEditInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.RemoveCharacter = func(childComplexity int, input input.CharacterRemoveInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.AddChannel = func(childComplexity int, input input.ChannelAddInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
cr.Mutation.EditChannel = func(childComplexity int, input input.ChannelEditInput) int {
|
||||
return childComplexity + mutationComplexity
|
||||
}
|
||||
|
||||
cr.Log.Characters = func(childComplexity int) int {
|
||||
return childComplexity + subListComplexity
|
||||
}
|
||||
cr.Log.Posts = func(childComplexity int, kinds []string) int {
|
||||
return childComplexity + hugeSublistComplexity
|
||||
}
|
||||
|
||||
cr.Story.Chapters = func(childComplexity int) int {
|
||||
return childComplexity + bigSublistComplexity
|
||||
}
|
||||
cr.Story.Tags = func(childComplexity int) int {
|
||||
return childComplexity + subListComplexity
|
||||
}
|
||||
|
||||
cr.Chapter.Comments = func(childComplexity int, limit *int) int {
|
||||
return childComplexity + subListComplexity
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -23,6 +23,8 @@ models:
|
||||
model: git.aiterp.net/rpdata/api/models.Channel
|
||||
ChannelsFilter:
|
||||
model: git.aiterp.net/rpdata/api/models/channels.Filter
|
||||
UnknownNick:
|
||||
model: git.aiterp.net/rpdata/api/models.UnknownNick
|
||||
Post:
|
||||
model: git.aiterp.net/rpdata/api/models.Post
|
||||
PostsFilter:
|
||||
@@ -31,6 +33,15 @@ models:
|
||||
model: git.aiterp.net/rpdata/api/models.Log
|
||||
LogsFilter:
|
||||
model: git.aiterp.net/rpdata/api/models/logs.Filter
|
||||
LogImporter:
|
||||
model: git.aiterp.net/rpdata/api/models.LogImporter
|
||||
Comment:
|
||||
model: git.aiterp.net/rpdata/api/models.Comment
|
||||
fields:
|
||||
fictionalDate:
|
||||
resolver: true
|
||||
ChapterCommentMode:
|
||||
model: git.aiterp.net/rpdata/api/models.ChapterCommentMode
|
||||
Chapter:
|
||||
model: git.aiterp.net/rpdata/api/models.Chapter
|
||||
fields:
|
||||
@@ -49,6 +60,16 @@ models:
|
||||
model: git.aiterp.net/rpdata/api/models.File
|
||||
FilesFilter:
|
||||
model: git.aiterp.net/rpdata/api/models/files.Filter
|
||||
Change:
|
||||
model: git.aiterp.net/rpdata/api/models.Change
|
||||
ChangeModel:
|
||||
model: git.aiterp.net/rpdata/api/models.ChangeModel
|
||||
ChangeKey:
|
||||
model: git.aiterp.net/rpdata/api/models.ChangeKey
|
||||
ChangeKeyInput: # It's the same as ChangeKey
|
||||
model: git.aiterp.net/rpdata/api/models.ChangeKey
|
||||
ChangesFilter:
|
||||
model: git.aiterp.net/rpdata/api/models/changes.Filter
|
||||
Token:
|
||||
model: git.aiterp.net/rpdata/api/models.Token
|
||||
User:
|
||||
|
||||
+15
-2
@@ -7,12 +7,13 @@ import (
|
||||
)
|
||||
|
||||
//go:generate ./combine.sh
|
||||
//go:generate gorunpkg github.com/99designs/gqlgen -v
|
||||
//go:generate go run github.com/99designs/gqlgen -v
|
||||
|
||||
// New creates a new GraphQL schema.
|
||||
func New() graphql.ExecutableSchema {
|
||||
return NewExecutableSchema(Config{
|
||||
Resolvers: &rootResolver{},
|
||||
Resolvers: &rootResolver{},
|
||||
Complexity: complexity(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -26,10 +27,18 @@ func (r *rootResolver) Mutation() MutationResolver {
|
||||
return queries.MutationResolver
|
||||
}
|
||||
|
||||
func (r *rootResolver) Subscription() SubscriptionResolver {
|
||||
return queries.SubscriptionResolver
|
||||
}
|
||||
|
||||
func (r *rootResolver) Log() LogResolver {
|
||||
return &types.LogResolver
|
||||
}
|
||||
|
||||
func (r *rootResolver) Comment() CommentResolver {
|
||||
return &types.CommentResolver
|
||||
}
|
||||
|
||||
func (r *rootResolver) Chapter() ChapterResolver {
|
||||
return &types.ChapterResolver
|
||||
}
|
||||
@@ -42,6 +51,10 @@ func (r *rootResolver) File() FileResolver {
|
||||
return &types.FileResolver
|
||||
}
|
||||
|
||||
func (r *rootResolver) Change() ChangeResolver {
|
||||
return &types.ChangeResolver
|
||||
}
|
||||
|
||||
func (r *rootResolver) Token() TokenResolver {
|
||||
return &types.TokenResolver
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package queries
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"git.aiterp.net/rpdata/api/models/changes"
|
||||
)
|
||||
|
||||
/// Queries
|
||||
|
||||
func (r *resolver) Changes(ctx context.Context, filter *changes.Filter) ([]models.Change, error) {
|
||||
return changes.List(filter)
|
||||
}
|
||||
|
||||
/// Subscriptions
|
||||
|
||||
func (r *subscriptionResolver) Changes(ctx context.Context, keys []models.ChangeKey) (<-chan models.Change, error) {
|
||||
if len(keys) == 0 {
|
||||
return nil, errors.New("At least one key is required for a subscription")
|
||||
}
|
||||
|
||||
return changes.Subscribe(ctx, keys, false), nil
|
||||
}
|
||||
+12
-11
@@ -7,13 +7,14 @@ import (
|
||||
"git.aiterp.net/rpdata/api/graph2/input"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"git.aiterp.net/rpdata/api/models/changekeys"
|
||||
"git.aiterp.net/rpdata/api/models/changes"
|
||||
"git.aiterp.net/rpdata/api/models/channels"
|
||||
)
|
||||
|
||||
// Queries
|
||||
|
||||
func (r *resolver) Channel(ctx context.Context, name string) (models.Channel, error) {
|
||||
func (r *resolver) Channel(ctx context.Context, name string) (*models.Channel, error) {
|
||||
return channels.FindName(name)
|
||||
}
|
||||
|
||||
@@ -23,10 +24,10 @@ func (r *resolver) Channels(ctx context.Context, filter *channels.Filter) ([]mod
|
||||
|
||||
// Mutations
|
||||
|
||||
func (r *mutationResolver) AddChannel(ctx context.Context, input input.ChannelAddInput) (models.Channel, error) {
|
||||
func (r *mutationResolver) AddChannel(ctx context.Context, input input.ChannelAddInput) (*models.Channel, error) {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Authenticated() || !token.Permitted("channel.add") {
|
||||
return models.Channel{}, errors.New("You are not permitted to add channels")
|
||||
return nil, errors.New("You are not permitted to add channels")
|
||||
}
|
||||
|
||||
logged := false
|
||||
@@ -48,31 +49,31 @@ func (r *mutationResolver) AddChannel(ctx context.Context, input input.ChannelAd
|
||||
|
||||
channel, err := channels.Add(input.Name, logged, hub, eventName, locationName)
|
||||
if err != nil {
|
||||
return models.Channel{}, errors.New("Failed to add channel: " + err.Error())
|
||||
return nil, errors.New("Failed to add channel: " + err.Error())
|
||||
}
|
||||
|
||||
go changes.Submit("Channel", "add", token.UserID, channel)
|
||||
go changes.Submit("Channel", "add", token.UserID, true, changekeys.Listed(channel), channel)
|
||||
|
||||
return channel, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) EditChannel(ctx context.Context, input input.ChannelEditInput) (models.Channel, error) {
|
||||
func (r *mutationResolver) EditChannel(ctx context.Context, input input.ChannelEditInput) (*models.Channel, error) {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Authenticated() || !token.Permitted("channel.edit") {
|
||||
return models.Channel{}, errors.New("You are not permitted to edit channels")
|
||||
return nil, errors.New("You are not permitted to edit channels")
|
||||
}
|
||||
|
||||
channel, err := channels.FindName(input.Name)
|
||||
if err != nil {
|
||||
return models.Channel{}, errors.New("Channel not found")
|
||||
return nil, errors.New("Channel not found")
|
||||
}
|
||||
|
||||
channel, err = channels.Edit(channel, input.Logged, input.Hub, input.EventName, input.LocationName)
|
||||
channel, err = channels.Edit(*channel, input.Logged, input.Hub, input.EventName, input.LocationName)
|
||||
if err != nil {
|
||||
return models.Channel{}, errors.New("Failed to edit channel: " + err.Error())
|
||||
return nil, errors.New("Failed to edit channel: " + err.Error())
|
||||
}
|
||||
|
||||
go changes.Submit("Channel", "edit", token.UserID, channel)
|
||||
go changes.Submit("Channel", "edit", token.UserID, true, changekeys.Listed(channel), channel)
|
||||
|
||||
return channel, nil
|
||||
}
|
||||
|
||||
+111
-18
@@ -5,6 +5,10 @@ import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models/changekeys"
|
||||
"git.aiterp.net/rpdata/api/models/changes"
|
||||
"git.aiterp.net/rpdata/api/models/comments"
|
||||
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/models/stories"
|
||||
|
||||
@@ -15,67 +19,156 @@ import (
|
||||
|
||||
// Queries
|
||||
|
||||
func (r *resolver) Chapter(ctx context.Context, id string) (models.Chapter, error) {
|
||||
func (r *resolver) Chapter(ctx context.Context, id string) (*models.Chapter, error) {
|
||||
return chapters.FindID(id)
|
||||
}
|
||||
|
||||
// Mutations
|
||||
|
||||
func (r *mutationResolver) AddChapter(ctx context.Context, input input.ChapterAddInput) (models.Chapter, error) {
|
||||
func (r *mutationResolver) AddChapter(ctx context.Context, input input.ChapterAddInput) (*models.Chapter, error) {
|
||||
story, err := stories.FindID(input.StoryID)
|
||||
if err != nil {
|
||||
return models.Chapter{}, errors.New("Story not found")
|
||||
return nil, errors.New("Story not found")
|
||||
}
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Authenticated() {
|
||||
return models.Chapter{}, errors.New("Unauthorized")
|
||||
if !token.Permitted("member", "story.add") {
|
||||
return nil, errors.New("Unauthorized")
|
||||
}
|
||||
|
||||
author := token.UserID
|
||||
if input.Author != nil && *input.Author != author {
|
||||
if !token.Permitted("story.add") {
|
||||
return models.Chapter{}, errors.New("False pretender")
|
||||
return nil, errors.New("False pretender")
|
||||
}
|
||||
|
||||
author = *input.Author
|
||||
}
|
||||
|
||||
if !story.Open && story.Author != author {
|
||||
return models.Chapter{}, errors.New("Story is not open")
|
||||
return nil, errors.New("Story is not open")
|
||||
}
|
||||
|
||||
return chapters.Add(story, input.Title, author, input.Source, time.Now(), input.FictionalDate)
|
||||
commentMode := models.ChapterCommentModeDisabled
|
||||
if input.CommentMode != nil {
|
||||
commentMode = *input.CommentMode
|
||||
}
|
||||
|
||||
chapter, err := chapters.Add(*story, input.Title, author, input.Source, time.Now(), input.FictionalDate, commentMode)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to create chapter: " + err.Error())
|
||||
}
|
||||
|
||||
go changes.Submit("Chapter", "add", token.UserID, story.Listed, changekeys.Listed(story, chapter), story, chapter)
|
||||
|
||||
return &chapter, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) EditChapter(ctx context.Context, input input.ChapterEditInput) (models.Chapter, error) {
|
||||
func (r *mutationResolver) MoveChapter(ctx context.Context, input input.ChapterMoveInput) (*models.Chapter, error) {
|
||||
chapter, err := chapters.FindID(input.ID)
|
||||
if err != nil {
|
||||
return models.Chapter{}, errors.New("Chapter not found")
|
||||
return nil, errors.New("Chapter not found")
|
||||
}
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Authenticated() || !token.PermittedUser(chapter.Author, "member", "story.edit") {
|
||||
return models.Chapter{}, errors.New("Unauthorized")
|
||||
if !token.Authenticated() || !token.PermittedUser(chapter.Author, "member", "chapter.move") {
|
||||
return nil, errors.New("You are not allowed to move this chapter")
|
||||
}
|
||||
|
||||
target, err := stories.FindID(input.StoryID)
|
||||
if err != nil {
|
||||
return nil, errors.New("Target story not found")
|
||||
}
|
||||
|
||||
if !target.Open && !token.PermittedUser(target.Author, "member", "chapter.move") {
|
||||
return nil, errors.New("You are not permitted to move chapters to this story")
|
||||
}
|
||||
|
||||
oldStoryID := chapter.StoryID
|
||||
chapter, err = chapters.Move(chapter, *target)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to move chapter: " + err.Error())
|
||||
}
|
||||
|
||||
go func() {
|
||||
story, err := stories.FindID(chapter.StoryID)
|
||||
if err != nil {
|
||||
story.ID = chapter.StoryID
|
||||
}
|
||||
|
||||
oldStory, err := stories.FindID(oldStoryID)
|
||||
if err != nil {
|
||||
oldStory.ID = oldStoryID
|
||||
}
|
||||
|
||||
changes.Submit("Chapter", "move-out", chapter.Author, oldStory.Listed, changekeys.Many(oldStory, chapter), chapter)
|
||||
changes.Submit("Chapter", "move-in", token.UserID, story.Listed, changekeys.Listed(story, chapter), story, chapter)
|
||||
}()
|
||||
|
||||
return &chapter, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) EditChapter(ctx context.Context, input input.ChapterEditInput) (*models.Chapter, error) {
|
||||
chapter, err := chapters.FindID(input.ID)
|
||||
if err != nil {
|
||||
return nil, errors.New("Chapter not found")
|
||||
}
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Authenticated() || !token.PermittedUser(chapter.Author, "member", "chapter.edit") {
|
||||
return nil, errors.New("Unauthorized")
|
||||
}
|
||||
|
||||
if input.ClearFictionalDate != nil && *input.ClearFictionalDate == true {
|
||||
input.FictionalDate = &time.Time{}
|
||||
}
|
||||
|
||||
return chapters.Edit(chapter, input.Title, input.Source, input.FictionalDate)
|
||||
chapter, err = chapters.Edit(chapter, input.Title, input.Source, input.FictionalDate, input.CommentMode, input.CommentsLocked)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to edit chapter: " + err.Error())
|
||||
}
|
||||
|
||||
go func() {
|
||||
story, err := stories.FindID(chapter.StoryID)
|
||||
if err != nil {
|
||||
story.ID = chapter.StoryID
|
||||
}
|
||||
|
||||
changes.Submit("Chapter", "edit", token.UserID, story.Listed, changekeys.Many(story, chapter), chapter)
|
||||
}()
|
||||
|
||||
return &chapter, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) RemoveChapter(ctx context.Context, input input.ChapterRemoveInput) (models.Chapter, error) {
|
||||
func (r *mutationResolver) RemoveChapter(ctx context.Context, input input.ChapterRemoveInput) (*models.Chapter, error) {
|
||||
chapter, err := chapters.FindID(input.ID)
|
||||
if err != nil {
|
||||
return models.Chapter{}, errors.New("Chapter not found")
|
||||
return nil, errors.New("Chapter not found")
|
||||
}
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Authenticated() || !token.PermittedUser(chapter.Author, "member", "story.remove") {
|
||||
return models.Chapter{}, errors.New("Unauthorized")
|
||||
if !token.Authenticated() || !token.PermittedUser(chapter.Author, "member", "chapter.remove") {
|
||||
return nil, errors.New("Unauthorized")
|
||||
}
|
||||
|
||||
return chapters.Remove(chapter)
|
||||
chapter, err = chapters.Remove(chapter)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to remove chapter: " + err.Error())
|
||||
}
|
||||
|
||||
err = comments.RemoveChapter(chapter)
|
||||
if err != nil {
|
||||
return nil, errors.New("Chapter was removed, but comment removal failed: " + err.Error())
|
||||
}
|
||||
|
||||
go func() {
|
||||
story, err := stories.FindID(chapter.StoryID)
|
||||
if err != nil {
|
||||
story.ID = chapter.StoryID
|
||||
}
|
||||
|
||||
changes.Submit("Chapter", "remove", token.UserID, story.Listed, changekeys.Many(story, chapter), chapter)
|
||||
}()
|
||||
|
||||
return &chapter, nil
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"git.aiterp.net/rpdata/api/graph2/input"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"git.aiterp.net/rpdata/api/models/changekeys"
|
||||
"git.aiterp.net/rpdata/api/models/changes"
|
||||
"git.aiterp.net/rpdata/api/models/characters"
|
||||
"git.aiterp.net/rpdata/api/models/logs"
|
||||
@@ -68,7 +69,7 @@ func (r *mutationResolver) AddCharacter(ctx context.Context, input input.Charact
|
||||
return models.Character{}, errors.New("Adding character failed: " + err.Error())
|
||||
}
|
||||
|
||||
go changes.Submit("Character", "add", token.UserID, character)
|
||||
go changes.Submit("Character", "add", token.UserID, true, changekeys.Listed(character), character)
|
||||
|
||||
return character, nil
|
||||
}
|
||||
@@ -96,7 +97,7 @@ func (r *mutationResolver) AddCharacterNick(ctx context.Context, input input.Cha
|
||||
}
|
||||
|
||||
go logs.ScheduleFullUpdate()
|
||||
go changes.Submit("Character", "edit", token.UserID, character)
|
||||
go changes.Submit("Character", "edit", token.UserID, true, changekeys.Listed(character), character)
|
||||
|
||||
return character, nil
|
||||
}
|
||||
@@ -118,7 +119,7 @@ func (r *mutationResolver) RemoveCharacterNick(ctx context.Context, input input.
|
||||
}
|
||||
|
||||
go logs.ScheduleFullUpdate()
|
||||
go changes.Submit("Character", "edit", token.UserID, character)
|
||||
go changes.Submit("Character", "edit", token.UserID, true, changekeys.Listed(character), character)
|
||||
|
||||
return character, nil
|
||||
}
|
||||
@@ -146,7 +147,7 @@ func (r *mutationResolver) EditCharacter(ctx context.Context, input input.Charac
|
||||
return models.Character{}, errors.New("Failed to edit character: " + err.Error())
|
||||
}
|
||||
|
||||
go changes.Submit("Character", "edit", token.UserID, character)
|
||||
go changes.Submit("Character", "edit", token.UserID, true, changekeys.Listed(character), character)
|
||||
|
||||
return character, nil
|
||||
}
|
||||
@@ -167,7 +168,7 @@ func (r *mutationResolver) RemoveCharacter(ctx context.Context, input input.Char
|
||||
return models.Character{}, errors.New("Failed to remove character: " + err.Error())
|
||||
}
|
||||
|
||||
go changes.Submit("Character", "remove", token.UserID, character)
|
||||
go changes.Submit("Character", "remove", token.UserID, true, changekeys.Listed(character), character)
|
||||
|
||||
return character, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
package queries
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models/stories"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models/characters"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models/changekeys"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models/changes"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graph2/input"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/models/chapters"
|
||||
"git.aiterp.net/rpdata/api/models/comments"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
)
|
||||
|
||||
// Queries
|
||||
|
||||
func (r *resolver) Comment(ctx context.Context, id string) (models.Comment, error) {
|
||||
return comments.Find(id)
|
||||
}
|
||||
|
||||
// Mutations
|
||||
|
||||
func (r *mutationResolver) AddComment(ctx context.Context, input input.CommentAddInput) (models.Comment, error) {
|
||||
chapter, err := chapters.FindID(input.ChapterID)
|
||||
if err != nil {
|
||||
return models.Comment{}, errors.New("Chapter not found")
|
||||
}
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Permitted("member", "story.edit") {
|
||||
return models.Comment{}, errors.New("Unauthorized")
|
||||
}
|
||||
|
||||
if !chapter.CanComment() {
|
||||
return models.Comment{}, errors.New("Comments are disabled or locked")
|
||||
}
|
||||
|
||||
var characterPtr *models.Character
|
||||
if input.CharacterID != nil {
|
||||
character, err := characters.FindID(*input.CharacterID)
|
||||
if err != nil {
|
||||
return models.Comment{}, errors.New("Character not found")
|
||||
} else if character.Author != token.UserID {
|
||||
return models.Comment{}, errors.New("That is not your character")
|
||||
}
|
||||
|
||||
characterPtr = &character
|
||||
}
|
||||
|
||||
fictionalDate := time.Time{}
|
||||
if input.FictionalDate != nil {
|
||||
fictionalDate = *input.FictionalDate
|
||||
}
|
||||
|
||||
subject := ""
|
||||
if input.Subject != nil {
|
||||
subject = *input.Subject
|
||||
}
|
||||
|
||||
comment, err := comments.Add(chapter, subject, token.UserID, input.Source, input.CharacterName, characterPtr, time.Now(), fictionalDate)
|
||||
if err != nil {
|
||||
return models.Comment{}, errors.New("Failed to add comment: " + err.Error())
|
||||
}
|
||||
|
||||
go func() {
|
||||
story, err := stories.FindID(chapter.StoryID)
|
||||
if err != nil {
|
||||
log.Println("WARNING: Couldn't log comment change:", err)
|
||||
return
|
||||
}
|
||||
|
||||
changes.Submit("Comment", "add", token.UserID, true, changekeys.Many(comment, chapter, models.Story{ID: chapter.StoryID}), comment, chapter, story)
|
||||
}()
|
||||
|
||||
return comment, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) EditComment(ctx context.Context, input input.CommentEditInput) (models.Comment, error) {
|
||||
comment, err := comments.Find(input.CommentID)
|
||||
if err != nil {
|
||||
return models.Comment{}, errors.New("Comment not found")
|
||||
}
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.PermittedUser(comment.Author, "member", "story.edit") {
|
||||
return models.Comment{}, errors.New("You cannot edit this comment")
|
||||
}
|
||||
|
||||
chapter, err := chapters.FindID(comment.ChapterID)
|
||||
if err != nil {
|
||||
return models.Comment{}, errors.New("Comment's chapter not found")
|
||||
}
|
||||
|
||||
if !chapter.CanComment() {
|
||||
return models.Comment{}, errors.New("Comments are disabled or locked")
|
||||
}
|
||||
|
||||
if input.ClearFictionalDate != nil && *input.ClearFictionalDate == true {
|
||||
input.FictionalDate = &time.Time{}
|
||||
}
|
||||
|
||||
if input.CharacterID != nil && *input.CharacterID != "" {
|
||||
character, err := characters.FindID(*input.CharacterID)
|
||||
if err != nil {
|
||||
return models.Comment{}, errors.New("Character not found")
|
||||
} else if character.Author != token.UserID {
|
||||
return models.Comment{}, errors.New("That is not your character")
|
||||
}
|
||||
}
|
||||
|
||||
comment, err = comments.Edit(comment, input.Source, input.CharacterName, input.CharacterID, input.Subject, input.FictionalDate)
|
||||
if err != nil {
|
||||
return models.Comment{}, errors.New("Could not post comment: " + err.Error())
|
||||
}
|
||||
|
||||
go func() {
|
||||
story, err := stories.FindID(chapter.StoryID)
|
||||
if err != nil {
|
||||
log.Println("WARNING: Couldn't log comment change:", err)
|
||||
return
|
||||
}
|
||||
|
||||
changes.Submit("Comment", "edit", token.UserID, true, changekeys.Many(comment, chapter, models.Story{ID: chapter.StoryID}), comment, chapter, story)
|
||||
}()
|
||||
|
||||
return comment, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) RemoveComment(ctx context.Context, input input.CommentRemoveInput) (models.Comment, error) {
|
||||
comment, err := comments.Find(input.CommentID)
|
||||
if err != nil {
|
||||
return models.Comment{}, errors.New("Comment not found")
|
||||
}
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.PermittedUser(comment.Author, "member", "story.edit") {
|
||||
return models.Comment{}, errors.New("You cannot remove this comment")
|
||||
}
|
||||
|
||||
chapter, err := chapters.FindID(comment.ChapterID)
|
||||
if err != nil {
|
||||
return models.Comment{}, errors.New("Comment's chapter not found")
|
||||
}
|
||||
|
||||
if !chapter.CanComment() {
|
||||
return models.Comment{}, errors.New("Comments are disabled or locked")
|
||||
}
|
||||
|
||||
err = comments.Remove(comment)
|
||||
if err != nil {
|
||||
return models.Comment{}, errors.New("Failed to remove comment: " + err.Error())
|
||||
}
|
||||
|
||||
go func() {
|
||||
story, err := stories.FindID(chapter.StoryID)
|
||||
if err != nil {
|
||||
log.Println("WARNING: Couldn't log comment change:", err)
|
||||
return
|
||||
}
|
||||
|
||||
changes.Submit("Comment", "remove", token.UserID, true, changekeys.Many(comment, chapter, models.Story{ID: chapter.StoryID}), comment, chapter, story)
|
||||
}()
|
||||
|
||||
return comment, nil
|
||||
}
|
||||
+62
-3
@@ -4,11 +4,15 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models/channels"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graph2/input"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/internal/loader"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"git.aiterp.net/rpdata/api/models/changekeys"
|
||||
"git.aiterp.net/rpdata/api/models/changes"
|
||||
"git.aiterp.net/rpdata/api/models/logs"
|
||||
"github.com/99designs/gqlgen/graphql"
|
||||
@@ -72,16 +76,71 @@ func (r *mutationResolver) AddLog(ctx context.Context, input input.LogAddInput)
|
||||
description = *input.Description
|
||||
}
|
||||
|
||||
existingChannel, _ := channels.FindName(input.Channel)
|
||||
|
||||
log, err := logs.Add(input.Date, input.Channel, title, event, description, open)
|
||||
if !token.Authenticated() || !token.Permitted("log.add") {
|
||||
return models.Log{}, errors.New("Failed to create log: " + err.Error())
|
||||
}
|
||||
|
||||
go changes.Submit("Log", "add", token.UserID, log)
|
||||
go func() {
|
||||
if existingChannel.Name == "" {
|
||||
channel, err := channels.FindName(input.Channel)
|
||||
if err == nil {
|
||||
changes.Submit("Channel", "add", token.UserID, true, changekeys.Listed(channel), channel)
|
||||
}
|
||||
}
|
||||
|
||||
changes.Submit("Log", "add", token.UserID, true, changekeys.Listed(log), log)
|
||||
}()
|
||||
|
||||
return log, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) ImportLog(ctx context.Context, input input.LogImportInput) ([]models.Log, error) {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Authenticated() || !token.Permitted("log.add") {
|
||||
return nil, errors.New("You are not permitted to add logs")
|
||||
}
|
||||
|
||||
date := time.Time{}
|
||||
if input.Date != nil {
|
||||
date = *input.Date
|
||||
}
|
||||
|
||||
tz := time.UTC
|
||||
if input.Timezone != nil {
|
||||
parsedTZ, err := time.LoadLocation(*input.Timezone)
|
||||
if err != nil {
|
||||
return nil, errors.New("Unknown timezone: " + *input.Timezone)
|
||||
}
|
||||
|
||||
tz = parsedTZ
|
||||
}
|
||||
|
||||
results, err := logs.Import(input.Importer, date, tz, input.ChannelName, input.Data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newLogs := make([]models.Log, 0, len(results))
|
||||
for _, result := range results {
|
||||
go func(result logs.ImportedLog) {
|
||||
changes.Submit("Log", "add", token.UserID, true, changekeys.Many(result.Log), result.Log)
|
||||
changes.Submit("Post", "add", token.UserID, true, changekeys.Many(result.Log, result.Posts), result.Posts)
|
||||
}(result)
|
||||
|
||||
log, err := logs.UpdateCharacters(result.Log, nil)
|
||||
if err != nil {
|
||||
log = result.Log
|
||||
}
|
||||
|
||||
newLogs = append(newLogs, log)
|
||||
}
|
||||
|
||||
return newLogs, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) EditLog(ctx context.Context, input input.LogEditInput) (models.Log, error) {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Authenticated() || !token.Permitted("log.edit") {
|
||||
@@ -98,7 +157,7 @@ func (r *mutationResolver) EditLog(ctx context.Context, input input.LogEditInput
|
||||
return models.Log{}, errors.New("Failed to edit log: " + err.Error())
|
||||
}
|
||||
|
||||
go changes.Submit("Log", "edit", token.UserID, log)
|
||||
go changes.Submit("Log", "edit", token.UserID, true, changekeys.Listed(log), log)
|
||||
|
||||
return log, nil
|
||||
}
|
||||
@@ -119,7 +178,7 @@ func (r *mutationResolver) RemoveLog(ctx context.Context, input input.LogRemoveI
|
||||
return models.Log{}, errors.New("Failed to remove log: " + err.Error())
|
||||
}
|
||||
|
||||
go changes.Submit("Log", "remove", token.UserID, log)
|
||||
go changes.Submit("Log", "remove", token.UserID, true, changekeys.Listed(log), log)
|
||||
|
||||
return log, nil
|
||||
}
|
||||
|
||||
+22
-11
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models/changekeys"
|
||||
"git.aiterp.net/rpdata/api/models/changes"
|
||||
"git.aiterp.net/rpdata/api/models/logs"
|
||||
|
||||
@@ -58,8 +59,8 @@ func (r *mutationResolver) AddPost(ctx context.Context, input input.PostAddInput
|
||||
return models.Post{}, err
|
||||
}
|
||||
|
||||
go logs.UpdateCharacters(log)
|
||||
go changes.Submit("Post", "add", token.UserID, post)
|
||||
go logs.UpdateCharacters(log, nil)
|
||||
go changes.Submit("Post", "add", token.UserID, true, changekeys.Many(log, post), post)
|
||||
|
||||
return post, nil
|
||||
}
|
||||
@@ -82,7 +83,7 @@ func (r *mutationResolver) EditPost(ctx context.Context, input input.PostEditInp
|
||||
return
|
||||
}
|
||||
|
||||
logs.UpdateCharacters(log)
|
||||
logs.UpdateCharacters(log, nil)
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -91,7 +92,14 @@ func (r *mutationResolver) EditPost(ctx context.Context, input input.PostEditInp
|
||||
return models.Post{}, errors.New("Adding post failed: " + err.Error())
|
||||
}
|
||||
|
||||
go changes.Submit("Post", "edit", token.UserID, post)
|
||||
go func() {
|
||||
log, err := logs.FindID(post.LogID)
|
||||
if err != nil {
|
||||
log = models.Log{ID: post.LogID}
|
||||
}
|
||||
|
||||
changes.Submit("Post", "edit", token.UserID, true, changekeys.Many(log, post), post)
|
||||
}()
|
||||
|
||||
return post, nil
|
||||
}
|
||||
@@ -112,11 +120,14 @@ func (r *mutationResolver) MovePost(ctx context.Context, input input.PostMoveInp
|
||||
return nil, errors.New("Moving posts failed: " + err.Error())
|
||||
}
|
||||
|
||||
posts2 := make([]interface{}, len(posts))
|
||||
for i := range posts {
|
||||
posts2[i] = posts[i]
|
||||
}
|
||||
go changes.Submit("Post", "move", token.UserID, posts2...)
|
||||
go func() {
|
||||
log, err := logs.FindID(post.LogID)
|
||||
if err != nil {
|
||||
log = models.Log{ID: post.LogID}
|
||||
}
|
||||
|
||||
changes.Submit("Post", "move", token.UserID, true, changekeys.Many(log, posts), posts)
|
||||
}()
|
||||
|
||||
return posts, nil
|
||||
}
|
||||
@@ -143,9 +154,9 @@ func (r *mutationResolver) RemovePost(ctx context.Context, input input.PostRemov
|
||||
return
|
||||
}
|
||||
|
||||
logs.UpdateCharacters(log)
|
||||
logs.UpdateCharacters(log, nil)
|
||||
changes.Submit("Post", "remove", token.UserID, true, changekeys.Many(log, post), post)
|
||||
}()
|
||||
go changes.Submit("Post", "remove", token.UserID, post)
|
||||
|
||||
return post, nil
|
||||
}
|
||||
|
||||
@@ -2,9 +2,13 @@ package queries
|
||||
|
||||
type resolver struct{}
|
||||
type mutationResolver struct{}
|
||||
type subscriptionResolver struct{}
|
||||
|
||||
// Resolver has all the queries
|
||||
var Resolver resolver
|
||||
|
||||
// MutationResolver brings the mutagens.
|
||||
var MutationResolver *mutationResolver
|
||||
|
||||
// SubscriptionResolver has the real-time magic.
|
||||
var SubscriptionResolver *subscriptionResolver
|
||||
|
||||
+71
-26
@@ -5,6 +5,9 @@ import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models/changekeys"
|
||||
"git.aiterp.net/rpdata/api/models/changes"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graph2/input"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
@@ -12,7 +15,7 @@ import (
|
||||
"git.aiterp.net/rpdata/api/models/stories"
|
||||
)
|
||||
|
||||
func (r *resolver) Story(ctx context.Context, id string) (models.Story, error) {
|
||||
func (r *resolver) Story(ctx context.Context, id string) (*models.Story, error) {
|
||||
return stories.FindID(id)
|
||||
}
|
||||
|
||||
@@ -35,16 +38,16 @@ func (r *resolver) Stories(ctx context.Context, filter *stories.Filter) ([]model
|
||||
|
||||
// Mutations
|
||||
|
||||
func (r *mutationResolver) AddStory(ctx context.Context, input input.StoryAddInput) (models.Story, error) {
|
||||
func (r *mutationResolver) AddStory(ctx context.Context, input input.StoryAddInput) (*models.Story, error) {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if token == nil || !token.Permitted("member", "story.add") {
|
||||
return models.Story{}, errors.New("Permission denied")
|
||||
return nil, errors.New("Permission denied")
|
||||
}
|
||||
|
||||
author := token.UserID
|
||||
if input.Author != nil && *input.Author != author {
|
||||
if !token.Permitted("story.add") {
|
||||
return models.Story{}, errors.New("You are not permitted to add a story in another author's name")
|
||||
return nil, errors.New("You are not permitted to add a story in another author's name")
|
||||
}
|
||||
|
||||
author = *input.Author
|
||||
@@ -58,79 +61,121 @@ func (r *mutationResolver) AddStory(ctx context.Context, input input.StoryAddInp
|
||||
listed := input.Listed != nil && *input.Listed
|
||||
open := input.Open != nil && *input.Open
|
||||
|
||||
return stories.Add(input.Name, author, input.Category, listed, open, input.Tags, time.Now(), fictionalDate)
|
||||
story, err := stories.Add(input.Name, author, input.Category, listed, open, input.Tags, time.Now(), fictionalDate)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to add story: " + err.Error())
|
||||
}
|
||||
|
||||
go changes.Submit("Story", "add", token.UserID, story.Listed, changekeys.Listed(story), story)
|
||||
|
||||
return story, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) AddStoryTag(ctx context.Context, input input.StoryTagAddInput) (models.Story, error) {
|
||||
func (r *mutationResolver) AddStoryTag(ctx context.Context, input input.StoryTagAddInput) (*models.Story, error) {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
|
||||
story, err := stories.FindID(input.ID)
|
||||
if err != nil {
|
||||
return models.Story{}, errors.New("Story not found")
|
||||
return nil, errors.New("Story not found")
|
||||
}
|
||||
|
||||
if !token.PermittedUser(story.Author, "member", "story.edit") {
|
||||
return models.Story{}, errors.New("You are not permitted to edit this story")
|
||||
if story.Open {
|
||||
if !token.Permitted("member") {
|
||||
return nil, errors.New("You are not permitted to edit this story")
|
||||
}
|
||||
} else {
|
||||
if !token.PermittedUser(story.Author, "member", "story.edit") {
|
||||
return nil, errors.New("You are not permitted to edit this story")
|
||||
}
|
||||
}
|
||||
|
||||
return stories.AddTag(story, input.Tag)
|
||||
story, err = stories.AddTag(*story, input.Tag)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to add story: " + err.Error())
|
||||
}
|
||||
|
||||
go changes.Submit("Story", "tag", token.UserID, story.Listed, changekeys.Listed(story), story, input.Tag)
|
||||
|
||||
return story, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) RemoveStoryTag(ctx context.Context, input input.StoryTagRemoveInput) (models.Story, error) {
|
||||
func (r *mutationResolver) RemoveStoryTag(ctx context.Context, input input.StoryTagRemoveInput) (*models.Story, error) {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
|
||||
story, err := stories.FindID(input.ID)
|
||||
if err != nil {
|
||||
return models.Story{}, errors.New("Story not found")
|
||||
return nil, errors.New("Story not found")
|
||||
}
|
||||
|
||||
if !token.PermittedUser(story.Author, "member", "story.edit") {
|
||||
return models.Story{}, errors.New("You are not permitted to edit this story")
|
||||
if story.Open {
|
||||
if !token.Permitted("member") {
|
||||
return nil, errors.New("You are not permitted to edit this story")
|
||||
}
|
||||
} else {
|
||||
if !token.PermittedUser(story.Author, "member", "story.edit") {
|
||||
return nil, errors.New("You are not permitted to edit this story")
|
||||
}
|
||||
}
|
||||
|
||||
return stories.RemoveTag(story, input.Tag)
|
||||
story, err = stories.RemoveTag(*story, input.Tag)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to add story: " + err.Error())
|
||||
}
|
||||
|
||||
go changes.Submit("Story", "untag", token.UserID, story.Listed, changekeys.Listed(story), story, input.Tag)
|
||||
|
||||
return story, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) EditStory(ctx context.Context, input input.StoryEditInput) (models.Story, error) {
|
||||
func (r *mutationResolver) EditStory(ctx context.Context, input input.StoryEditInput) (*models.Story, error) {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
|
||||
story, err := stories.FindID(input.ID)
|
||||
if err != nil {
|
||||
return models.Story{}, errors.New("Story not found")
|
||||
return nil, errors.New("Story not found")
|
||||
}
|
||||
|
||||
if !token.PermittedUser(story.Author, "member", "story.edit") {
|
||||
return models.Story{}, errors.New("You are not permitted to remove this story")
|
||||
return nil, errors.New("You are not permitted to remove this story")
|
||||
}
|
||||
|
||||
if input.ClearFictionalDate != nil && *input.ClearFictionalDate {
|
||||
input.FictionalDate = &time.Time{}
|
||||
}
|
||||
|
||||
return stories.Edit(story, input.Name, input.Category, input.Listed, input.Open, input.FictionalDate)
|
||||
story, err = stories.Edit(*story, input.Name, input.Category, input.Listed, input.Open, input.FictionalDate)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to add story: " + err.Error())
|
||||
}
|
||||
|
||||
go changes.Submit("Story", "edit", token.UserID, story.Listed, changekeys.Listed(story), story)
|
||||
|
||||
return story, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) RemoveStory(ctx context.Context, input input.StoryRemoveInput) (models.Story, error) {
|
||||
func (r *mutationResolver) RemoveStory(ctx context.Context, input input.StoryRemoveInput) (*models.Story, error) {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
|
||||
story, err := stories.FindID(input.ID)
|
||||
if err != nil {
|
||||
return models.Story{}, errors.New("Story not found")
|
||||
return nil, errors.New("Story not found")
|
||||
}
|
||||
|
||||
if !token.PermittedUser(story.Author, "member", "story.remove") {
|
||||
return models.Story{}, errors.New("You are not permitted to remove this story")
|
||||
return nil, errors.New("You are not permitted to remove this story")
|
||||
}
|
||||
|
||||
story, err = stories.Remove(story)
|
||||
story, err = stories.Remove(*story)
|
||||
if err != nil {
|
||||
return models.Story{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = chapters.RemoveStory(story)
|
||||
err = chapters.RemoveStory(*story)
|
||||
if err != nil {
|
||||
return models.Story{}, errors.New("Failed to remove chapters, but story is removed: " + err.Error())
|
||||
return nil, errors.New("Failed to remove chapters, but story is removed: " + err.Error())
|
||||
}
|
||||
|
||||
go changes.Submit("Story", "remove", token.UserID, story.Listed, changekeys.Listed(story), story)
|
||||
|
||||
return story, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package queries
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"git.aiterp.net/rpdata/api/models/unknownnicks"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graph2/input"
|
||||
)
|
||||
|
||||
/// Queries
|
||||
|
||||
func (r *resolver) UnknownNicks(ctx context.Context, filter *input.UnknownNicksFilter) ([]models.UnknownNick, error) {
|
||||
limit := 100
|
||||
if filter != nil {
|
||||
if filter.Limit > 0 {
|
||||
limit = filter.Limit
|
||||
}
|
||||
}
|
||||
|
||||
return unknownnicks.List(limit)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
schema {
|
||||
query: Query
|
||||
mutation: Mutation
|
||||
subscription: Subscription
|
||||
}
|
||||
|
||||
type Query {
|
||||
@@ -25,6 +26,10 @@ type Query {
|
||||
posts(filter: PostsFilter): [Post!]!
|
||||
|
||||
|
||||
"List unknown nicks"
|
||||
unknownNicks(filter: UnknownNicksFilter): [UnknownNick!]!
|
||||
|
||||
|
||||
# Find log by ID
|
||||
log(id: String!): Log!
|
||||
|
||||
@@ -36,6 +41,10 @@ type Query {
|
||||
chapter(id: String!): Chapter!
|
||||
|
||||
|
||||
# Find comment by ID
|
||||
comment(id: String!): Comment!
|
||||
|
||||
|
||||
# Find all distinct tags used in stories
|
||||
tags: [Tag!]!
|
||||
|
||||
@@ -54,6 +63,10 @@ type Query {
|
||||
files(filter: FilesFilter): [File!]!
|
||||
|
||||
|
||||
# Find changes
|
||||
changes(filter: ChangesFilter): [Change!]!
|
||||
|
||||
|
||||
# Get information about the token, useful for debugging.
|
||||
token: Token!
|
||||
}
|
||||
@@ -81,13 +94,29 @@ type Mutation {
|
||||
# Edit a chapter
|
||||
editChapter(input: ChapterEditInput!): Chapter!
|
||||
|
||||
# Move a chapter
|
||||
moveChapter(input: ChapterMoveInput!): Chapter!
|
||||
|
||||
# Remove a chapter
|
||||
removeChapter(input: ChapterRemoveInput!): Chapter!
|
||||
|
||||
|
||||
"Add a comment to a chapter."
|
||||
addComment(input: CommentAddInput!): Comment!
|
||||
|
||||
"Edit a comment in a chapter."
|
||||
editComment(input: CommentEditInput!): Comment!
|
||||
|
||||
"Remove a comemnt in a chapter."
|
||||
removeComment(input: CommentRemoveInput!): Comment!
|
||||
|
||||
|
||||
# Add a new log
|
||||
addLog(input: LogAddInput!): Log!
|
||||
|
||||
# Import a log
|
||||
importLog(input: LogImportInput!): [Log!]!
|
||||
|
||||
# Edit a log
|
||||
editLog(input: LogEditInput!): Log!
|
||||
|
||||
@@ -131,6 +160,13 @@ type Mutation {
|
||||
editChannel(input: ChannelEditInput!): Channel!
|
||||
}
|
||||
|
||||
type Subscription {
|
||||
"""
|
||||
Changes subscribes to the changes matching the following keys.
|
||||
"""
|
||||
changes(keys: [ChangeKeyInput!]!): Change!
|
||||
}
|
||||
|
||||
# A Date represents a RFC3339 encoded date with up to millisecond precision.
|
||||
scalar Date
|
||||
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
"""
|
||||
A change represents a a change in the history.
|
||||
"""
|
||||
type Change {
|
||||
"A unique ID of the change."
|
||||
id: String!
|
||||
|
||||
"The model of the changed object."
|
||||
model: ChangeModel!
|
||||
|
||||
"The operation performed."
|
||||
op: String!
|
||||
|
||||
"The author that performed the change."
|
||||
author: String!
|
||||
|
||||
"Whether the change will be listed without a matching key."
|
||||
listed: Boolean!
|
||||
|
||||
"The keys indexing this change."
|
||||
keys: [ChangeKey!]!
|
||||
|
||||
"The date when the change happened."
|
||||
date: Date
|
||||
|
||||
"The changed objects."
|
||||
objects: [ChangeObject!]!
|
||||
}
|
||||
|
||||
union ChangeObject = Character | Channel | Log | Post | Story | Tag | Chapter | Comment
|
||||
|
||||
"""
|
||||
A change key is a key associated with a change, used for filtering
|
||||
and subscription. A log post edit has keys for both the log and post,
|
||||
making it suitable to use it to patch an active log file.
|
||||
"""
|
||||
type ChangeKey {
|
||||
"The model the key is for."
|
||||
model: ChangeModel!
|
||||
|
||||
"The model's id, or * if it's a key indicating a model list should be updated."
|
||||
id: String!
|
||||
}
|
||||
|
||||
"""
|
||||
See ChangeKey
|
||||
"""
|
||||
input ChangeKeyInput {
|
||||
"The model the key is for."
|
||||
model: ChangeModel!
|
||||
|
||||
"The model's id, or * if it's a key indicating a model list should be updated."
|
||||
id: String!
|
||||
}
|
||||
|
||||
"""
|
||||
A model related to the change.
|
||||
"""
|
||||
enum ChangeModel {
|
||||
Character
|
||||
Channel
|
||||
Log
|
||||
Post
|
||||
Story
|
||||
Tag
|
||||
Chapter
|
||||
}
|
||||
|
||||
"""
|
||||
Optional filter for the changes query.
|
||||
"""
|
||||
input ChangesFilter {
|
||||
"The keys to query for."
|
||||
keys: [ChangeKeyInput!]
|
||||
|
||||
"Only show changes more recent than this date."
|
||||
earliestDate: Date
|
||||
|
||||
"Limit the amount of results."
|
||||
limit: Int
|
||||
}
|
||||
@@ -20,6 +20,35 @@ type Chapter {
|
||||
|
||||
# The date of edit.
|
||||
editedDate: Date!
|
||||
|
||||
"The comment mode."
|
||||
commentMode: ChapterCommentMode!
|
||||
|
||||
"Whether the author has locket comments."
|
||||
commentsLocked: Boolean!
|
||||
|
||||
"A shorthand for checking whether a logged-in user can comment on this chapter."
|
||||
canComment: Boolean!
|
||||
|
||||
"Get all chapter comments."
|
||||
comments(limit: Int): [Comment!]!
|
||||
}
|
||||
|
||||
"""
|
||||
The possibles modes of comments for a chapter.
|
||||
"""
|
||||
enum ChapterCommentMode {
|
||||
"Comments are disabled and hidden."
|
||||
Disabled
|
||||
|
||||
"Comments should be shown as typical news article comments."
|
||||
Article
|
||||
|
||||
"Comments should be shown in a compact form suitable for many small messages."
|
||||
Chat
|
||||
|
||||
"Comments are going to be shown as omni-tool messages."
|
||||
Message
|
||||
}
|
||||
|
||||
# Input for addChapter mutation
|
||||
@@ -41,6 +70,9 @@ input ChapterAddInput {
|
||||
|
||||
# Optionally, assign a fictional date for the chapter
|
||||
fictionalDate: Date
|
||||
|
||||
"The comment mode for the chapter."
|
||||
commentMode: ChapterCommentMode
|
||||
}
|
||||
|
||||
# Input for editChapter mutation
|
||||
@@ -59,6 +91,21 @@ input ChapterEditInput {
|
||||
|
||||
# Remove the fictional date for a chapter
|
||||
clearFictionalDate: Boolean
|
||||
|
||||
"Set the comment mode for the chapter"
|
||||
commentMode: ChapterCommentMode
|
||||
|
||||
"Set whether the chapter comments are locked"
|
||||
commentsLocked: Boolean
|
||||
}
|
||||
|
||||
# Input for moveChapter mutation
|
||||
input ChapterMoveInput {
|
||||
# The chapter to move
|
||||
id: String!
|
||||
|
||||
# The story to move it to
|
||||
storyId: String!
|
||||
}
|
||||
|
||||
# Input for removeChapter mutation
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
"""
|
||||
A Comment represents a comment to a story chapter.
|
||||
"""
|
||||
type Comment {
|
||||
"A unique ID of the change."
|
||||
id: String!
|
||||
|
||||
"subject"
|
||||
subject: String!
|
||||
|
||||
"The chapter ID the comment belongs to."
|
||||
chapterId: String!
|
||||
|
||||
"The comment's author."
|
||||
author: String!
|
||||
|
||||
"The displayed name of the character. This may be the same as character.name, but it does not need to be."
|
||||
characterName: String!
|
||||
|
||||
"The character associated with the comment."
|
||||
character: Character
|
||||
|
||||
"The fictional (IC) date of the comment."
|
||||
fictionalDate: Date
|
||||
|
||||
"The date of creation."
|
||||
createdDate: Date!
|
||||
|
||||
"The date of the last edit."
|
||||
editedDate: Date!
|
||||
|
||||
"The markdown source of the comment."
|
||||
source: String!
|
||||
}
|
||||
|
||||
"""
|
||||
Input for the addComment mutation
|
||||
"""
|
||||
input CommentAddInput {
|
||||
"What chapter to comment on."
|
||||
chapterId: String!
|
||||
|
||||
"The markdown source of the comment."
|
||||
source: String!
|
||||
|
||||
"The name of the character associated with this comment."
|
||||
characterName: String!
|
||||
|
||||
"Optioanlly, a character ID."
|
||||
characterId: String
|
||||
|
||||
"Optional in-universe date of the comment."
|
||||
fictionalDate: Date
|
||||
|
||||
"Optional subject to add to the comment, only shown in some modes."
|
||||
subject: String
|
||||
}
|
||||
|
||||
"""
|
||||
Input for the editComment mutation
|
||||
"""
|
||||
input CommentEditInput {
|
||||
"What comment to edit."
|
||||
commentId: String!
|
||||
|
||||
"Change the markdown source."
|
||||
source: String
|
||||
|
||||
"Change the name of the character associated with this comment."
|
||||
characterName: String
|
||||
|
||||
"Change the character assocaited with this comment."
|
||||
characterId: String
|
||||
|
||||
"Change the optional in-universe date of the comment."
|
||||
fictionalDate: Date
|
||||
|
||||
"Set to clear the fictional date."
|
||||
clearFictionalDate: Boolean
|
||||
|
||||
"Change the optional subject for the comment, only shown in some modes."
|
||||
subject: String
|
||||
}
|
||||
|
||||
"""
|
||||
Input for the removeComment mutation
|
||||
"""
|
||||
input CommentRemoveInput {
|
||||
"What comment to edit."
|
||||
commentId: String!
|
||||
}
|
||||
@@ -100,4 +100,36 @@ input LogEditInput {
|
||||
input LogRemoveInput {
|
||||
# The id of the log to remove
|
||||
id: String!
|
||||
}
|
||||
|
||||
"""
|
||||
Input for importLog mutation.
|
||||
"""
|
||||
input LogImportInput {
|
||||
"The channel of the log, alwyas required."
|
||||
channelName: String!
|
||||
|
||||
"Which importer to use."
|
||||
importer: LogImporter!
|
||||
|
||||
"The timezone of the import, all log and post dates will be treated as it. It will be UTC if none is specified."
|
||||
timezone: String
|
||||
|
||||
"The date of the log, if not provided in the log body."
|
||||
date: Date
|
||||
|
||||
"The log body itself."
|
||||
data: String!
|
||||
}
|
||||
|
||||
enum LogImporter {
|
||||
"""
|
||||
mIRC-like: This importer parses logs that copied from mIRC posts without spaces.
|
||||
"""
|
||||
MircLike
|
||||
|
||||
"""
|
||||
Forum log: This importer parses the format on the forum. The displayed log, not the post source.
|
||||
"""
|
||||
ForumLog
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
"""
|
||||
An unknown nick is a nick found by the logbot that's not associated with
|
||||
any characters. Many are going to be false positives, but most should be
|
||||
useful in pointing out which characters are missing.
|
||||
"""
|
||||
type UnknownNick {
|
||||
"The IRC nick that's unknown."
|
||||
nick: String!
|
||||
|
||||
"The amount of logs that contains the unknown nick."
|
||||
score: Int!
|
||||
}
|
||||
|
||||
"""
|
||||
Input for the unknownNicks query.
|
||||
"""
|
||||
input UnknownNicksFilter {
|
||||
"The highest amount of unknown nicks to return."
|
||||
limit: Int!
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graph2/input"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
)
|
||||
|
||||
type changeResolver struct{}
|
||||
|
||||
func (r *changeResolver) Objects(ctx context.Context, obj *models.Change) ([]input.ChangeObject, error) {
|
||||
objects := obj.Objects()
|
||||
|
||||
results := make([]input.ChangeObject, 0, len(objects))
|
||||
for _, object := range objects {
|
||||
if cObj, ok := object.(input.ChangeObject); ok {
|
||||
results = append(results, cObj)
|
||||
} else {
|
||||
log.Printf("Warning: %T is not a change object", object)
|
||||
}
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// ChangeResolver is a resolver
|
||||
var ChangeResolver changeResolver
|
||||
@@ -2,9 +2,11 @@ package types
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"git.aiterp.net/rpdata/api/models/comments"
|
||||
)
|
||||
|
||||
type chapterResolver struct{}
|
||||
@@ -17,5 +19,22 @@ func (r *chapterResolver) FictionalDate(ctx context.Context, chapter *models.Cha
|
||||
return &chapter.FictionalDate, nil
|
||||
}
|
||||
|
||||
func (r *chapterResolver) Comments(ctx context.Context, chapter *models.Chapter, limit *int) ([]models.Comment, error) {
|
||||
limitValue := 0
|
||||
if limit != nil {
|
||||
if *limit < 0 {
|
||||
return nil, errors.New("Limit cannot be negative")
|
||||
}
|
||||
|
||||
limitValue = *limit
|
||||
}
|
||||
|
||||
if !chapter.CommentMode.IsEnabled() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return comments.ListChapterID(chapter.ID, limitValue)
|
||||
}
|
||||
|
||||
// ChapterResolver is a resolver
|
||||
var ChapterResolver chapterResolver
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/internal/loader"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
)
|
||||
|
||||
type commentResolver struct{}
|
||||
|
||||
func (r *commentResolver) Character(ctx context.Context, obj *models.Comment) (*models.Character, error) {
|
||||
if obj.CharacterID == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
loader := loader.FromContext(ctx)
|
||||
if loader == nil {
|
||||
return nil, errors.New("no loader")
|
||||
}
|
||||
|
||||
character, err := loader.Character("id", obj.CharacterID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &character, nil
|
||||
}
|
||||
|
||||
func (r *commentResolver) FictionalDate(ctx context.Context, obj *models.Comment) (*time.Time, error) {
|
||||
if obj.FictionalDate.IsZero() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return &obj.FictionalDate, nil
|
||||
}
|
||||
|
||||
// CommentResolver is a resolver
|
||||
var CommentResolver commentResolver
|
||||
@@ -1,59 +0,0 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/channel"
|
||||
)
|
||||
|
||||
// ChannelAddArgs is input for the addChannel mutation
|
||||
type ChannelAddArgs struct {
|
||||
Input *struct {
|
||||
Name string
|
||||
Logged *bool
|
||||
Hub *bool
|
||||
EventName *string
|
||||
LocationName *string
|
||||
}
|
||||
}
|
||||
|
||||
// AddChannel resolves the addChannel mutation
|
||||
func (r *MutationResolver) AddChannel(ctx context.Context, args *ChannelAddArgs) (*types.ChannelResolver, error) {
|
||||
input := args.Input
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Permitted("channel.add") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
logged := input.Logged != nil && *input.Logged
|
||||
|
||||
hub := input.Hub != nil && *input.Hub
|
||||
|
||||
eventName := ""
|
||||
if input.EventName != nil {
|
||||
eventName = *input.EventName
|
||||
}
|
||||
|
||||
locationName := ""
|
||||
if input.LocationName != nil {
|
||||
locationName = *input.LocationName
|
||||
}
|
||||
|
||||
channel, err := channel.New(input.Name, logged, hub, eventName, locationName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Channel", "add", token.UserID, channel.Name, map[string]interface{}{
|
||||
"logged": channel.Logged,
|
||||
"hub": channel.Hub,
|
||||
"location": input.LocationName,
|
||||
"event": input.EventName,
|
||||
})
|
||||
|
||||
return &types.ChannelResolver{C: channel}, nil
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/story"
|
||||
)
|
||||
|
||||
// AddChapterArgs is args for the addChapter mutation
|
||||
type AddChapterArgs struct {
|
||||
Input *struct {
|
||||
StoryID string
|
||||
Title string
|
||||
Author *string
|
||||
Source string
|
||||
FictionalDate *string
|
||||
}
|
||||
}
|
||||
|
||||
// AddChapter resolves the addChapter mutation
|
||||
func (r *MutationResolver) AddChapter(ctx context.Context, args *AddChapterArgs) (*types.ChapterResolver, error) {
|
||||
input := args.Input
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Permitted("member", "chapter.add") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
story, err := story.FindID(input.StoryID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
author := token.UserID
|
||||
if input.Author != nil {
|
||||
author = *input.Author
|
||||
|
||||
if token.UserID != author && !token.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", token.UserID, chapter.ID, map[string]interface{}{
|
||||
"title": input.Title,
|
||||
"author": author,
|
||||
"fictionalDate": fictionalDate,
|
||||
})
|
||||
|
||||
return &types.ChapterResolver{C: chapter}, nil
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/character"
|
||||
"git.aiterp.net/rpdata/api/model/log"
|
||||
)
|
||||
|
||||
// AddCharacterInput is args for the addCharacter mutation
|
||||
type AddCharacterInput struct {
|
||||
Nick string
|
||||
Name string
|
||||
ShortName *string
|
||||
Author *string
|
||||
Description *string
|
||||
}
|
||||
|
||||
// AddCharacter resolves the addCharacter mutation
|
||||
func (r *MutationResolver) AddCharacter(ctx context.Context, args struct{ Input *AddCharacterInput }) (*types.CharacterResolver, error) {
|
||||
input := args.Input
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Permitted("member", "character.add") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
nick := input.Nick
|
||||
|
||||
name := input.Name
|
||||
|
||||
shortName := ""
|
||||
if input.ShortName != nil {
|
||||
shortName = *input.ShortName
|
||||
} else {
|
||||
shortName = strings.SplitN(input.Name, " ", 2)[0]
|
||||
}
|
||||
|
||||
author := token.UserID
|
||||
if input.Author != nil {
|
||||
author = *input.Author
|
||||
|
||||
if author != token.UserID && !token.Permitted("character.add") {
|
||||
return nil, ErrPermissionDenied
|
||||
}
|
||||
}
|
||||
|
||||
description := ""
|
||||
if input.Description != nil {
|
||||
description = *input.Description
|
||||
}
|
||||
|
||||
character, err := character.New(nick, name, shortName, author, description)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Character", "add", token.UserID, character.ID, map[string]interface{}{
|
||||
"name": character.Name,
|
||||
"nick": character.Nicks[0],
|
||||
"author": character.Author,
|
||||
})
|
||||
|
||||
log.ScheduleCharacterUpdate()
|
||||
|
||||
return &types.CharacterResolver{C: character}, nil
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/character"
|
||||
"git.aiterp.net/rpdata/api/model/log"
|
||||
)
|
||||
|
||||
// AddCharacterNickInput is args for the addCharacterNick mutation
|
||||
type AddCharacterNickInput struct {
|
||||
ID string
|
||||
Nick string
|
||||
}
|
||||
|
||||
// AddCharacterNick resolves the addCharacterNick mutation
|
||||
func (r *MutationResolver) AddCharacterNick(ctx context.Context, args struct{ Input *AddCharacterNickInput }) (*types.CharacterResolver, error) {
|
||||
input := args.Input
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Permitted("member") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
character, err := character.FindID(input.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if character.Author != token.UserID && !token.Permitted("character.edit") {
|
||||
return nil, ErrPermissionDenied
|
||||
}
|
||||
|
||||
err = character.AddNick(input.Nick)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Character", "add.nick", token.UserID, character.ID, map[string]interface{}{
|
||||
"nick": input.Nick,
|
||||
})
|
||||
|
||||
log.ScheduleCharacterUpdate()
|
||||
|
||||
return &types.CharacterResolver{C: character}, nil
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/log"
|
||||
)
|
||||
|
||||
// LogAddArgs is args for the addLog mutation
|
||||
type LogAddArgs struct {
|
||||
Input *struct {
|
||||
Date string
|
||||
Channel string
|
||||
Title *string
|
||||
Open *bool
|
||||
Event *string
|
||||
Description *string
|
||||
}
|
||||
}
|
||||
|
||||
// AddLog resolves the addLog mutation
|
||||
func (r *MutationResolver) AddLog(ctx context.Context, args *LogAddArgs) (*types.LogResolver, error) {
|
||||
input := args.Input
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Permitted("log.add") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
date, err := time.Parse(time.RFC3339Nano, args.Input.Date)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
title := ""
|
||||
if input.Title != nil {
|
||||
title = *input.Title
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Log", "add", token.UserID, log.ID, map[string]interface{}{
|
||||
"channel": log.Channel,
|
||||
"title": log.Title,
|
||||
"event": log.Event,
|
||||
"description": log.Description,
|
||||
"open": log.Open,
|
||||
})
|
||||
|
||||
return &types.LogResolver{L: log}, nil
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/log"
|
||||
)
|
||||
|
||||
// PostAddArgs is args for addPost mutation
|
||||
type PostAddArgs struct {
|
||||
Input *struct {
|
||||
LogID string
|
||||
Time string
|
||||
Kind string
|
||||
Nick string
|
||||
Text string
|
||||
}
|
||||
}
|
||||
|
||||
// AddPost resolves the addPost mutation
|
||||
func (r *MutationResolver) AddPost(ctx context.Context, args *PostAddArgs) (*types.PostResolver, error) {
|
||||
input := args.Input
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Permitted("post.add") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
postTime, err := time.Parse(time.RFC3339Nano, input.Time)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log, err := log.FindID(input.LogID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
post, err := log.NewPost(postTime, input.Kind, input.Nick, input.Text)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Post", "add", token.UserID, post.ID, map[string]interface{}{
|
||||
"logId": post.LogID,
|
||||
"time": post.Time,
|
||||
"kind": post.Kind,
|
||||
"nick": post.Nick,
|
||||
"text": post.Text,
|
||||
"position": post.Position,
|
||||
})
|
||||
|
||||
go log.UpdateCharacters()
|
||||
|
||||
return &types.PostResolver{P: post}, nil
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/story"
|
||||
)
|
||||
|
||||
// StoryAddArgs is args for the addStory mutation
|
||||
type StoryAddArgs struct {
|
||||
Input *struct {
|
||||
Name string
|
||||
Category string
|
||||
Author *string
|
||||
Open *bool
|
||||
Listed *bool
|
||||
FictionalDate *string
|
||||
Tags *[]struct {
|
||||
Kind string
|
||||
Name string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AddStory resolves the addStory mutation
|
||||
func (r *MutationResolver) AddStory(ctx context.Context, args *StoryAddArgs) (*types.StoryResolver, error) {
|
||||
input := args.Input
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Permitted("member", "story.add") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
author := token.UserID
|
||||
if input.Author != nil {
|
||||
author = *input.Author
|
||||
|
||||
if token.UserID != author && !token.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", token.UserID, story.ID, map[string]interface{}{
|
||||
"name": input.Name,
|
||||
"category": input.Category,
|
||||
"author": input.Author,
|
||||
})
|
||||
|
||||
return &types.StoryResolver{S: story}, nil
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/story"
|
||||
)
|
||||
|
||||
// StoryTagAddArgs is args for the addStoryTag mutation
|
||||
type StoryTagAddArgs struct {
|
||||
Input *struct {
|
||||
ID string
|
||||
Tag struct {
|
||||
Kind string
|
||||
Name string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AddStoryTag resolves the addStoryTag mutation
|
||||
func (r *MutationResolver) AddStoryTag(ctx context.Context, args *StoryTagAddArgs) (*types.StoryResolver, error) {
|
||||
input := args.Input
|
||||
tag := story.Tag{Kind: input.Tag.Kind, Name: input.Tag.Name}
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Permitted("member", "story.edit") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
story, err := story.FindID(input.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if story.Author != token.UserID && !token.Permitted("story.edit") {
|
||||
return nil, ErrPermissionDenied
|
||||
}
|
||||
|
||||
err = story.AddTag(tag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Story", "add.tag", token.UserID, story.ID, map[string]interface{}{
|
||||
"kind": tag.Kind,
|
||||
"name": tag.Name,
|
||||
})
|
||||
|
||||
return &types.StoryResolver{S: story}, nil
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/channel"
|
||||
)
|
||||
|
||||
// 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
|
||||
func (r *MutationResolver) EditChannel(ctx context.Context, args *ChannelEditArgs) (*types.ChannelResolver, error) {
|
||||
input := args.Input
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Permitted("channel.edit") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
channel, err := channel.FindName(input.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = channel.Edit(input.Logged, input.Hub, input.EventName, input.LocationName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Channel", "edit", token.UserID, channel.Name, map[string]interface{}{
|
||||
"logged": input.Logged,
|
||||
"hub": input.Hub,
|
||||
"location": input.LocationName,
|
||||
"event": input.EventName,
|
||||
})
|
||||
|
||||
return &types.ChannelResolver{C: channel}, nil
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/story"
|
||||
)
|
||||
|
||||
// EditChapterArgs is args for the editChapter mutation
|
||||
type EditChapterArgs struct {
|
||||
Input *struct {
|
||||
ID string
|
||||
Title *string
|
||||
Source *string
|
||||
FictionalDate *string
|
||||
}
|
||||
}
|
||||
|
||||
// EditChapter resolves the editChapter mutation
|
||||
func (r *MutationResolver) EditChapter(ctx context.Context, args *EditChapterArgs) (*types.ChapterResolver, error) {
|
||||
input := args.Input
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Permitted("member", "chapter.edit") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
chapter, err := story.FindChapterID(input.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if chapter.Author != token.UserID && !token.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", token.UserID, chapter.ID, map[string]interface{}{
|
||||
"title": input.Title,
|
||||
"source": input.Source,
|
||||
"fictionalDate": fictionalDate,
|
||||
})
|
||||
|
||||
return &types.ChapterResolver{C: chapter}, nil
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/character"
|
||||
)
|
||||
|
||||
// CharacterEditInput is args for mutation addCharacterNick/removeCharacterNick
|
||||
type CharacterEditInput struct {
|
||||
ID string
|
||||
Name *string
|
||||
ShortName *string
|
||||
Description *string
|
||||
}
|
||||
|
||||
// EditCharacter resolves the editCharacter mutation
|
||||
func (r *MutationResolver) EditCharacter(ctx context.Context, args struct{ Input *CharacterEditInput }) (*types.CharacterResolver, error) {
|
||||
input := args.Input
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Permitted("member") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
character, err := character.FindID(input.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if character.Author != token.UserID && !token.Permitted("character.edit") {
|
||||
return nil, ErrPermissionDenied
|
||||
}
|
||||
|
||||
name := ""
|
||||
if input.Name != nil {
|
||||
name = *input.Name
|
||||
}
|
||||
|
||||
shortName := ""
|
||||
if input.ShortName != nil {
|
||||
shortName = *input.ShortName
|
||||
}
|
||||
|
||||
description := ""
|
||||
if input.Description != nil {
|
||||
description = *input.Description
|
||||
}
|
||||
|
||||
err = character.Edit(name, shortName, description)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Character", "edit", token.UserID, character.ID, map[string]interface{}{
|
||||
"name": character.Name,
|
||||
"shortName": character.ShortName,
|
||||
"description": character.Description,
|
||||
})
|
||||
|
||||
return &types.CharacterResolver{C: character}, nil
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/model/file"
|
||||
)
|
||||
|
||||
// FileEditArgs is args for the editFile mutation
|
||||
type FileEditArgs struct {
|
||||
Input *struct {
|
||||
ID string
|
||||
Name *string
|
||||
Public *bool
|
||||
}
|
||||
}
|
||||
|
||||
// EditFile resolves the editFile mutation
|
||||
func (r *MutationResolver) EditFile(ctx context.Context, args *FileEditArgs) (*types.FileResolver, error) {
|
||||
input := args.Input
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Permitted("member") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
file, err := file.FindID(input.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if file.Author != token.UserID && !token.Permitted("file.edit") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
err = file.Edit(input.Name, input.Public)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.FileResolver{F: file}, nil
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/log"
|
||||
)
|
||||
|
||||
// LogEditArgs is args for the editLog mutation
|
||||
type LogEditArgs struct {
|
||||
Input *struct {
|
||||
ID string
|
||||
Title *string
|
||||
Event *string
|
||||
Description *string
|
||||
Open *bool
|
||||
}
|
||||
}
|
||||
|
||||
// EditLog resolves the editLog mutation
|
||||
func (r *MutationResolver) EditLog(ctx context.Context, args *LogEditArgs) (*types.LogResolver, error) {
|
||||
input := args.Input
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Permitted("log.edit") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
log, err := log.FindID(input.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = log.Edit(input.Title, input.Event, input.Description, input.Open)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Log", "edit", token.UserID, log.ID, map[string]interface{}{
|
||||
"channel": log.Channel,
|
||||
"title": input.Title,
|
||||
"event": input.Event,
|
||||
"description": input.Description,
|
||||
"open": input.Open,
|
||||
})
|
||||
|
||||
return &types.LogResolver{L: log}, nil
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/log"
|
||||
)
|
||||
|
||||
// PostEditArgs is args for the editPost mutation
|
||||
type PostEditArgs struct {
|
||||
Input *struct {
|
||||
ID string
|
||||
Time *string
|
||||
Kind *string
|
||||
Nick *string
|
||||
Text *string
|
||||
}
|
||||
}
|
||||
|
||||
// EditPost resolves the editPost mutation
|
||||
func (r *MutationResolver) EditPost(ctx context.Context, args *PostEditArgs) (*types.PostResolver, error) {
|
||||
input := args.Input
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Permitted("post.edit") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
postTime := (*time.Time)(nil)
|
||||
if args.Input.Time != nil {
|
||||
t, err := time.Parse(time.RFC3339Nano, *input.Time)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
postTime = &t
|
||||
}
|
||||
|
||||
post, err := log.FindPostID(input.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = post.Edit(postTime, input.Kind, input.Nick, input.Text)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Post", "edit", token.UserID, post.ID, map[string]interface{}{
|
||||
"time": postTime,
|
||||
"kind": input.Kind,
|
||||
"nick": input.Nick,
|
||||
"text": input.Text,
|
||||
})
|
||||
|
||||
go log.UpdateCharacters(post.LogID)
|
||||
|
||||
return &types.PostResolver{P: post}, nil
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/story"
|
||||
)
|
||||
|
||||
// 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 resolves the editStory mutation
|
||||
func (r *MutationResolver) EditStory(ctx context.Context, args *StoryEditArgs) (*types.StoryResolver, error) {
|
||||
input := args.Input
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Permitted("member", "story.edit") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
story, err := story.FindID(input.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if story.Author != token.UserID && !token.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", token.UserID, story.ID, map[string]interface{}{
|
||||
"name": input.Name,
|
||||
"category": input.Category,
|
||||
"author": input.Author,
|
||||
"open": input.Open,
|
||||
"listed": input.Listed,
|
||||
"fictionalDate": input.FictionalDate,
|
||||
})
|
||||
|
||||
return &types.StoryResolver{S: story}, nil
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/log"
|
||||
)
|
||||
|
||||
// PostMoveArgs is args for movePost mutation
|
||||
type PostMoveArgs struct {
|
||||
Input *struct {
|
||||
ID string
|
||||
ToPosition int32
|
||||
}
|
||||
}
|
||||
|
||||
// MovePost resolves the movePost mutation
|
||||
func (r *MutationResolver) MovePost(ctx context.Context, args *PostMoveArgs) (*types.PostResolver, error) {
|
||||
input := args.Input
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Permitted("post.move") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
post, err := log.FindPostID(input.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = post.Move(int(input.ToPosition))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Post", "move", token.UserID, post.ID, map[string]interface{}{
|
||||
"logId": post.LogID,
|
||||
"targetIndex": input.ToPosition,
|
||||
})
|
||||
|
||||
return &types.PostResolver{P: post}, nil
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/channel"
|
||||
)
|
||||
|
||||
// RemoveChannelArgs is args for removeChannels mutation
|
||||
type RemoveChannelArgs struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
// RemoveChannel resolves the editChannel mutation
|
||||
func (r *MutationResolver) RemoveChannel(ctx context.Context, args RemoveChannelArgs) (*types.ChannelResolver, error) {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Permitted("channel.remove") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
channel, err := channel.FindName(args.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = channel.Remove()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Channel", "remove", token.UserID, channel.Name, nil)
|
||||
|
||||
return &types.ChannelResolver{C: channel}, nil
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/story"
|
||||
)
|
||||
|
||||
// RemoveChapterArgs is args for the addChapter mutation
|
||||
type RemoveChapterArgs struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
// RemoveChapter resolves the removeChapter mutation
|
||||
func (r *MutationResolver) RemoveChapter(ctx context.Context, args *RemoveChapterArgs) (*types.ChapterResolver, error) {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.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", token.UserID, chapter.ID, nil)
|
||||
|
||||
return &types.ChapterResolver{C: chapter}, nil
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/character"
|
||||
)
|
||||
|
||||
// RemoveCharacterArgs is args for the removeCharacter mutation
|
||||
type RemoveCharacterArgs struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
// RemoveCharacter resolves the removeCharacter mutation
|
||||
func (r *MutationResolver) RemoveCharacter(ctx context.Context, args RemoveCharacterArgs) (*types.CharacterResolver, error) {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Permitted("member") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
character, err := character.FindID(args.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if character.Author != token.UserID && !token.Permitted("character.remove") {
|
||||
return nil, ErrPermissionDenied
|
||||
}
|
||||
|
||||
err = character.Remove()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Character", "remove", token.UserID, character.ID, map[string]interface{}{
|
||||
"name": character.Name,
|
||||
"author": character.Author,
|
||||
"nicks": character.Nicks,
|
||||
})
|
||||
|
||||
return &types.CharacterResolver{C: character}, nil
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/character"
|
||||
"git.aiterp.net/rpdata/api/model/log"
|
||||
)
|
||||
|
||||
// RemoveCharacterNick is args for the addCharacterNick mutation
|
||||
type RemoveCharacterNick struct {
|
||||
ID string
|
||||
Nick string
|
||||
}
|
||||
|
||||
// RemoveCharacterNick resolves the removeCharacterNick mutation
|
||||
func (r *MutationResolver) RemoveCharacterNick(ctx context.Context, args struct{ Input *RemoveCharacterNick }) (*types.CharacterResolver, error) {
|
||||
input := args.Input
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Permitted("member") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
character, err := character.FindID(input.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if character.Author != token.UserID && !token.Permitted("character.edit") {
|
||||
return nil, ErrPermissionDenied
|
||||
}
|
||||
|
||||
err = character.RemoveNick(input.Nick)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Character", "remove.nick", token.UserID, character.ID, map[string]interface{}{
|
||||
"nick": input.Nick,
|
||||
})
|
||||
|
||||
log.ScheduleCharacterUpdate()
|
||||
|
||||
return &types.CharacterResolver{C: character}, nil
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/model/file"
|
||||
)
|
||||
|
||||
// RemoveFileArgs is an arg
|
||||
type RemoveFileArgs struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
// RemoveFile resolves the removeFIle mutation
|
||||
func (r *MutationResolver) RemoveFile(ctx context.Context, args *RemoveFileArgs) (*types.FileResolver, error) {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Permitted("member") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
file, err := file.FindID(args.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if file.Author != token.UserID && !token.Permitted("file.remove") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
err = file.Delete()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.FileResolver{F: file}, nil
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/log"
|
||||
)
|
||||
|
||||
// RemoveLogArgs is args for the removeLog mutation
|
||||
type RemoveLogArgs struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
// RemoveLog resolves the removeLog mutation
|
||||
func (r *MutationResolver) RemoveLog(ctx context.Context, args *RemoveLogArgs) (*types.LogResolver, error) {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Permitted("log.remove") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
log, err := log.FindID(args.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = log.Remove()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Log", "remove", token.UserID, log.ID, nil)
|
||||
|
||||
return &types.LogResolver{L: log}, nil
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/log"
|
||||
)
|
||||
|
||||
// PostRemoveArgs is args for the removePost mutation
|
||||
type PostRemoveArgs struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
// RemovePost resolves the removePost mutation
|
||||
func (r *MutationResolver) RemovePost(ctx context.Context, args PostRemoveArgs) (*types.PostResolver, error) {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Permitted("post.remove") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
post, err := log.RemovePost(args.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Post", "remove", token.UserID, post.ID, map[string]interface{}{
|
||||
"logId": post.LogID,
|
||||
})
|
||||
|
||||
go log.UpdateCharacters(post.LogID)
|
||||
|
||||
return &types.PostResolver{P: post}, nil
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/story"
|
||||
)
|
||||
|
||||
// StoryRemoveArgs is args for the removeStory mutation
|
||||
type StoryRemoveArgs struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
// RemoveStory resolves the removeStory mutation
|
||||
func (r *MutationResolver) RemoveStory(ctx context.Context, args *StoryRemoveArgs) (*types.StoryResolver, error) {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Permitted("member", "story.edit") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
story, err := story.FindID(args.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if story.Author != token.UserID && !token.Permitted("story.remove") {
|
||||
return nil, ErrPermissionDenied
|
||||
}
|
||||
|
||||
err = story.Remove()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Story", "remove", token.UserID, story.ID, nil)
|
||||
|
||||
return &types.StoryResolver{S: story}, nil
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package mutations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/story"
|
||||
)
|
||||
|
||||
// StoryTagRemoveArgs is args for the removeStoryTag mutation
|
||||
type StoryTagRemoveArgs struct {
|
||||
Input *struct {
|
||||
ID string
|
||||
Tag struct {
|
||||
Kind string
|
||||
Name string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveStoryTag resolves the removeStoryTag mutation
|
||||
func (r *MutationResolver) RemoveStoryTag(ctx context.Context, args *StoryTagRemoveArgs) (*types.StoryResolver, error) {
|
||||
input := args.Input
|
||||
tag := story.Tag{Kind: input.Tag.Kind, Name: input.Tag.Name}
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Permitted("member", "story.edit") {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
story, err := story.FindID(input.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if story.Author != token.UserID && !token.Permitted("story.edit") {
|
||||
return nil, ErrPermissionDenied
|
||||
}
|
||||
|
||||
err = story.RemoveTag(tag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go change.Submit("Story", "remove.tag", token.UserID, story.ID, map[string]interface{}{
|
||||
"kind": tag.Kind,
|
||||
"name": tag.Name,
|
||||
})
|
||||
|
||||
return &types.StoryResolver{S: story}, nil
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
// Package mutations contains resolvers for individual GrahpQL mutations. They were previously mixed in with one another, but that caused
|
||||
// difficulty seeking out specific mutations.
|
||||
package mutations
|
||||
|
||||
import "errors"
|
||||
|
||||
// MutationResolver is a resolver for all mutations. The stuttery name is due to being embedded in the root resolver.
|
||||
type MutationResolver struct{}
|
||||
|
||||
// ErrUnauthorized is when a guest acts like they own the place
|
||||
var ErrUnauthorized = errors.New("Unauthorized")
|
||||
|
||||
// ErrPermissionDenied is returned when users act above their station
|
||||
var ErrPermissionDenied = errors.New("Permission denied")
|
||||
@@ -1,23 +0,0 @@
|
||||
package queries
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/model/channel"
|
||||
)
|
||||
|
||||
// ChannelArgs is args for channel query
|
||||
type ChannelArgs struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
// Channel resolves the channel query
|
||||
func (r *QueryResolver) Channel(ctx context.Context, args *ChannelArgs) (*types.ChannelResolver, error) {
|
||||
channel, err := channel.FindName(args.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.ChannelResolver{C: channel}, nil
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package queries
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/model/channel"
|
||||
)
|
||||
|
||||
// ChannelsArgs is args for channel query
|
||||
type ChannelsArgs struct {
|
||||
Filter *struct {
|
||||
Logged *bool
|
||||
EventName *string
|
||||
LocationName *string
|
||||
}
|
||||
}
|
||||
|
||||
// Channels resolves the channels query
|
||||
func (r *QueryResolver) Channels(ctx context.Context, args *ChannelsArgs) ([]*types.ChannelResolver, error) {
|
||||
var channels []channel.Channel
|
||||
var err error
|
||||
|
||||
filter := args.Filter
|
||||
if filter != nil {
|
||||
channels, err = channel.List(filter.Logged, filter.EventName, filter.LocationName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
channels, err = channel.List(nil, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
resolvers := make([]*types.ChannelResolver, len(channels))
|
||||
for i := range channels {
|
||||
resolvers[i] = &types.ChannelResolver{C: channels[i]}
|
||||
}
|
||||
|
||||
return resolvers, nil
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package queries
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/model/story"
|
||||
)
|
||||
|
||||
// ChapterArgs is args for chapter query
|
||||
type ChapterArgs struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
// Chapter resolves the chapter query
|
||||
func (r *QueryResolver) Chapter(ctx context.Context, args *ChapterArgs) (*types.ChapterResolver, error) {
|
||||
chapter, err := story.FindChapterID(args.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.ChapterResolver{C: chapter}, nil
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package queries
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/model/character"
|
||||
)
|
||||
|
||||
// ErrIncorrectArguments is returned by character query.
|
||||
var ErrIncorrectArguments = errors.New("You can only query character by ID or Nick")
|
||||
|
||||
// CharacterArgs is args for character query
|
||||
type CharacterArgs struct {
|
||||
ID *string
|
||||
Nick *string
|
||||
}
|
||||
|
||||
// Character resolves the character query
|
||||
func (r *QueryResolver) Character(ctx context.Context, args *CharacterArgs) (*types.CharacterResolver, error) {
|
||||
var char character.Character
|
||||
var err error
|
||||
|
||||
if args.Nick != nil && args.ID != nil {
|
||||
return nil, ErrIncorrectArguments
|
||||
}
|
||||
|
||||
switch {
|
||||
case args.ID != nil:
|
||||
char, err = character.FindID(*args.ID)
|
||||
case args.Nick != nil:
|
||||
char, err = character.FindNick(*args.Nick)
|
||||
default:
|
||||
err = ErrIncorrectArguments
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.CharacterResolver{C: char}, nil
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
package queries
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/model/character"
|
||||
)
|
||||
|
||||
// CharactersArgs is args for characters query
|
||||
type CharactersArgs struct {
|
||||
Filter *struct {
|
||||
IDs *[]string
|
||||
Nicks *[]string
|
||||
Names *[]string
|
||||
Author *string
|
||||
Search *string
|
||||
Logged *bool
|
||||
}
|
||||
}
|
||||
|
||||
// Characters resolves the characters query
|
||||
func (r *QueryResolver) Characters(ctx context.Context, args *CharactersArgs) ([]*types.CharacterResolver, error) {
|
||||
var characters []character.Character
|
||||
var err error
|
||||
|
||||
filter := args.Filter
|
||||
if filter != nil {
|
||||
var ids []string
|
||||
var nicks []string
|
||||
var names []string
|
||||
|
||||
if filter.IDs != nil {
|
||||
ids = *filter.IDs
|
||||
}
|
||||
if filter.Nicks != nil {
|
||||
nicks = *filter.Nicks
|
||||
}
|
||||
if filter.Names != nil {
|
||||
names = *filter.Names
|
||||
}
|
||||
|
||||
characters, err = character.ListFilter(ids, nicks, names, filter.Author, filter.Search, filter.Logged)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
characters, err = character.List()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
resolvers := make([]*types.CharacterResolver, 0, len(characters))
|
||||
for _, character := range characters {
|
||||
resolvers = append(resolvers, &types.CharacterResolver{C: character})
|
||||
}
|
||||
|
||||
return resolvers, nil
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package queries
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/model/file"
|
||||
)
|
||||
|
||||
// FileArgs is args for file query
|
||||
type FileArgs struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
// File resolves the file query
|
||||
func (r *QueryResolver) File(ctx context.Context, args *FileArgs) (*types.FileResolver, error) {
|
||||
file, err := file.FindID(args.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.FileResolver{F: file}, nil
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package queries
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/model/file"
|
||||
)
|
||||
|
||||
// FilesArgs is args for files query
|
||||
type FilesArgs struct {
|
||||
Filter *struct {
|
||||
Public *bool
|
||||
MimeTypes *[]string
|
||||
}
|
||||
}
|
||||
|
||||
// Files resolves the file query
|
||||
func (r *QueryResolver) Files(ctx context.Context, args *FilesArgs) ([]*types.FileResolver, error) {
|
||||
filter := args.Filter
|
||||
token := auth.TokenFromContext(ctx)
|
||||
|
||||
author := ""
|
||||
if token != nil {
|
||||
author = token.UserID
|
||||
}
|
||||
|
||||
mimeTypes := []string(nil)
|
||||
if filter.MimeTypes != nil {
|
||||
mimeTypes = *filter.MimeTypes
|
||||
}
|
||||
|
||||
files, err := file.List(author, filter.Public, mimeTypes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resolvers := make([]*types.FileResolver, len(files))
|
||||
for i := range files {
|
||||
resolvers[i] = &types.FileResolver{F: files[i]}
|
||||
}
|
||||
|
||||
return resolvers, nil
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package queries
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/model/log"
|
||||
)
|
||||
|
||||
// LogArgs is args for log query
|
||||
type LogArgs struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
// Log resolves the log query
|
||||
func (r *QueryResolver) Log(ctx context.Context, args *LogArgs) (*types.LogResolver, error) {
|
||||
log, err := log.FindID(args.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.LogResolver{L: log}, nil
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package queries
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/loader"
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/model/log"
|
||||
)
|
||||
|
||||
// LogsArgs is args for the logs query
|
||||
type LogsArgs struct {
|
||||
Filter *log.Filter
|
||||
}
|
||||
|
||||
// Logs resolves the logs query
|
||||
func (r *QueryResolver) Logs(ctx context.Context, args *LogsArgs) ([]*types.LogResolver, error) {
|
||||
var logs []log.Log
|
||||
|
||||
filter := args.Filter
|
||||
if filter == nil {
|
||||
filter = log.NewFilter().WithLimit(100)
|
||||
}
|
||||
|
||||
logs, err := log.List(filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(logs) >= 100 {
|
||||
loader := loader.FromContext(ctx)
|
||||
if loader == nil {
|
||||
return nil, errors.New("no loader")
|
||||
}
|
||||
|
||||
for _, log := range logs {
|
||||
loader.PrimeCharacters("id", log.CharacterIDs...)
|
||||
loader.PrimeChannels("name", log.Channel)
|
||||
}
|
||||
}
|
||||
|
||||
resolvers := make([]*types.LogResolver, len(logs))
|
||||
for i := range logs {
|
||||
resolvers[i] = &types.LogResolver{L: logs[i]}
|
||||
}
|
||||
|
||||
return resolvers, nil
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package queries
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/model/log"
|
||||
)
|
||||
|
||||
// PostArgs is args for the post query
|
||||
type PostArgs struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
// Post resolves the post query
|
||||
func (r *QueryResolver) Post(ctx context.Context, args *PostArgs) (*types.PostResolver, error) {
|
||||
post, err := log.FindPostID(args.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.PostResolver{P: post}, nil
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package queries
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/model/log"
|
||||
)
|
||||
|
||||
// PostsArgs is args for the posts query
|
||||
type PostsArgs struct {
|
||||
IDs []string
|
||||
}
|
||||
|
||||
// Posts resolves the posts query
|
||||
func (r *QueryResolver) Posts(ctx context.Context, args *PostsArgs) ([]*types.PostResolver, error) {
|
||||
posts, err := log.ListPostIDs(args.IDs...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resolvers := make([]*types.PostResolver, len(posts))
|
||||
for i := range resolvers {
|
||||
resolvers[i] = &types.PostResolver{P: posts[i]}
|
||||
}
|
||||
|
||||
return resolvers, nil
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
// Package queries contains resolvers for individual GrahpQL queries. They were previously mixed in with one another, but that caused
|
||||
// difficulty seeking out specific queries.
|
||||
package queries
|
||||
|
||||
import "errors"
|
||||
|
||||
// QueryResolver is a resolver for all queries. The stuttery name is due to being embedded in the root resolver.
|
||||
type QueryResolver struct{}
|
||||
|
||||
// ErrCannotResolve is returned when a resolver constructor is at its wit's end
|
||||
var ErrCannotResolve = errors.New("Cannot resolve due to invalid arguments")
|
||||
|
||||
// ErrUnauthorized is when a guest acts like they own the place
|
||||
var ErrUnauthorized = errors.New("Unauthorized")
|
||||
|
||||
// ErrPermissionDenied is returned when users act above their station
|
||||
var ErrPermissionDenied = errors.New("Permission denied")
|
||||
@@ -1,109 +0,0 @@
|
||||
package queries
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/model/story"
|
||||
)
|
||||
|
||||
// StoriesArg is args for stories query
|
||||
type StoriesArg struct {
|
||||
Filter *struct {
|
||||
Author *string
|
||||
Tags *[]struct {
|
||||
Kind string
|
||||
Name string
|
||||
}
|
||||
EarliestFictionalDate *string
|
||||
LatestFictionalDate *string
|
||||
Category *string
|
||||
Limit *int32
|
||||
Open *bool
|
||||
Unlisted *bool
|
||||
}
|
||||
}
|
||||
|
||||
// Stories resolves the stories query
|
||||
func (r *QueryResolver) Stories(ctx context.Context, args *StoriesArg) ([]*types.StoryResolver, error) {
|
||||
var err error
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
|
||||
filter := args.Filter
|
||||
|
||||
author := ""
|
||||
category := ""
|
||||
tags := make([]story.Tag, 0, 8)
|
||||
earliest := time.Time{}
|
||||
latest := time.Time{}
|
||||
unlisted := false
|
||||
open := (*bool)(nil)
|
||||
limit := 0
|
||||
|
||||
if filter != nil {
|
||||
if filter.Author != nil {
|
||||
author = *filter.Author
|
||||
}
|
||||
|
||||
if filter.Category != nil {
|
||||
category = *filter.Category
|
||||
}
|
||||
|
||||
if filter.Tags != nil {
|
||||
for _, tagInput := range *filter.Tags {
|
||||
tags = append(tags, story.Tag{
|
||||
Kind: tagInput.Kind,
|
||||
Name: tagInput.Name,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if filter.EarliestFictionalDate != nil {
|
||||
earliest, err = time.Parse(time.RFC3339Nano, *filter.EarliestFictionalDate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if filter.LatestFictionalDate != nil {
|
||||
latest, err = time.Parse(time.RFC3339Nano, *filter.LatestFictionalDate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if filter.Limit != nil {
|
||||
limit = int(*filter.Limit)
|
||||
}
|
||||
|
||||
unlisted = filter.Unlisted != nil && *filter.Unlisted == true
|
||||
if unlisted {
|
||||
if token == nil {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
if author != "" && author != token.UserID && !token.Permitted("story.unlisted") {
|
||||
return nil, ErrPermissionDenied
|
||||
}
|
||||
|
||||
author = token.UserID
|
||||
}
|
||||
|
||||
open = filter.Open
|
||||
}
|
||||
|
||||
stories, err := story.List(author, category, tags, earliest, latest, unlisted, open, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resolvers := make([]*types.StoryResolver, len(stories))
|
||||
for i, story := range stories {
|
||||
resolvers[i] = &types.StoryResolver{S: story}
|
||||
}
|
||||
|
||||
return resolvers, nil
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package queries
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/model/story"
|
||||
)
|
||||
|
||||
// StoryArgs is args for story query
|
||||
type StoryArgs struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
// Story resolves the story query
|
||||
func (r *QueryResolver) Story(ctx context.Context, args *StoryArgs) (*types.StoryResolver, error) {
|
||||
story, err := story.FindID(args.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.StoryResolver{S: story}, nil
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package queries
|
||||
|
||||
import (
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/types"
|
||||
"git.aiterp.net/rpdata/api/model/story"
|
||||
)
|
||||
|
||||
// Tags resolves the tags query
|
||||
func (r *QueryResolver) Tags() ([]*types.TagResolver, error) {
|
||||
tags, err := story.ListTags()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resolvers := make([]*types.TagResolver, len(tags))
|
||||
for i, tag := range tags {
|
||||
resolvers[i] = &types.TagResolver{T: tag}
|
||||
}
|
||||
|
||||
return resolvers, nil
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package resolver
|
||||
|
||||
import (
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/mutations"
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver/queries"
|
||||
)
|
||||
|
||||
// The Resolver combines the query and mutation resolvers from the subpackages.
|
||||
// This is the one to pass along with the schema.
|
||||
type Resolver struct {
|
||||
queries.QueryResolver
|
||||
mutations.MutationResolver
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package types
|
||||
|
||||
import "git.aiterp.net/rpdata/api/model/channel"
|
||||
|
||||
// ChannelResolver for the Channel graphql type
|
||||
type ChannelResolver struct{ C channel.Channel }
|
||||
|
||||
// Name resolves channel.name
|
||||
func (r *ChannelResolver) Name() string {
|
||||
return r.C.Name
|
||||
}
|
||||
|
||||
// Logged resolves channel.logged
|
||||
func (r *ChannelResolver) Logged() bool {
|
||||
return r.C.Logged
|
||||
}
|
||||
|
||||
// Hub resolves channel.hub
|
||||
func (r *ChannelResolver) Hub() bool {
|
||||
return r.C.Hub
|
||||
}
|
||||
|
||||
// EventName resolves channel.eventName
|
||||
func (r *ChannelResolver) EventName() *string {
|
||||
if r.C.Event == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &r.C.Event
|
||||
}
|
||||
|
||||
// LocationName resolves channel.locationName
|
||||
func (r *ChannelResolver) LocationName() *string {
|
||||
if r.C.Location == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &r.C.Location
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/model/story"
|
||||
)
|
||||
|
||||
// ChapterResolver for the Chapter graphql type
|
||||
type ChapterResolver struct{ C story.Chapter }
|
||||
|
||||
// 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)
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package types
|
||||
|
||||
import "git.aiterp.net/rpdata/api/model/character"
|
||||
|
||||
// CharacterResolver for the Character graphql type
|
||||
type CharacterResolver struct{ C character.Character }
|
||||
|
||||
// ID is a property resolver
|
||||
func (r *CharacterResolver) ID() string {
|
||||
return r.C.ID
|
||||
}
|
||||
|
||||
// Nick is a property resolver
|
||||
func (r *CharacterResolver) Nick() *string {
|
||||
if len(r.C.Nicks) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &r.C.Nicks[0]
|
||||
}
|
||||
|
||||
// Nicks is a property resolver
|
||||
func (r *CharacterResolver) Nicks() []string {
|
||||
return r.C.Nicks
|
||||
}
|
||||
|
||||
// Name is a property resolver
|
||||
func (r *CharacterResolver) Name() string {
|
||||
return r.C.Name
|
||||
}
|
||||
|
||||
// ShortName is a property resolver
|
||||
func (r *CharacterResolver) ShortName() string {
|
||||
return r.C.ShortName
|
||||
}
|
||||
|
||||
// Author is a property resolver
|
||||
func (r *CharacterResolver) Author() string {
|
||||
return r.C.Author
|
||||
}
|
||||
|
||||
// Description is a property resolver
|
||||
func (r *CharacterResolver) Description() string {
|
||||
return r.C.Description
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/model/file"
|
||||
)
|
||||
|
||||
// FileResolver for the File graphql type
|
||||
type FileResolver struct{ F file.File }
|
||||
|
||||
// ID resolves File.id
|
||||
func (f *FileResolver) ID() string {
|
||||
return f.F.ID
|
||||
}
|
||||
|
||||
// Author resolves File.author
|
||||
func (f *FileResolver) Author() string {
|
||||
return f.F.Author
|
||||
}
|
||||
|
||||
// Kind resolves File.kind
|
||||
func (f *FileResolver) Kind() string {
|
||||
return f.F.Kind
|
||||
}
|
||||
|
||||
// Time resolves File.time
|
||||
func (f *FileResolver) Time() string {
|
||||
return f.F.Time.Format(time.RFC3339Nano)
|
||||
}
|
||||
|
||||
// Public resolves File.public
|
||||
func (f *FileResolver) Public() bool {
|
||||
return f.F.Public
|
||||
}
|
||||
|
||||
// Name resolves File.name
|
||||
func (f *FileResolver) Name() string {
|
||||
return f.F.Name
|
||||
}
|
||||
|
||||
// MimeType resolves File.mimeType
|
||||
func (f *FileResolver) MimeType() string {
|
||||
return f.F.MimeType
|
||||
}
|
||||
|
||||
// Size resolves File.size
|
||||
func (f *FileResolver) Size() int32 {
|
||||
return int32(f.F.Size)
|
||||
}
|
||||
|
||||
// URL resolves File.url
|
||||
func (f *FileResolver) URL() *string {
|
||||
if f.F.URL == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &f.F.URL
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/loader"
|
||||
"git.aiterp.net/rpdata/api/model/log"
|
||||
)
|
||||
|
||||
// LogResolver for the Log graphql type
|
||||
type LogResolver struct{ L log.Log }
|
||||
|
||||
// ID resolves Log.id
|
||||
func (r *LogResolver) ID() string {
|
||||
return r.L.ID
|
||||
}
|
||||
|
||||
// ShortID resolves Log.shortId
|
||||
func (r *LogResolver) ShortID() string {
|
||||
return r.L.ShortID
|
||||
}
|
||||
|
||||
// Date resolves Log.date
|
||||
func (r *LogResolver) Date() string {
|
||||
return r.L.Date.Format(time.RFC3339Nano)
|
||||
}
|
||||
|
||||
// ChannelName resolves Log.channelName
|
||||
func (r *LogResolver) ChannelName() string {
|
||||
return r.L.Channel
|
||||
}
|
||||
|
||||
// Channel resolves Log.channe
|
||||
func (r *LogResolver) Channel(ctx context.Context) (*ChannelResolver, error) {
|
||||
loader := loader.FromContext(ctx)
|
||||
if loader == nil {
|
||||
return nil, errors.New("no loader")
|
||||
}
|
||||
|
||||
channel, err := loader.Channel("name", r.L.Channel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ChannelResolver{C: channel}, nil
|
||||
}
|
||||
|
||||
// Title resolves Log.title
|
||||
func (r *LogResolver) Title() string {
|
||||
return r.L.Title
|
||||
}
|
||||
|
||||
// Event resolves Log.event
|
||||
func (r *LogResolver) Event() string {
|
||||
return r.L.Event
|
||||
}
|
||||
|
||||
// Description resolves Log.description
|
||||
func (r *LogResolver) Description() string {
|
||||
return r.L.Description
|
||||
}
|
||||
|
||||
// Open resolves Log.open
|
||||
func (r *LogResolver) Open() bool {
|
||||
return r.L.Open
|
||||
}
|
||||
|
||||
// Characters resolves Log.characters
|
||||
func (r *LogResolver) Characters(ctx context.Context) ([]*CharacterResolver, error) {
|
||||
loader := loader.FromContext(ctx)
|
||||
if loader == nil {
|
||||
return nil, errors.New("no loader")
|
||||
}
|
||||
|
||||
chars, err := loader.Characters("id", r.L.CharacterIDs...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resolvers := make([]*CharacterResolver, 0, len(chars))
|
||||
for i := range chars {
|
||||
if chars[i].ID == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
resolvers = append(resolvers, &CharacterResolver{C: chars[i]})
|
||||
}
|
||||
|
||||
return resolvers, nil
|
||||
}
|
||||
|
||||
// Posts resolves Log.posts
|
||||
func (r *LogResolver) Posts(ctx context.Context, args *LogPostArgs) ([]*PostResolver, error) {
|
||||
var kinds []string
|
||||
if args.Kinds != nil {
|
||||
kinds = *args.Kinds
|
||||
}
|
||||
|
||||
posts, err := r.L.Posts(kinds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resolvers := make([]*PostResolver, len(posts))
|
||||
for i := range posts {
|
||||
resolvers[i] = &PostResolver{posts[i]}
|
||||
}
|
||||
|
||||
return resolvers, nil
|
||||
}
|
||||
|
||||
// LogPostArgs is an arg
|
||||
type LogPostArgs struct {
|
||||
Kinds *[]string
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/model/log"
|
||||
)
|
||||
|
||||
// PostResolver for the Post graphql type
|
||||
type PostResolver struct{ P log.Post }
|
||||
|
||||
// ID resolves Post.id
|
||||
func (r *PostResolver) ID() string {
|
||||
return r.P.ID
|
||||
}
|
||||
|
||||
// LogID resolves Post.logId
|
||||
func (r *PostResolver) LogID() string {
|
||||
return r.P.LogID
|
||||
}
|
||||
|
||||
// Time resolves Post.time
|
||||
func (r *PostResolver) Time() string {
|
||||
return r.P.Time.Format(time.RFC3339Nano)
|
||||
}
|
||||
|
||||
// Kind resolves Post.logId
|
||||
func (r *PostResolver) Kind() string {
|
||||
return r.P.Kind
|
||||
}
|
||||
|
||||
// Nick resolves Post.nick
|
||||
func (r *PostResolver) Nick() string {
|
||||
return r.P.Nick
|
||||
}
|
||||
|
||||
// Text resolves Post.text
|
||||
func (r *PostResolver) Text() string {
|
||||
return r.P.Text
|
||||
}
|
||||
|
||||
// Position resolves Post.text
|
||||
func (r *PostResolver) Position() int32 {
|
||||
return int32(r.P.Position)
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/model/story"
|
||||
)
|
||||
|
||||
// StoryResolver for the Story graphql type
|
||||
type StoryResolver struct{ S story.Story }
|
||||
|
||||
// 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)
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package types
|
||||
|
||||
import "git.aiterp.net/rpdata/api/model/story"
|
||||
|
||||
// TagResolver for the Tag graphql type
|
||||
type TagResolver struct{ T story.Tag }
|
||||
|
||||
// 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
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
# The Query type represents the read entry points into the API.
|
||||
type Query {
|
||||
# Find character by either an ID or a nick.
|
||||
character(id: String, nick: String): Character
|
||||
|
||||
# Find characters
|
||||
characters(filter: CharactersFilter): [Character!]!
|
||||
|
||||
|
||||
# Find channel by name
|
||||
channel(name: String!): Channel
|
||||
|
||||
# Find channels
|
||||
channels(filter: ChannelsFilter): [Channel!]!
|
||||
|
||||
|
||||
# Find log by ID
|
||||
log(id: String!): Log
|
||||
|
||||
# Find logs
|
||||
logs(filter: LogsFilter): [LogHeader!]!
|
||||
|
||||
|
||||
# Find post by ID.
|
||||
post(id: String!): Post
|
||||
|
||||
# Find posts by IDs. It's meant to allow other parts of the UI to link to a cluster of posts, e.g. for a room description for the
|
||||
# Mapp should it ever become a thing. This does not have a filter, since it's meant to be queried in the logs' response's selection set.
|
||||
posts(ids: [String!]!): [Post!]!
|
||||
|
||||
|
||||
# Find file by ID
|
||||
file(id: String!): File
|
||||
|
||||
# Find files
|
||||
files(filter: FilesFilter): [File!]!
|
||||
|
||||
|
||||
# Find story by ID
|
||||
story(id: String!): Story
|
||||
|
||||
# Find stories
|
||||
stories(filter: StoriesFilter): [Story!]!
|
||||
|
||||
|
||||
# Find story chapter by ID
|
||||
chapter(id: String!): Chapter
|
||||
|
||||
|
||||
# Find all distinct tags used in stories
|
||||
tags: [Tag!]!
|
||||
}
|
||||
|
||||
# The Mutation type represents write entry points into the API.
|
||||
type Mutation {
|
||||
# Add a new character
|
||||
addCharacter(input: CharacterAddInput!): Character!
|
||||
|
||||
# Add nick to character
|
||||
addCharacterNick(input: CharacterNickInput!): Character!
|
||||
|
||||
# Remove nick from character
|
||||
removeCharacterNick(input: CharacterNickInput!): Character!
|
||||
|
||||
# Edit character
|
||||
editCharacter(input: CharacterEditInput!): Character!
|
||||
|
||||
# Remove a character
|
||||
removeCharacter(id: String!): Character!
|
||||
|
||||
|
||||
# Add a channel
|
||||
addChannel(input: ChannelAddInput!): Channel!
|
||||
|
||||
# Edit a channel
|
||||
editChannel(input: ChannelEditInput!): Channel!
|
||||
|
||||
# Remove a channel
|
||||
removeChannel(name: String!): Channel!
|
||||
|
||||
|
||||
# Add a new log
|
||||
addLog(input: LogAddInput!): Log!
|
||||
|
||||
# Edit a log
|
||||
editLog(input: LogEditInput!): Log!
|
||||
|
||||
# Remove a log
|
||||
removeLog(id: String!): Log!
|
||||
|
||||
|
||||
# Add a post
|
||||
addPost(input: AddPostInput!): Post!
|
||||
|
||||
# Edit a post
|
||||
editPost(input: EditPostInput!): Post!
|
||||
|
||||
# Move a post
|
||||
movePost(input: MovePostInput!): Post!
|
||||
|
||||
# Remove a post
|
||||
removePost(id: String!): Post!
|
||||
|
||||
|
||||
# Edit a file
|
||||
editFile(input: EditFileInput!): File!
|
||||
|
||||
# Remove a 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!
|
||||
}
|
||||
|
||||
schema {
|
||||
query: Query
|
||||
mutation: Mutation
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package schema
|
||||
|
||||
//go:generate go-bindata -ignore=\.go -pkg=schema -o=bindata.go ./...
|
||||
|
||||
import "bytes"
|
||||
|
||||
// String gets the schema
|
||||
func String() string {
|
||||
buf := bytes.Buffer{}
|
||||
for _, name := range AssetNames() {
|
||||
b := MustAsset(name)
|
||||
buf.Write(b)
|
||||
|
||||
// Add a newline if the file does not end in a newline.
|
||||
if len(b) > 0 && b[len(b)-1] != '\n' {
|
||||
buf.WriteByte('\n')
|
||||
}
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
# A Change is a part of the history that can be used to keep clients up to date.
|
||||
type Change {
|
||||
id: Int!
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
# Information about an IRC channel
|
||||
type Channel {
|
||||
# The channel's name
|
||||
name: String!
|
||||
|
||||
# Whether the channel should be logged
|
||||
logged: Boolean!
|
||||
|
||||
# Whether the channel is a hub channel
|
||||
hub: Boolean!
|
||||
|
||||
# The event name, or `null` if none is specified
|
||||
eventName: String
|
||||
|
||||
# The location name, or `null` if none is specified
|
||||
locationName: String
|
||||
}
|
||||
|
||||
# Filters for the channels query
|
||||
input ChannelsFilter {
|
||||
# Filter to either logged or unlogged channels
|
||||
logged: Boolean
|
||||
|
||||
# Filter by event name
|
||||
eventName: String
|
||||
|
||||
# Filter by location name
|
||||
locationName: String
|
||||
}
|
||||
|
||||
input ChannelAddInput {
|
||||
# The channel's name
|
||||
name: String!
|
||||
|
||||
# Whether the channel should be logged
|
||||
logged: Boolean
|
||||
|
||||
# Whether the channel is a hub channel
|
||||
hub: Boolean
|
||||
|
||||
# The event name, or `null` if none is specified
|
||||
eventName: String
|
||||
|
||||
# The location name, or `null` if none is specified
|
||||
locationName: String
|
||||
}
|
||||
|
||||
input ChannelEditInput {
|
||||
# The channel's name
|
||||
name: String!
|
||||
|
||||
# Whether the channel should be logged
|
||||
logged: Boolean
|
||||
|
||||
# Whether the channel is a hub channel
|
||||
hub: Boolean
|
||||
|
||||
# The event name, or `null` if none is specified
|
||||
eventName: String
|
||||
|
||||
# The location name, or `null` if none is specified
|
||||
locationName: String
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
# A Chapter is the main content body of a story.
|
||||
type Chapter {
|
||||
# The chapter's ID
|
||||
id: String!
|
||||
|
||||
# The chapter's title
|
||||
title: String!
|
||||
|
||||
# The chapter's author
|
||||
author: String!
|
||||
|
||||
# The chapter's source
|
||||
source: String!
|
||||
|
||||
# When the chapter was posted initialy
|
||||
createdDate: String!
|
||||
|
||||
# The in-universe date and time of the chapter
|
||||
fictionalDate: String!
|
||||
|
||||
# The date of edit.
|
||||
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
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
# A Character represents an RP character
|
||||
type Character {
|
||||
# A unique identifier for the character
|
||||
id: String!
|
||||
|
||||
# The primary IRC nick belonging to the character
|
||||
nick: String
|
||||
|
||||
# All IRC nicks associated with this character
|
||||
nicks: [String!]!
|
||||
|
||||
# The character's author
|
||||
author: String!
|
||||
|
||||
# The character's name
|
||||
name: String!
|
||||
|
||||
# The name to display when space is scarce, usually the first/given name
|
||||
shortName: String!
|
||||
|
||||
# A short description of the character
|
||||
description: String!
|
||||
}
|
||||
|
||||
# Filter for characters query
|
||||
input CharactersFilter {
|
||||
# Filter by character IDs
|
||||
ids: [String!]
|
||||
|
||||
# Filter by nicks
|
||||
nicks: [String!]
|
||||
|
||||
# Filter by names
|
||||
names: [String!]
|
||||
|
||||
# Filter by author
|
||||
author: String
|
||||
|
||||
# Filter by text search matching against the character's description
|
||||
search: String
|
||||
|
||||
# Filter by whether they've been part of a log.
|
||||
logged: Boolean
|
||||
}
|
||||
|
||||
# Input for adding characters
|
||||
input CharacterAddInput {
|
||||
# The primary IRC nick name to recognize this character by
|
||||
nick: String!
|
||||
|
||||
# The character's name
|
||||
name: String!
|
||||
|
||||
# Optioanl shortened name. By default, it uses the first token in the name
|
||||
shortName: String
|
||||
|
||||
# Description for a character.
|
||||
description: String
|
||||
|
||||
# Optioanlly, specify another author. This needs special permissions if it's not
|
||||
# your own username
|
||||
author: String
|
||||
}
|
||||
|
||||
# Input for addNick and removeNick mutation
|
||||
input CharacterNickInput {
|
||||
# The ID of the character
|
||||
id: String!
|
||||
|
||||
# The nick to add or remove
|
||||
nick: String!
|
||||
}
|
||||
|
||||
input CharacterEditInput {
|
||||
# The id for the character to edit
|
||||
id: String!
|
||||
|
||||
# The full name of the character -- not the salarian full name!
|
||||
name: String
|
||||
|
||||
# The character's short name that is used in compact lists
|
||||
shortName: String
|
||||
|
||||
# A short description for the character
|
||||
description: String
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
# A File contains information about a file and where to download it.
|
||||
type File {
|
||||
# The file's unique ID
|
||||
id: String!
|
||||
|
||||
# The kind of file. Most will be "upload", but some that are ported from the wiki will have other values for this.
|
||||
kind: String!
|
||||
|
||||
# The time of uploading
|
||||
time: String!
|
||||
|
||||
# Whether the file is publicly listable. Someone with knowledge of the ID
|
||||
# will still be able to view it, however.
|
||||
public: Boolean!
|
||||
|
||||
# The file's name
|
||||
name: String!
|
||||
|
||||
# The MIME type of the file
|
||||
mimeType: String!
|
||||
|
||||
# The file's size in bytes
|
||||
size: Int!
|
||||
|
||||
# The uploader
|
||||
author: String!
|
||||
|
||||
# The URL where the file is hosted
|
||||
url: String
|
||||
}
|
||||
|
||||
# Filter for the files quiery.
|
||||
input FilesFilter {
|
||||
# If set, this will limit the results to either public or private files.
|
||||
public: Boolean
|
||||
|
||||
# Limit the MIME types of the files.
|
||||
mimeTypes: [String!]
|
||||
}
|
||||
|
||||
# Input for editFile mutation
|
||||
input EditFileInput {
|
||||
# The file's unique ID
|
||||
id: String!
|
||||
|
||||
# Whether the file is publicly listable. Someone with knowledge of the ID
|
||||
# will still be able to view it, however.
|
||||
public: Boolean
|
||||
|
||||
# The file's name
|
||||
name: String
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
# A Log is the "file" of an RP session
|
||||
type Log {
|
||||
# A unique identifier for the log.
|
||||
id: String!
|
||||
|
||||
# A secondary unique identifier for the log. This is a lot shorter and more suitable for storage when
|
||||
# the link doesn't need to be as expressive.
|
||||
shortId: String!
|
||||
|
||||
# The date for a log.
|
||||
date: String!
|
||||
|
||||
# The channel of a log.
|
||||
channelName: String!
|
||||
|
||||
# Information about the channel this log is in.
|
||||
channel: Channel!
|
||||
|
||||
# The session's title.
|
||||
title: String!
|
||||
|
||||
# The log's event, which is the same as the tags in the previous logbot site.
|
||||
# Empty string means that it's no event.
|
||||
event: String!
|
||||
|
||||
# The description of a session, which is empty if unset.
|
||||
description: String!
|
||||
|
||||
# Whether the log session is open.
|
||||
open: Boolean!
|
||||
|
||||
# The characters involved in the log file.
|
||||
characters: [Character!]!
|
||||
|
||||
# The posts of the logfile, which can be filtered by kinds.
|
||||
posts(kinds:[String!]): [Post!]!
|
||||
}
|
||||
|
||||
# Filter for logs query
|
||||
input LogsFilter {
|
||||
# Channels to limit results to (inclusive)
|
||||
channels: [String!]
|
||||
|
||||
# Events to limit results to (inclusive)
|
||||
events: [String!]
|
||||
|
||||
# Characters to limit results to (exclusive)
|
||||
characters: [String!]
|
||||
|
||||
# Search post content
|
||||
search: String
|
||||
|
||||
# Limit by whether it's open or not
|
||||
open: Boolean
|
||||
|
||||
# Limit the amount of results you get back
|
||||
limit: Int
|
||||
}
|
||||
|
||||
# Input for addLog mutation
|
||||
input LogAddInput {
|
||||
# The date of the log, in RFC3339 format with up to nanosecond precision
|
||||
date: String!
|
||||
|
||||
# The channel where the log is set
|
||||
channel: String!
|
||||
|
||||
# Optional: The log title
|
||||
title: String
|
||||
|
||||
# Optional: Whether the log is open
|
||||
open: Boolean
|
||||
|
||||
# Optional: The log event name
|
||||
event: String
|
||||
|
||||
# Optional: A short description of the log
|
||||
description: String
|
||||
}
|
||||
|
||||
# Input for addLog mutation
|
||||
input LogEditInput {
|
||||
# The id of the log
|
||||
id: String!
|
||||
|
||||
# The log title
|
||||
title: String
|
||||
|
||||
# The log event name
|
||||
event: String
|
||||
|
||||
# A short description of the log
|
||||
description: String
|
||||
|
||||
# Open/close the log
|
||||
open: Boolean
|
||||
}
|
||||
|
||||
# A LogHeader is a cut-down version of Log that does not allow getting the actual posts.
|
||||
type LogHeader {
|
||||
# A unique identifier for the log.
|
||||
id: String!
|
||||
|
||||
# A secondary unique identifier for the log. This is a lot shorter and more suitable for storage when
|
||||
# the link doesn't need to be as expressive.
|
||||
shortId: String!
|
||||
|
||||
# The date for a log.
|
||||
date: String!
|
||||
|
||||
# The channel name of a log.
|
||||
channelName: String!
|
||||
|
||||
# Information about the channel this log is in.
|
||||
channel: Channel!
|
||||
|
||||
# The session's title.
|
||||
title: String!
|
||||
|
||||
# The log's event, which is the same as the tags in the previous logbot site.
|
||||
# Empty string means that it's no event.
|
||||
event: String!
|
||||
|
||||
# The description of a session, which is empty if unset.
|
||||
description: String!
|
||||
|
||||
# Whether the log session is open.
|
||||
open: Boolean!
|
||||
|
||||
# The characters involved in the log file.
|
||||
characters: [Character!]!
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
# A Post is a part of a log
|
||||
type Post {
|
||||
# The post's ID
|
||||
id: String!
|
||||
|
||||
# The post's Log ID. This is the closest thing to a link back since this API graph doesn't have any cycles.
|
||||
logId: String!
|
||||
|
||||
# The date and time of posting
|
||||
time: String!
|
||||
|
||||
# The kind of post this is. Only "text", "scene" and "action" are RP, while others are annotations and 'commands'.
|
||||
kind: String!
|
||||
|
||||
# The character nick
|
||||
nick: String!
|
||||
|
||||
# The post's text, which purpose depends on the kind
|
||||
text: String!
|
||||
|
||||
# The post's position, used for reordering
|
||||
position: Int!
|
||||
}
|
||||
|
||||
# Input for the addPost mutation
|
||||
input AddPostInput {
|
||||
# The log's ID that this post should be a part of
|
||||
logId: String!
|
||||
|
||||
# The date and time of posting, in a RFC3339 format with up to a nanosecond's precision
|
||||
time: String!
|
||||
|
||||
# The kind of post this is. Only "text", "scene" and "action" are RP, while others are annotations and 'commands'.
|
||||
kind: String!
|
||||
|
||||
# The character nick, or command invoker for non-RP stuff
|
||||
nick: String!
|
||||
|
||||
# The post's text, which purpose depends on the kind
|
||||
text: String!
|
||||
}
|
||||
|
||||
# Input for the editPost mutation
|
||||
input EditPostInput {
|
||||
# The Post ID
|
||||
id: String!
|
||||
|
||||
# The date and time of posting, in a RFC3339 format with up to a nanosecond's precision
|
||||
time: String
|
||||
|
||||
# The kind of post this is. Only "text", "scene" and "action" are RP, while others are annotations and 'commands'.
|
||||
kind: String
|
||||
|
||||
# The character nick, or command invoker for non-RP stuff
|
||||
nick: String
|
||||
|
||||
# The post's text, which purpose depends on the kind
|
||||
text: String
|
||||
}
|
||||
|
||||
# Input for the movePost mutation
|
||||
input MovePostInput {
|
||||
# The Post ID
|
||||
id: String!
|
||||
|
||||
# Target index
|
||||
toPosition: Int!
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
# A Story is a piece of content that, unlike wiki articles, are not something that should be ordered by time instead of title. It can
|
||||
# contain multiple chapters by one or more autohrs
|
||||
type Story {
|
||||
# The story's ID
|
||||
id: String!
|
||||
|
||||
# The story's name, which will show up in the lists.
|
||||
name: String!
|
||||
|
||||
# The story's author
|
||||
author: String!
|
||||
|
||||
# Whether other users may add/edit chapters to the story.
|
||||
open: Boolean!
|
||||
|
||||
# Whether users without a direct link can find this story.
|
||||
listed: Boolean!
|
||||
|
||||
# The category of the story
|
||||
category: StoryCategory!
|
||||
|
||||
# The tags for this tory
|
||||
tags: [Tag!]!
|
||||
|
||||
# The chapters of this story
|
||||
chapters: [Chapter!]!
|
||||
|
||||
# The date the story was created (RFC3339 format).
|
||||
createdDate: String!
|
||||
|
||||
# The date the story is set in (RFC3339 format).
|
||||
fictionalDate: String!
|
||||
|
||||
# The date of the last major update to the story (RFC3339 format). This being bumped means a new chapter has been added.
|
||||
updatedDate: String!
|
||||
}
|
||||
|
||||
# Filter for stories query.
|
||||
input StoriesFilter {
|
||||
# What author to query for
|
||||
author: String
|
||||
|
||||
# What category to query for
|
||||
category: StoryCategory
|
||||
|
||||
# What tags to query for
|
||||
tags: [TagInput!]
|
||||
|
||||
# If true, it will only show unlisted page you have access to
|
||||
unlisted: Boolean
|
||||
|
||||
# Whether the page is open for additions by other users
|
||||
open: Boolean
|
||||
|
||||
# The earliest fictionalDate
|
||||
earliestFictionalDate: 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
|
||||
enum StoryCategory {
|
||||
# General information
|
||||
Info
|
||||
|
||||
# News stories
|
||||
News
|
||||
|
||||
# Description and content of a document or item
|
||||
Document
|
||||
|
||||
# Information about something going on in the background that may or may not inform RP
|
||||
Background
|
||||
|
||||
# A short story
|
||||
Story
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
# A Tag is a means of associating stories that have details in common with one another.
|
||||
type Tag {
|
||||
# The tag's kind
|
||||
kind: TagKind!
|
||||
|
||||
# The tag's name
|
||||
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
|
||||
enum TagKind {
|
||||
# An organization is a catch all term for in-universe corporations, teams, groups, cults, forces, etc...
|
||||
Organization
|
||||
|
||||
# A character tag should have the exact full name of the character.
|
||||
Character
|
||||
|
||||
# A location is anything from a planet to an establishment. This may overlap with an organization, and if so, both
|
||||
# kinds of tags should be used.
|
||||
Location
|
||||
|
||||
# An event is a plot or a part of a plot.
|
||||
Event
|
||||
|
||||
# None of the above, but it does still tie multiple stories together. The new story/chapter format may obsolete this tag kind.
|
||||
Series
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
# The User type is for interacting with user options and settings
|
||||
type User {
|
||||
# Their username
|
||||
id: String!
|
||||
|
||||
# Their permission
|
||||
permissions: [String!]!
|
||||
}
|
||||
@@ -14,6 +14,7 @@ var global *Config
|
||||
// Config is configuration
|
||||
type Config struct {
|
||||
Space struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Host string `json:"host"`
|
||||
AccessKey string `json:"accessKey"`
|
||||
SecretKey string `json:"secretKey"`
|
||||
|
||||
@@ -30,6 +30,24 @@ func Next(category, name string) (int, error) {
|
||||
return doc.Value, nil
|
||||
}
|
||||
|
||||
// NextMany gets the next value of a counter, or an error if it hasn't, and increments by a specified value.
|
||||
// Any value `returned` to `returned+(increment-1)` should then be safe to use.
|
||||
func NextMany(category, name string, increment int) (int, error) {
|
||||
id := category + "." + name
|
||||
doc := counter{}
|
||||
|
||||
_, err := collection.Find(bson.M{"_id": id}).Apply(mgo.Change{
|
||||
Update: bson.M{"$inc": bson.M{"value": increment}},
|
||||
Upsert: true,
|
||||
ReturnNew: true,
|
||||
}, &doc)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
return doc.Value, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
store.HandleInit(func(db *mgo.Database) {
|
||||
collection = db.C("core.counters")
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
package forumlog
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/internal/importers/mirclike"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
)
|
||||
|
||||
// A ParsedLog contains the parsed log header and its posts.
|
||||
type ParsedLog struct {
|
||||
Log models.Log
|
||||
Posts []models.Post
|
||||
}
|
||||
|
||||
// ParseLogs parses the logs from the data.
|
||||
func ParseLogs(data string, tz *time.Location) ([]ParsedLog, error) {
|
||||
metadata := ParseMetadata(data)
|
||||
results := make([]ParsedLog, 0, len(metadata["Date"]))
|
||||
scanner := bufio.NewScanner(strings.NewReader(data))
|
||||
|
||||
for i, dateStr := range metadata["Date"] {
|
||||
// Parse date
|
||||
date, err := time.ParseInLocation("January 2, 2006", dateStr, tz)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to parse date #%d: %#+v is not the in the correct format of \"January 2, 2006\"", i+1, dateStr)
|
||||
}
|
||||
|
||||
// Parse posts
|
||||
posts := make([]models.Post, 0, 128)
|
||||
parsing := false
|
||||
prev := ""
|
||||
prevPost := models.Post{}
|
||||
for scanner.Scan() {
|
||||
line := strings.Trim(scanner.Text(), "\r\t ")
|
||||
|
||||
// Skip lines with links to other logs using the --> and <-- notation.
|
||||
if strings.HasPrefix(line, "-") || strings.HasPrefix(line, "<") {
|
||||
prev = line
|
||||
continue
|
||||
}
|
||||
|
||||
// If parsing and reaching a double empty-line, the session is done.
|
||||
if parsing && len(line) < 2 && len(prev) < 2 {
|
||||
break
|
||||
}
|
||||
|
||||
// Skip empty lines, but record them as thep revious for the above check.
|
||||
if len(line) < 2 {
|
||||
prev = line
|
||||
continue
|
||||
}
|
||||
|
||||
// If not parsing, skip until the first mirclike post.
|
||||
if !parsing {
|
||||
if strings.HasPrefix(line, "[") {
|
||||
parsing = true
|
||||
} else {
|
||||
prev = line
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the post.
|
||||
post, err := mirclike.ParsePost(line, date, prevPost)
|
||||
if err != nil {
|
||||
summary := ""
|
||||
for _, ru := range line {
|
||||
summary += string(ru)
|
||||
if len(summary) > 60 {
|
||||
summary += "..."
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("Failed to parse post: %s", summary)
|
||||
}
|
||||
|
||||
posts = append(posts, post)
|
||||
prevPost = post
|
||||
prev = line
|
||||
}
|
||||
|
||||
// No posts means there's a problem.
|
||||
if len(posts) == 0 {
|
||||
return nil, fmt.Errorf("Session %d (%s) has no posts (too many dates?)", i+1, dateStr)
|
||||
}
|
||||
|
||||
// Add it.
|
||||
results = append(results, ParsedLog{
|
||||
Log: models.Log{Date: posts[0].Time},
|
||||
Posts: posts,
|
||||
})
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
package forumlog_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"git.aiterp.net/rpdata/api/internal/importers/forumlog"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
)
|
||||
|
||||
func TestParseLogs(t *testing.T) {
|
||||
results, err := forumlog.ParseLogs(testLog, time.UTC)
|
||||
if err != nil {
|
||||
t.Fatalf("Parse: %s", err)
|
||||
}
|
||||
|
||||
assert.Equal(t, 2, len(results), "Amount of results.")
|
||||
assert.Equal(t, testLogPosts[0], results[0].Posts, "First log's posts.")
|
||||
assert.Equal(t, testLogPosts[1], results[1].Posts, "Second log's posts.")
|
||||
}
|
||||
|
||||
func TestParseLogsErrors(t *testing.T) {
|
||||
_, err1 := forumlog.ParseLogs(brokenLogNoPosts, time.UTC)
|
||||
_, err2 := forumlog.ParseLogs(brokenLogBrokenPost, time.UTC)
|
||||
_, err3 := forumlog.ParseLogs(brokenLogBrokenDate, time.UTC)
|
||||
|
||||
t.Log("Should be about no posts:", err1)
|
||||
t.Log("Should be about a broken post:", err2)
|
||||
t.Log("Should be about a broken date:", err3)
|
||||
|
||||
assert.NotEqual(t, err1, nil, "Should be no posts")
|
||||
assert.NotEqual(t, err2, nil, "Should be about a bad post")
|
||||
assert.NotEqual(t, err3, nil, "Should be about a broken date")
|
||||
}
|
||||
|
||||
var testLog = `
|
||||
Date:
|
||||
July 25, 2013
|
||||
July 26, 2013
|
||||
|
||||
GM:
|
||||
Tyranniac
|
||||
|
||||
Length:
|
||||
92 posts
|
||||
|
||||
Characters:
|
||||
Fera'Sel nar Veltar - Baphomet
|
||||
Renala T’Iavay - Gisle
|
||||
Steven Briggs - Dante
|
||||
|
||||
NPCs:
|
||||
Receptionist
|
||||
|
||||
Relevant RPs:
|
||||
<-- Hub - Miner's Respite (July 16)
|
||||
--> Event - Hinpinn's Salvage Mission (August 5)
|
||||
|
||||
[21:28] * @Tyranniac | The Hnipinn Minerals local administrative center is a rather small building located next to the refinery. The area is mostly asphalt and industrial surroundings. The gate in the fence surrounding the refinery is nearby, with a transport truck just being admitted by the guard - rather heavily armed for corporate security. Despite not being that late, it's rather dark due to the rain-bearing clouds that have just started emptying their content. The windows of the office glow with invitingly warm light. A receptionist can be seen working through the glass door.
|
||||
|
||||
[21:46] * Steve_Briggs approaches the building at a brisk jog, pulling up the collar of his jacket to block as much of the rain as possible. Stopping just short of the door, he holds back and awaits Renala. "Well, hopefully this is the universe's way of getting our bad luck out of the way early." he says, glancing up at the sky. "Good things to come!" he adds with a smile.
|
||||
|
||||
|
||||
[21:46] * Steve_Briggs approaches the building at a brisk jog, pulling up the collar of his jacket to block as much of the rain as possible. Stopping just short of the door, he holds back and awaits Renala. "Well, hopefully this is the universe's way of getting our bad luck out of the way early." he says, glancing up at the sky. "Good things to come!" he adds with a smile.
|
||||
|
||||
--> Stuff and things
|
||||
|
||||
<-- Stuff and things
|
||||
|
||||
[21:46] * Steve_Briggs approaches the building at a brisk jog, pulling up the collar of his jacket to block as much of the rain as possible. Stopping just short of the door, he holds back and awaits Renala. "Well, hopefully this is the universe's way of getting our bad luck out of the way early." he says, glancing up at the sky. "Good things to come!" he adds with a smile.
|
||||
`
|
||||
|
||||
var testLogPosts = [][]models.Post{
|
||||
{
|
||||
{
|
||||
ID: "UNASSIGNED",
|
||||
LogID: "UNASSIGNED",
|
||||
Time: parseDate(nil, "2013-07-25 21:28:00"),
|
||||
Kind: "action",
|
||||
Nick: "Tyranniac",
|
||||
Text: "| The Hnipinn Minerals local administrative center is a rather small building located next to the refinery. The area is mostly asphalt and industrial surroundings. The gate in the fence surrounding the refinery is nearby, with a transport truck just being admitted by the guard - rather heavily armed for corporate security. Despite not being that late, it's rather dark due to the rain-bearing clouds that have just started emptying their content. The windows of the office glow with invitingly warm light. A receptionist can be seen working through the glass door.",
|
||||
Position: 1,
|
||||
},
|
||||
{
|
||||
ID: "UNASSIGNED",
|
||||
LogID: "UNASSIGNED",
|
||||
Time: parseDate(nil, "2013-07-25 21:46:00"),
|
||||
Kind: "action",
|
||||
Nick: "Steve_Briggs",
|
||||
Text: `approaches the building at a brisk jog, pulling up the collar of his jacket to block as much of the rain as possible. Stopping just short of the door, he holds back and awaits Renala. "Well, hopefully this is the universe's way of getting our bad luck out of the way early." he says, glancing up at the sky. "Good things to come!" he adds with a smile.`,
|
||||
Position: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
{
|
||||
ID: "UNASSIGNED",
|
||||
LogID: "UNASSIGNED",
|
||||
Time: parseDate(nil, "2013-07-26 21:46:00"),
|
||||
Kind: "action",
|
||||
Nick: "Steve_Briggs",
|
||||
Text: `approaches the building at a brisk jog, pulling up the collar of his jacket to block as much of the rain as possible. Stopping just short of the door, he holds back and awaits Renala. "Well, hopefully this is the universe's way of getting our bad luck out of the way early." he says, glancing up at the sky. "Good things to come!" he adds with a smile.`,
|
||||
Position: 1,
|
||||
},
|
||||
{
|
||||
ID: "UNASSIGNED",
|
||||
LogID: "UNASSIGNED",
|
||||
Time: parseDate(nil, "2013-07-26 21:46:00"),
|
||||
Kind: "action",
|
||||
Nick: "Steve_Briggs",
|
||||
Text: `approaches the building at a brisk jog, pulling up the collar of his jacket to block as much of the rain as possible. Stopping just short of the door, he holds back and awaits Renala. "Well, hopefully this is the universe's way of getting our bad luck out of the way early." he says, glancing up at the sky. "Good things to come!" he adds with a smile.`,
|
||||
Position: 2,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var brokenLogNoPosts = `
|
||||
Date:
|
||||
September 27, 2014
|
||||
`
|
||||
|
||||
var brokenLogBrokenPost = `
|
||||
Date:
|
||||
September 10, 2038
|
||||
|
||||
[12:38] * Some_Character is lasered by a reaper, and turns to ash before even knowing what happened.
|
||||
|
||||
[12:4 * Some_Other_Character also meets the same fate, because the Reapers are no joke.
|
||||
`
|
||||
|
||||
var brokenLogBrokenDate = `
|
||||
Date:
|
||||
10 September, 2038
|
||||
|
||||
[12:34] * =Scene= Stuff happens.
|
||||
`
|
||||
|
||||
func parseDate(t *testing.T, date string) time.Time {
|
||||
result, err := time.Parse("2006-01-02 15:04:05", date)
|
||||
if err != nil {
|
||||
if t != nil {
|
||||
t.Fatal("Could not parse date", date, err)
|
||||
} else {
|
||||
panic("Could not parse date: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package forumlog
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ParseMetadata parses metadata, discards the broken parts, and returns the
|
||||
// parsed data as a map (`m`) and the position of the first IRC post (`n`)
|
||||
func ParseMetadata(data string) map[string][]string {
|
||||
result := make(map[string][]string)
|
||||
key := ""
|
||||
|
||||
scanner := bufio.NewScanner(strings.NewReader(data))
|
||||
for scanner.Scan() {
|
||||
line := strings.Trim(scanner.Text(), "\r\t ")
|
||||
|
||||
if strings.HasPrefix(line, "[") {
|
||||
break
|
||||
}
|
||||
if len(line) < 1 {
|
||||
key = ""
|
||||
continue
|
||||
}
|
||||
|
||||
if key == "" {
|
||||
split := strings.Split(line, ":")
|
||||
key = split[0]
|
||||
} else {
|
||||
result[key] = append(result[key], line)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package forumlog_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"git.aiterp.net/rpdata/api/internal/importers/forumlog"
|
||||
)
|
||||
|
||||
func TestParseMetadata(t *testing.T) {
|
||||
data := forumlog.ParseMetadata(testData1)
|
||||
|
||||
assert.Equal(t, data["Date"], []string{
|
||||
"August 11, 2014",
|
||||
"August 12, 2014",
|
||||
"August 13, 2014",
|
||||
})
|
||||
assert.Equal(t, data["Location"], []string{
|
||||
"Aroste, an island off the Derraian coastline.",
|
||||
})
|
||||
assert.Equal(t, data["GM"], []string{
|
||||
"Gisle",
|
||||
})
|
||||
assert.Equal(t, data["Length"], []string{
|
||||
"847 posts",
|
||||
})
|
||||
assert.Equal(t, len(data["Stuff"]), 0)
|
||||
|
||||
for key := range data {
|
||||
if strings.HasPrefix(key, "[") {
|
||||
t.Error("It should have stopped at the IRC post, yet this key exists:", key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var testData1 = `
|
||||
Date: August 2185
|
||||
August 11, 2014
|
||||
August 12, 2014
|
||||
August 13, 2014
|
||||
|
||||
Location:
|
||||
Aroste, an island off the Derraian coastline.
|
||||
|
||||
GM:
|
||||
Gisle
|
||||
|
||||
Length:
|
||||
847 posts
|
||||
|
||||
Characters:
|
||||
Calyx Vadris - Osiris
|
||||
Damien Monroe - Bowe
|
||||
Jason Wolfe - Dante
|
||||
Renala T'Iavay - Gisle
|
||||
Victoria Steels - MCB280
|
||||
|
||||
NPCs:
|
||||
Recurring:
|
||||
Halisi - Tyranniac
|
||||
Jattic - Dante
|
||||
Jelvan Darennon - Gisle
|
||||
Leah - Dante
|
||||
Marissa T'Evin - Gisle
|
||||
Philip Lacour - Tyranniac
|
||||
|
||||
One-off:
|
||||
Reidas Falten (Turian - Guard meeting them on their trip to the building)
|
||||
Sarian T'Dera (Asari - Asari opening fire on Calyx, Leah and Renala)
|
||||
Mariam Adams (Human - The receptionist.)
|
||||
Prerix Falten (Turian2 - Victoria's chair)
|
||||
Jonathan Lyng (OtherHuman - The one opening fire on Leah entering the garage)
|
||||
Alerena Teris (Teris - Asari met in the maintenance corridor)
|
||||
|
||||
<-- Redorck Agency - August 11, 2014
|
||||
|
||||
[13:19] * Leah looks over at Damien. "Haven't seen you around the agency." she says curiously, "New hire?"
|
||||
|
||||
`
|
||||
@@ -0,0 +1,48 @@
|
||||
package mirclike
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
)
|
||||
|
||||
// ErrEmptyLog is returned by ParseLog if there are no (valid) posts in the log.
|
||||
var ErrEmptyLog = errors.New("No valid posts found in log")
|
||||
|
||||
// ParseLog parses the log and returns the things that can be gleamed from them.
|
||||
func ParseLog(data string, date time.Time, strict bool) (models.Log, []models.Post, error) {
|
||||
lines := strings.Split(data, "\n")
|
||||
posts := make([]models.Post, 0, len(lines))
|
||||
prev := models.Post{}
|
||||
|
||||
for _, line := range lines {
|
||||
line = strings.Trim(line, "\r\t ")
|
||||
if len(line) < 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
post, err := ParsePost(line, date, prev)
|
||||
if err != nil {
|
||||
if strict {
|
||||
return models.Log{}, nil, err
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
posts = append(posts, post)
|
||||
prev = post
|
||||
}
|
||||
|
||||
if len(posts) == 0 {
|
||||
return models.Log{}, nil, ErrEmptyLog
|
||||
}
|
||||
|
||||
log := models.Log{
|
||||
Date: posts[0].Time,
|
||||
}
|
||||
|
||||
return log, posts, nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user