Compare commits
41
Commits
1.1
...
module-madness
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
162ccf6bfb | ||
|
|
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 |
+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) {
|
||||
|
||||
@@ -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:
|
||||
|
||||
+11
-6
@@ -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
|
||||
}
|
||||
@@ -38,10 +47,6 @@ func (r *rootResolver) Story() StoryResolver {
|
||||
return &types.StoryResolver
|
||||
}
|
||||
|
||||
func (r *rootResolver) File() FileResolver {
|
||||
return &types.FileResolver
|
||||
}
|
||||
|
||||
func (r *rootResolver) Change() ChangeResolver {
|
||||
return &types.ChangeResolver
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package queries
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"git.aiterp.net/rpdata/api/models/changes"
|
||||
@@ -12,3 +13,13 @@ import (
|
||||
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
|
||||
}
|
||||
|
||||
+16
-11
@@ -14,8 +14,13 @@ import (
|
||||
|
||||
// Queries
|
||||
|
||||
func (r *resolver) Channel(ctx context.Context, name string) (models.Channel, error) {
|
||||
return channels.FindName(name)
|
||||
func (r *resolver) Channel(ctx context.Context, name string) (*models.Channel, error) {
|
||||
channel, err := channels.FindName(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &channel, nil
|
||||
}
|
||||
|
||||
func (r *resolver) Channels(ctx context.Context, filter *channels.Filter) ([]models.Channel, error) {
|
||||
@@ -24,10 +29,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
|
||||
@@ -49,31 +54,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, true, changekeys.Listed(channel), channel)
|
||||
|
||||
return channel, nil
|
||||
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)
|
||||
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, true, changekeys.Listed(channel), channel)
|
||||
|
||||
return channel, nil
|
||||
return &channel, nil
|
||||
}
|
||||
|
||||
+48
-34
@@ -5,83 +5,92 @@ import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"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/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/models/stories"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graph2/input"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"git.aiterp.net/rpdata/api/models/chapters"
|
||||
"git.aiterp.net/rpdata/api/models/comments"
|
||||
"git.aiterp.net/rpdata/api/models/stories"
|
||||
)
|
||||
|
||||
// Queries
|
||||
|
||||
func (r *resolver) Chapter(ctx context.Context, id string) (models.Chapter, error) {
|
||||
return chapters.FindID(id)
|
||||
func (r *resolver) Chapter(ctx context.Context, id string) (*models.Chapter, error) {
|
||||
chapter, err := chapters.FindID(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &chapter, nil
|
||||
}
|
||||
|
||||
// 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.Permitted("member", "story.add") {
|
||||
return models.Chapter{}, errors.New("Unauthorized")
|
||||
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")
|
||||
}
|
||||
|
||||
chapter, err := chapters.Add(story, input.Title, author, input.Source, time.Now(), input.FictionalDate)
|
||||
commentMode := models.ChapterCommentModeDisabled
|
||||
if input.CommentMode != nil {
|
||||
commentMode = *input.CommentMode
|
||||
}
|
||||
|
||||
chapter, err := chapters.Add(story, input.Title, author, input.Source, time.Now(), input.FictionalDate, commentMode)
|
||||
if err != nil {
|
||||
return models.Chapter{}, errors.New("Failed to create chapter: " + err.Error())
|
||||
return nil, errors.New("Failed to create chapter: " + err.Error())
|
||||
}
|
||||
|
||||
go changes.Submit("Chapter", "add", token.UserID, story.Listed, changekeys.Listed(story, chapter), story, chapter)
|
||||
|
||||
return chapter, nil
|
||||
return &chapter, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) MoveChapter(ctx context.Context, input input.ChapterMoveInput) (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", "chapter.move") {
|
||||
return models.Chapter{}, errors.New("You are not allowed to move this chapter")
|
||||
return nil, errors.New("You are not allowed to move this chapter")
|
||||
}
|
||||
|
||||
target, err := stories.FindID(input.StoryID)
|
||||
if err != nil {
|
||||
return models.Chapter{}, errors.New("Target story not found")
|
||||
return nil, errors.New("Target story not found")
|
||||
}
|
||||
|
||||
if !target.Open && !token.PermittedUser(target.Author, "member", "chapter.move") {
|
||||
return models.Chapter{}, errors.New("You are not permitted to move chapters to this story")
|
||||
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 models.Chapter{}, errors.New("Failed to move chapter: " + err.Error())
|
||||
return nil, errors.New("Failed to move chapter: " + err.Error())
|
||||
}
|
||||
|
||||
go func() {
|
||||
@@ -99,27 +108,27 @@ func (r *mutationResolver) MoveChapter(ctx context.Context, input input.ChapterM
|
||||
changes.Submit("Chapter", "move-in", token.UserID, story.Listed, changekeys.Listed(story, chapter), story, chapter)
|
||||
}()
|
||||
|
||||
return chapter, nil
|
||||
return &chapter, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) EditChapter(ctx context.Context, input input.ChapterEditInput) (models.Chapter, error) {
|
||||
func (r *mutationResolver) EditChapter(ctx context.Context, input input.ChapterEditInput) (*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", "chapter.edit") {
|
||||
return models.Chapter{}, errors.New("Unauthorized")
|
||||
return nil, errors.New("Unauthorized")
|
||||
}
|
||||
|
||||
if input.ClearFictionalDate != nil && *input.ClearFictionalDate == true {
|
||||
input.FictionalDate = &time.Time{}
|
||||
}
|
||||
|
||||
chapter, err = chapters.Edit(chapter, input.Title, input.Source, input.FictionalDate)
|
||||
chapter, err = chapters.Edit(chapter, input.Title, input.Source, input.FictionalDate, input.CommentMode, input.CommentsLocked)
|
||||
if err != nil {
|
||||
return models.Chapter{}, errors.New("Failed to edit chapter: " + err.Error())
|
||||
return nil, errors.New("Failed to edit chapter: " + err.Error())
|
||||
}
|
||||
|
||||
go func() {
|
||||
@@ -131,23 +140,28 @@ func (r *mutationResolver) EditChapter(ctx context.Context, input input.ChapterE
|
||||
changes.Submit("Chapter", "edit", token.UserID, story.Listed, changekeys.Many(story, chapter), chapter)
|
||||
}()
|
||||
|
||||
return chapter, nil
|
||||
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", "chapter.remove") {
|
||||
return models.Chapter{}, errors.New("Unauthorized")
|
||||
return nil, errors.New("Unauthorized")
|
||||
}
|
||||
|
||||
chapter, err = chapters.Remove(chapter)
|
||||
if err != nil {
|
||||
return models.Chapter{}, errors.New("Failed to remove chapter: " + err.Error())
|
||||
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() {
|
||||
@@ -159,5 +173,5 @@ func (r *mutationResolver) RemoveChapter(ctx context.Context, input input.Chapte
|
||||
changes.Submit("Chapter", "remove", token.UserID, story.Listed, changekeys.Many(story, chapter), chapter)
|
||||
}()
|
||||
|
||||
return chapter, nil
|
||||
return &chapter, nil
|
||||
}
|
||||
|
||||
+43
-33
@@ -16,13 +16,23 @@ import (
|
||||
|
||||
// Queries
|
||||
|
||||
func (r *resolver) Character(ctx context.Context, id *string, nick *string) (models.Character, error) {
|
||||
func (r *resolver) Character(ctx context.Context, id *string, nick *string) (*models.Character, error) {
|
||||
if id != nil {
|
||||
return characters.FindID(*id)
|
||||
character, err := characters.FindID(*id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &character, nil
|
||||
} else if nick != nil {
|
||||
return characters.FindNick(*nick)
|
||||
character, err := characters.FindNick(*nick)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &character, nil
|
||||
} else {
|
||||
return models.Character{}, errors.New("You must specify either an ID or a nick")
|
||||
return nil, errors.New("You must specify either an ID or a nick")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,13 +42,13 @@ func (r *resolver) Characters(ctx context.Context, filter *characters.Filter) ([
|
||||
|
||||
// Mutations
|
||||
|
||||
func (r *mutationResolver) AddCharacter(ctx context.Context, input input.CharacterAddInput) (models.Character, error) {
|
||||
func (r *mutationResolver) AddCharacter(ctx context.Context, input input.CharacterAddInput) (*models.Character, error) {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Permitted("member", "character.add") {
|
||||
return models.Character{}, errors.New("You are not permitted to add characters")
|
||||
return nil, errors.New("You are not permitted to add characters")
|
||||
}
|
||||
if len(input.Name) < 2 || len(input.Nick) < 2 {
|
||||
return models.Character{}, errors.New("You need to provide a name and a nick (min length: 2)")
|
||||
return nil, errors.New("You need to provide a name and a nick (min length: 2)")
|
||||
}
|
||||
|
||||
shortName := ""
|
||||
@@ -56,7 +66,7 @@ func (r *mutationResolver) AddCharacter(ctx context.Context, input input.Charact
|
||||
author := token.UserID
|
||||
if input.Author != nil && *input.Author != author {
|
||||
if !token.Permitted("character.add") {
|
||||
return models.Character{}, errors.New("You are only permitted to add your own characters")
|
||||
return nil, errors.New("You are only permitted to add your own characters")
|
||||
}
|
||||
|
||||
author = *input.Author
|
||||
@@ -66,109 +76,109 @@ func (r *mutationResolver) AddCharacter(ctx context.Context, input input.Charact
|
||||
|
||||
character, err := characters.Add(input.Nick, input.Name, shortName, author, description)
|
||||
if err != nil {
|
||||
return models.Character{}, errors.New("Adding character failed: " + err.Error())
|
||||
return nil, errors.New("Adding character failed: " + err.Error())
|
||||
}
|
||||
|
||||
go changes.Submit("Character", "add", token.UserID, true, changekeys.Listed(character), character)
|
||||
|
||||
return character, nil
|
||||
return &character, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) AddCharacterNick(ctx context.Context, input input.CharacterNickInput) (models.Character, error) {
|
||||
func (r *mutationResolver) AddCharacterNick(ctx context.Context, input input.CharacterNickInput) (*models.Character, error) {
|
||||
character, err := characters.FindID(input.ID)
|
||||
if err != nil {
|
||||
return models.Character{}, errors.New("Character not found")
|
||||
return nil, errors.New("Character not found")
|
||||
}
|
||||
|
||||
if len(input.Nick) < 2 {
|
||||
return models.Character{}, errors.New("You need to provide a valid nick (min length: 2)")
|
||||
return nil, errors.New("You need to provide a valid nick (min length: 2)")
|
||||
}
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.PermittedUser(character.Author, "member", "character.edit") {
|
||||
return models.Character{}, errors.New("You are not permitted to edit this character")
|
||||
return nil, errors.New("You are not permitted to edit this character")
|
||||
}
|
||||
|
||||
logs.ScheduleFullUpdate()
|
||||
|
||||
character, err = characters.AddNick(character, input.Nick)
|
||||
if err != nil {
|
||||
return models.Character{}, errors.New("Failed to add nick: " + err.Error())
|
||||
return nil, errors.New("Failed to add nick: " + err.Error())
|
||||
}
|
||||
|
||||
go logs.ScheduleFullUpdate()
|
||||
go changes.Submit("Character", "edit", token.UserID, true, changekeys.Listed(character), character)
|
||||
|
||||
return character, nil
|
||||
return &character, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) RemoveCharacterNick(ctx context.Context, input input.CharacterNickInput) (models.Character, error) {
|
||||
func (r *mutationResolver) RemoveCharacterNick(ctx context.Context, input input.CharacterNickInput) (*models.Character, error) {
|
||||
character, err := characters.FindID(input.ID)
|
||||
if err != nil {
|
||||
return models.Character{}, errors.New("Character not found")
|
||||
return nil, errors.New("Character not found")
|
||||
}
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.PermittedUser(character.Author, "member", "character.edit") {
|
||||
return models.Character{}, errors.New("You are not permitted to edit this character")
|
||||
return nil, errors.New("You are not permitted to edit this character")
|
||||
}
|
||||
|
||||
character, err = characters.RemoveNick(character, input.Nick)
|
||||
if err != nil {
|
||||
return models.Character{}, errors.New("Failed to remove nick: " + err.Error())
|
||||
return nil, errors.New("Failed to remove nick: " + err.Error())
|
||||
}
|
||||
|
||||
go logs.ScheduleFullUpdate()
|
||||
go changes.Submit("Character", "edit", token.UserID, true, changekeys.Listed(character), character)
|
||||
|
||||
return character, nil
|
||||
return &character, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) EditCharacter(ctx context.Context, input input.CharacterEditInput) (models.Character, error) {
|
||||
func (r *mutationResolver) EditCharacter(ctx context.Context, input input.CharacterEditInput) (*models.Character, error) {
|
||||
character, err := characters.FindID(input.ID)
|
||||
if err != nil {
|
||||
return models.Character{}, errors.New("Character not found")
|
||||
return nil, errors.New("Character not found")
|
||||
}
|
||||
|
||||
if input.Name != nil && len(*input.Name) < 2 {
|
||||
return models.Character{}, errors.New("You need to provide a valid name (min length: 2)")
|
||||
return nil, errors.New("You need to provide a valid name (min length: 2)")
|
||||
}
|
||||
if input.ShortName != nil && len(*input.ShortName) < 2 {
|
||||
return models.Character{}, errors.New("You need to provide a valid short name (min length: 2)")
|
||||
return nil, errors.New("You need to provide a valid short name (min length: 2)")
|
||||
}
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.PermittedUser(character.Author, "member", "character.edit") {
|
||||
return models.Character{}, errors.New("You are not permitted to edit this character")
|
||||
return nil, errors.New("You are not permitted to edit this character")
|
||||
}
|
||||
|
||||
character, err = characters.Edit(character, input.Name, input.ShortName, input.Description)
|
||||
if err != nil {
|
||||
return models.Character{}, errors.New("Failed to edit character: " + err.Error())
|
||||
return nil, errors.New("Failed to edit character: " + err.Error())
|
||||
}
|
||||
|
||||
go changes.Submit("Character", "edit", token.UserID, true, changekeys.Listed(character), character)
|
||||
|
||||
return character, nil
|
||||
return &character, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) RemoveCharacter(ctx context.Context, input input.CharacterRemoveInput) (models.Character, error) {
|
||||
func (r *mutationResolver) RemoveCharacter(ctx context.Context, input input.CharacterRemoveInput) (*models.Character, error) {
|
||||
character, err := characters.FindID(input.ID)
|
||||
if err != nil {
|
||||
return models.Character{}, errors.New("Character not found")
|
||||
return nil, errors.New("Character not found")
|
||||
}
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.PermittedUser(character.Author, "member", "character.remove") {
|
||||
return models.Character{}, errors.New("You are not permitted to remove this character")
|
||||
return nil, errors.New("You are not permitted to remove this character")
|
||||
}
|
||||
|
||||
character, err = characters.Remove(character)
|
||||
if err != nil {
|
||||
return models.Character{}, errors.New("Failed to remove character: " + err.Error())
|
||||
return nil, errors.New("Failed to remove character: " + err.Error())
|
||||
}
|
||||
|
||||
go changes.Submit("Character", "remove", token.UserID, true, changekeys.Listed(character), character)
|
||||
|
||||
return character, nil
|
||||
return &character, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
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) {
|
||||
comment, err := comments.Find(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &comment, nil
|
||||
}
|
||||
|
||||
// Mutations
|
||||
|
||||
func (r *mutationResolver) AddComment(ctx context.Context, input input.CommentAddInput) (*models.Comment, error) {
|
||||
chapter, err := chapters.FindID(input.ChapterID)
|
||||
if err != nil {
|
||||
return nil, errors.New("Chapter not found")
|
||||
}
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Permitted("member", "story.edit") {
|
||||
return nil, errors.New("Unauthorized")
|
||||
}
|
||||
|
||||
if !chapter.CanComment() {
|
||||
return nil, errors.New("Comments are disabled or locked")
|
||||
}
|
||||
|
||||
var characterPtr *models.Character
|
||||
if input.CharacterID != nil {
|
||||
character, err := characters.FindID(*input.CharacterID)
|
||||
if err != nil {
|
||||
return nil, errors.New("Character not found")
|
||||
} else if character.Author != token.UserID {
|
||||
return nil, errors.New("That is not your character")
|
||||
}
|
||||
|
||||
characterPtr = &character
|
||||
}
|
||||
|
||||
fictionalDate := time.Time{}
|
||||
if input.FictionalDate != nil {
|
||||
fictionalDate = *input.FictionalDate
|
||||
}
|
||||
|
||||
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 nil, 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 nil, errors.New("Comment not found")
|
||||
}
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.PermittedUser(comment.Author, "member", "story.edit") {
|
||||
return nil, errors.New("You cannot edit this comment")
|
||||
}
|
||||
|
||||
chapter, err := chapters.FindID(comment.ChapterID)
|
||||
if err != nil {
|
||||
return nil, errors.New("Comment's chapter not found")
|
||||
}
|
||||
|
||||
if !chapter.CanComment() {
|
||||
return nil, errors.New("Comments are disabled or locked")
|
||||
}
|
||||
|
||||
if input.ClearFictionalDate != nil && *input.ClearFictionalDate == true {
|
||||
input.FictionalDate = &time.Time{}
|
||||
}
|
||||
|
||||
if input.CharacterID != nil && *input.CharacterID != "" {
|
||||
character, err := characters.FindID(*input.CharacterID)
|
||||
if err != nil {
|
||||
return nil, errors.New("Character not found")
|
||||
} else if character.Author != token.UserID {
|
||||
return nil, errors.New("That is not your character")
|
||||
}
|
||||
}
|
||||
|
||||
comment, err = comments.Edit(comment, input.Source, input.CharacterName, input.CharacterID, input.Subject, input.FictionalDate)
|
||||
if err != nil {
|
||||
return nil, errors.New("Could not post comment: " + err.Error())
|
||||
}
|
||||
|
||||
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 nil, errors.New("Comment not found")
|
||||
}
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.PermittedUser(comment.Author, "member", "story.edit") {
|
||||
return nil, errors.New("You cannot remove this comment")
|
||||
}
|
||||
|
||||
chapter, err := chapters.FindID(comment.ChapterID)
|
||||
if err != nil {
|
||||
return nil, errors.New("Comment's chapter not found")
|
||||
}
|
||||
|
||||
if !chapter.CanComment() {
|
||||
return nil, errors.New("Comments are disabled or locked")
|
||||
}
|
||||
|
||||
err = comments.Remove(comment)
|
||||
if err != nil {
|
||||
return nil, 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
|
||||
}
|
||||
@@ -8,8 +8,13 @@ import (
|
||||
"git.aiterp.net/rpdata/api/models/files"
|
||||
)
|
||||
|
||||
func (r *resolver) File(ctx context.Context, id string) (models.File, error) {
|
||||
return files.FindID(id)
|
||||
func (r *resolver) File(ctx context.Context, id string) (*models.File, error) {
|
||||
file, err := files.FindID(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &file, nil
|
||||
}
|
||||
|
||||
func (r *resolver) Files(ctx context.Context, filter *files.Filter) ([]models.File, error) {
|
||||
|
||||
+66
-16
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models/channels"
|
||||
|
||||
@@ -19,8 +20,13 @@ import (
|
||||
|
||||
// Queries
|
||||
|
||||
func (r *resolver) Log(ctx context.Context, id string) (models.Log, error) {
|
||||
return logs.FindID(id)
|
||||
func (r *resolver) Log(ctx context.Context, id string) (*models.Log, error) {
|
||||
log, err := logs.FindID(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &log, nil
|
||||
}
|
||||
|
||||
func (r *resolver) Logs(ctx context.Context, filter *logs.Filter) ([]models.Log, error) {
|
||||
@@ -55,10 +61,10 @@ func (r *resolver) Logs(ctx context.Context, filter *logs.Filter) ([]models.Log,
|
||||
|
||||
// Mutations
|
||||
|
||||
func (r *mutationResolver) AddLog(ctx context.Context, input input.LogAddInput) (models.Log, error) {
|
||||
func (r *mutationResolver) AddLog(ctx context.Context, input input.LogAddInput) (*models.Log, error) {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Authenticated() || !token.Permitted("log.add") {
|
||||
return models.Log{}, errors.New("You are not permitted to add logs")
|
||||
return nil, errors.New("You are not permitted to add logs")
|
||||
}
|
||||
|
||||
open := input.Open != nil && *input.Open == true
|
||||
@@ -79,7 +85,7 @@ func (r *mutationResolver) AddLog(ctx context.Context, input input.LogAddInput)
|
||||
|
||||
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())
|
||||
return nil, errors.New("Failed to create log: " + err.Error())
|
||||
}
|
||||
|
||||
go func() {
|
||||
@@ -93,47 +99,91 @@ func (r *mutationResolver) AddLog(ctx context.Context, input input.LogAddInput)
|
||||
changes.Submit("Log", "add", token.UserID, true, changekeys.Listed(log), log)
|
||||
}()
|
||||
|
||||
return log, nil
|
||||
return &log, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) EditLog(ctx context.Context, input input.LogEditInput) (models.Log, error) {
|
||||
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") {
|
||||
return models.Log{}, errors.New("You are not permitted to edit logs")
|
||||
return nil, errors.New("You are not permitted to edit logs")
|
||||
}
|
||||
|
||||
log, err := logs.FindID(input.ID)
|
||||
if err != nil {
|
||||
return models.Log{}, errors.New("Log not found")
|
||||
return nil, errors.New("Log not found")
|
||||
}
|
||||
|
||||
log, err = logs.Edit(log, input.Title, input.Event, input.Description, input.Open)
|
||||
if err != nil {
|
||||
return models.Log{}, errors.New("Failed to edit log: " + err.Error())
|
||||
return nil, errors.New("Failed to edit log: " + err.Error())
|
||||
}
|
||||
|
||||
go changes.Submit("Log", "edit", token.UserID, true, changekeys.Listed(log), log)
|
||||
|
||||
return log, nil
|
||||
return &log, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) RemoveLog(ctx context.Context, input input.LogRemoveInput) (models.Log, error) {
|
||||
func (r *mutationResolver) RemoveLog(ctx context.Context, input input.LogRemoveInput) (*models.Log, error) {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Authenticated() || !token.Permitted("log.remove") {
|
||||
return models.Log{}, errors.New("You are not permitted to remove logs")
|
||||
return nil, errors.New("You are not permitted to remove logs")
|
||||
}
|
||||
|
||||
log, err := logs.FindID(input.ID)
|
||||
if err != nil {
|
||||
return models.Log{}, errors.New("Log not found")
|
||||
return nil, errors.New("Log not found")
|
||||
}
|
||||
|
||||
log, err = logs.Remove(log)
|
||||
if err != nil {
|
||||
return models.Log{}, errors.New("Failed to remove log: " + err.Error())
|
||||
return nil, errors.New("Failed to remove log: " + err.Error())
|
||||
}
|
||||
|
||||
go changes.Submit("Log", "remove", token.UserID, true, changekeys.Listed(log), log)
|
||||
|
||||
return log, nil
|
||||
return &log, nil
|
||||
}
|
||||
|
||||
+25
-20
@@ -16,8 +16,13 @@ import (
|
||||
|
||||
// Queries
|
||||
|
||||
func (r *resolver) Post(ctx context.Context, id string) (models.Post, error) {
|
||||
return posts.FindID(id)
|
||||
func (r *resolver) Post(ctx context.Context, id string) (*models.Post, error) {
|
||||
post, err := posts.FindID(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &post, nil
|
||||
}
|
||||
|
||||
func (r *resolver) Posts(ctx context.Context, filter *posts.Filter) ([]models.Post, error) {
|
||||
@@ -43,37 +48,37 @@ func (r *resolver) Posts(ctx context.Context, filter *posts.Filter) ([]models.Po
|
||||
|
||||
// Mutation
|
||||
|
||||
func (r *mutationResolver) AddPost(ctx context.Context, input input.PostAddInput) (models.Post, error) {
|
||||
func (r *mutationResolver) AddPost(ctx context.Context, input input.PostAddInput) (*models.Post, error) {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Authenticated() || !token.Permitted("post.add") {
|
||||
return models.Post{}, errors.New("You are not permitted to edit logs")
|
||||
return nil, errors.New("You are not permitted to edit logs")
|
||||
}
|
||||
|
||||
log, err := logs.FindID(input.LogID)
|
||||
if err != nil {
|
||||
return models.Post{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
post, err := posts.Add(log, input.Time, input.Kind, input.Nick, input.Text)
|
||||
if err != nil {
|
||||
return models.Post{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go logs.UpdateCharacters(log)
|
||||
go logs.UpdateCharacters(log, nil)
|
||||
go changes.Submit("Post", "add", token.UserID, true, changekeys.Many(log, post), post)
|
||||
|
||||
return post, nil
|
||||
return &post, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) EditPost(ctx context.Context, input input.PostEditInput) (models.Post, error) {
|
||||
func (r *mutationResolver) EditPost(ctx context.Context, input input.PostEditInput) (*models.Post, error) {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Authenticated() || !token.Permitted("post.edit") {
|
||||
return models.Post{}, errors.New("You are not permitted to edit logs")
|
||||
return nil, errors.New("You are not permitted to edit logs")
|
||||
}
|
||||
|
||||
post, err := posts.FindID(input.ID)
|
||||
if err != nil {
|
||||
return models.Post{}, errors.New("Post not found")
|
||||
return nil, errors.New("Post not found")
|
||||
}
|
||||
|
||||
if input.Nick != nil {
|
||||
@@ -83,13 +88,13 @@ func (r *mutationResolver) EditPost(ctx context.Context, input input.PostEditInp
|
||||
return
|
||||
}
|
||||
|
||||
logs.UpdateCharacters(log)
|
||||
logs.UpdateCharacters(log, nil)
|
||||
}()
|
||||
}
|
||||
|
||||
post, err = posts.Edit(post, input.Time, input.Kind, input.Nick, input.Text)
|
||||
if err != nil {
|
||||
return models.Post{}, errors.New("Adding post failed: " + err.Error())
|
||||
return nil, errors.New("Adding post failed: " + err.Error())
|
||||
}
|
||||
|
||||
go func() {
|
||||
@@ -101,7 +106,7 @@ func (r *mutationResolver) EditPost(ctx context.Context, input input.PostEditInp
|
||||
changes.Submit("Post", "edit", token.UserID, true, changekeys.Many(log, post), post)
|
||||
}()
|
||||
|
||||
return post, nil
|
||||
return &post, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) MovePost(ctx context.Context, input input.PostMoveInput) ([]models.Post, error) {
|
||||
@@ -132,20 +137,20 @@ func (r *mutationResolver) MovePost(ctx context.Context, input input.PostMoveInp
|
||||
return posts, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) RemovePost(ctx context.Context, input input.PostRemoveInput) (models.Post, error) {
|
||||
func (r *mutationResolver) RemovePost(ctx context.Context, input input.PostRemoveInput) (*models.Post, error) {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Authenticated() || !token.Permitted("post.remove") {
|
||||
return models.Post{}, errors.New("You are not permitted to edit logs")
|
||||
return nil, errors.New("You are not permitted to edit logs")
|
||||
}
|
||||
|
||||
post, err := posts.FindID(input.ID)
|
||||
if err != nil {
|
||||
return models.Post{}, errors.New("Post not found (before removing, of course)")
|
||||
return nil, errors.New("Post not found (before removing, of course)")
|
||||
}
|
||||
|
||||
post, err = posts.Remove(post)
|
||||
if err != nil {
|
||||
return models.Post{}, errors.New("Could not remove post: " + err.Error())
|
||||
return nil, errors.New("Could not remove post: " + err.Error())
|
||||
}
|
||||
|
||||
go func() {
|
||||
@@ -154,9 +159,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)
|
||||
}()
|
||||
|
||||
return post, nil
|
||||
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
|
||||
|
||||
+47
-30
@@ -15,8 +15,13 @@ import (
|
||||
"git.aiterp.net/rpdata/api/models/stories"
|
||||
)
|
||||
|
||||
func (r *resolver) Story(ctx context.Context, id string) (models.Story, error) {
|
||||
return stories.FindID(id)
|
||||
func (r *resolver) Story(ctx context.Context, id string) (*models.Story, error) {
|
||||
story, err := stories.FindID(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &story, nil
|
||||
}
|
||||
|
||||
func (r *resolver) Stories(ctx context.Context, filter *stories.Filter) ([]models.Story, error) {
|
||||
@@ -38,16 +43,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
|
||||
@@ -63,68 +68,80 @@ func (r *mutationResolver) AddStory(ctx context.Context, input input.StoryAddInp
|
||||
|
||||
story, err := stories.Add(input.Name, author, input.Category, listed, open, input.Tags, time.Now(), fictionalDate)
|
||||
if err != nil {
|
||||
return models.Story{}, errors.New("Failed to add story: " + err.Error())
|
||||
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
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
story, err = stories.AddTag(story, input.Tag)
|
||||
if err != nil {
|
||||
return models.Story{}, errors.New("Failed to add story: " + err.Error())
|
||||
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
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
story, err = stories.RemoveTag(story, input.Tag)
|
||||
if err != nil {
|
||||
return models.Story{}, errors.New("Failed to add story: " + err.Error())
|
||||
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
|
||||
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 {
|
||||
@@ -133,37 +150,37 @@ func (r *mutationResolver) EditStory(ctx context.Context, input input.StoryEditI
|
||||
|
||||
story, err = stories.Edit(story, input.Name, input.Category, input.Listed, input.Open, input.FictionalDate)
|
||||
if err != nil {
|
||||
return models.Story{}, errors.New("Failed to add story: " + err.Error())
|
||||
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
|
||||
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)
|
||||
if err != nil {
|
||||
return models.Story{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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
|
||||
return &story, nil
|
||||
}
|
||||
|
||||
@@ -8,11 +8,11 @@ import (
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
)
|
||||
|
||||
func (r *resolver) Token(ctx context.Context) (models.Token, error) {
|
||||
func (r *resolver) Token(ctx context.Context) (*models.Token, error) {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Authenticated() {
|
||||
return models.Token{}, errors.New("No (valid) token")
|
||||
return nil, errors.New("No (valid) token")
|
||||
}
|
||||
|
||||
return *token, nil
|
||||
return token, 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!]!
|
||||
|
||||
@@ -92,9 +101,22 @@ type Mutation {
|
||||
removeChapter(input: ChapterRemoveInput!): Chapter!
|
||||
|
||||
|
||||
"Add a comment to a chapter."
|
||||
addComment(input: CommentAddInput!): Comment!
|
||||
|
||||
"Edit a comment in a chapter."
|
||||
editComment(input: CommentEditInput!): Comment!
|
||||
|
||||
"Remove a comemnt in a chapter."
|
||||
removeComment(input: CommentRemoveInput!): Comment!
|
||||
|
||||
|
||||
# Add a new log
|
||||
addLog(input: LogAddInput!): Log!
|
||||
|
||||
# Import a log
|
||||
importLog(input: LogImportInput!): [Log!]!
|
||||
|
||||
# Edit a log
|
||||
editLog(input: LogEditInput!): Log!
|
||||
|
||||
@@ -138,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
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ type Change {
|
||||
objects: [ChangeObject!]!
|
||||
}
|
||||
|
||||
union ChangeObject = Character | Channel | Log | Post | Story | Tag | Chapter
|
||||
union ChangeObject = Character | Channel | Log | Post | Story | Tag | Chapter | Comment
|
||||
|
||||
"""
|
||||
A change key is a key associated with a change, used for filtering
|
||||
|
||||
@@ -20,6 +20,35 @@ type Chapter {
|
||||
|
||||
# The date of edit.
|
||||
editedDate: Date!
|
||||
|
||||
"The comment mode."
|
||||
commentMode: ChapterCommentMode!
|
||||
|
||||
"Whether the author has locket comments."
|
||||
commentsLocked: Boolean!
|
||||
|
||||
"A shorthand for checking whether a logged-in user can comment on this chapter."
|
||||
canComment: Boolean!
|
||||
|
||||
"Get all chapter comments."
|
||||
comments(limit: Int): [Comment!]!
|
||||
}
|
||||
|
||||
"""
|
||||
The possibles modes of comments for a chapter.
|
||||
"""
|
||||
enum ChapterCommentMode {
|
||||
"Comments are disabled and hidden."
|
||||
Disabled
|
||||
|
||||
"Comments should be shown as typical news article comments."
|
||||
Article
|
||||
|
||||
"Comments should be shown in a compact form suitable for many small messages."
|
||||
Chat
|
||||
|
||||
"Comments are going to be shown as omni-tool messages."
|
||||
Message
|
||||
}
|
||||
|
||||
# Input for addChapter mutation
|
||||
@@ -41,6 +70,9 @@ input ChapterAddInput {
|
||||
|
||||
# Optionally, assign a fictional date for the chapter
|
||||
fictionalDate: Date
|
||||
|
||||
"The comment mode for the chapter."
|
||||
commentMode: ChapterCommentMode
|
||||
}
|
||||
|
||||
# Input for editChapter mutation
|
||||
@@ -59,6 +91,12 @@ input ChapterEditInput {
|
||||
|
||||
# Remove the fictional date for a chapter
|
||||
clearFictionalDate: Boolean
|
||||
|
||||
"Set the comment mode for the chapter"
|
||||
commentMode: ChapterCommentMode
|
||||
|
||||
"Set whether the chapter comments are locked"
|
||||
commentsLocked: Boolean
|
||||
}
|
||||
|
||||
# Input for moveChapter mutation
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
"""
|
||||
A Comment represents a comment to a story chapter.
|
||||
"""
|
||||
type Comment {
|
||||
"A unique ID of the change."
|
||||
id: String!
|
||||
|
||||
"subject"
|
||||
subject: String!
|
||||
|
||||
"The chapter ID the comment belongs to."
|
||||
chapterId: String!
|
||||
|
||||
"The comment's author."
|
||||
author: String!
|
||||
|
||||
"The displayed name of the character. This may be the same as character.name, but it does not need to be."
|
||||
characterName: String!
|
||||
|
||||
"The character associated with the comment."
|
||||
character: Character
|
||||
|
||||
"The fictional (IC) date of the comment."
|
||||
fictionalDate: Date
|
||||
|
||||
"The date of creation."
|
||||
createdDate: Date!
|
||||
|
||||
"The date of the last edit."
|
||||
editedDate: Date!
|
||||
|
||||
"The markdown source of the comment."
|
||||
source: String!
|
||||
}
|
||||
|
||||
"""
|
||||
Input for the addComment mutation
|
||||
"""
|
||||
input CommentAddInput {
|
||||
"What chapter to comment on."
|
||||
chapterId: String!
|
||||
|
||||
"The markdown source of the comment."
|
||||
source: String!
|
||||
|
||||
"The name of the character associated with this comment."
|
||||
characterName: String!
|
||||
|
||||
"Optioanlly, a character ID."
|
||||
characterId: String
|
||||
|
||||
"Optional in-universe date of the comment."
|
||||
fictionalDate: Date
|
||||
|
||||
"Optional subject to add to the comment, only shown in some modes."
|
||||
subject: String
|
||||
}
|
||||
|
||||
"""
|
||||
Input for the editComment mutation
|
||||
"""
|
||||
input CommentEditInput {
|
||||
"What comment to edit."
|
||||
commentId: String!
|
||||
|
||||
"Change the markdown source."
|
||||
source: String
|
||||
|
||||
"Change the name of the character associated with this comment."
|
||||
characterName: String
|
||||
|
||||
"Change the character assocaited with this comment."
|
||||
characterId: String
|
||||
|
||||
"Change the optional in-universe date of the comment."
|
||||
fictionalDate: Date
|
||||
|
||||
"Set to clear the fictional date."
|
||||
clearFictionalDate: Boolean
|
||||
|
||||
"Change the optional subject for the comment, only shown in some modes."
|
||||
subject: String
|
||||
}
|
||||
|
||||
"""
|
||||
Input for the removeComment mutation
|
||||
"""
|
||||
input CommentRemoveInput {
|
||||
"What comment to edit."
|
||||
commentId: String!
|
||||
}
|
||||
@@ -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!
|
||||
}
|
||||
@@ -12,9 +12,11 @@ type changeResolver struct{}
|
||||
func (r *changeResolver) Objects(ctx context.Context, obj *models.Change) ([]input.ChangeObject, error) {
|
||||
objects := obj.Objects()
|
||||
|
||||
results := make([]input.ChangeObject, len(objects))
|
||||
for i := range objects {
|
||||
results[i] = objects[i]
|
||||
results := make([]input.ChangeObject, 0, len(objects))
|
||||
for _, object := range objects {
|
||||
if changeObject, ok := object.(input.ChangeObject); ok {
|
||||
results = append(results, changeObject)
|
||||
}
|
||||
}
|
||||
|
||||
return results, nil
|
||||
|
||||
@@ -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,16 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
)
|
||||
|
||||
type fileResolver struct{}
|
||||
|
||||
func (r *fileResolver) Size(ctx context.Context, file *models.File) (int, error) {
|
||||
return int(file.Size), nil
|
||||
}
|
||||
|
||||
// FileResolver is a resolver
|
||||
var FileResolver fileResolver
|
||||
+2
-2
@@ -11,10 +11,10 @@ import (
|
||||
|
||||
type logResolver struct{}
|
||||
|
||||
func (r *logResolver) Channel(ctx context.Context, log *models.Log) (models.Channel, error) {
|
||||
func (r *logResolver) Channel(ctx context.Context, log *models.Log) (*models.Channel, error) {
|
||||
loader := loader.FromContext(ctx)
|
||||
if loader == nil {
|
||||
return models.Channel{}, errors.New("no loader")
|
||||
return nil, errors.New("no loader")
|
||||
}
|
||||
|
||||
return loader.Channel("name", log.ChannelName)
|
||||
|
||||
@@ -9,8 +9,13 @@ import (
|
||||
|
||||
type tokenResolver struct{}
|
||||
|
||||
func (r *tokenResolver) User(ctx context.Context, token *models.Token) (models.User, error) {
|
||||
return users.Find(token.UserID)
|
||||
func (r *tokenResolver) User(ctx context.Context, token *models.Token) (*models.User, error) {
|
||||
user, err := users.Find(token.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// TokenResolver is a resolver
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package mirclike_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"git.aiterp.net/rpdata/api/internal/importers/mirclike"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
)
|
||||
|
||||
var testLog = strings.Join([]string{
|
||||
"[11:21] * Va`ynna_Atana returns to the apartment at the end of another long day. She hangs up her jacket and a green scarf next to the door before going straight to the bathroom; she leaves the door open, though. She walked home with Uvena this time, but the two parted ways downstairs.",
|
||||
"",
|
||||
"[11:21] <=Scene=> The weather outside is not pleasant in the slightest. The storm is still going on, visibility is reduced and the worst gusts of wind can be felt inside the apartment as a faint shudder. ",
|
||||
"",
|
||||
"[11:",
|
||||
" [11:27] <Test> Stuff and things.",
|
||||
}, "\r\n")
|
||||
|
||||
var testLogPosts = []models.Post{
|
||||
{
|
||||
ID: "UNASSIGNED",
|
||||
LogID: "UNASSIGNED",
|
||||
Time: parseDate(nil, "2018-05-11 11:21:00"),
|
||||
Kind: "action",
|
||||
Nick: "Va`ynna_Atana",
|
||||
Position: 1,
|
||||
Text: "returns to the apartment at the end of another long day. She hangs up her jacket and a green scarf next to the door before going straight to the bathroom; she leaves the door open, though. She walked home with Uvena this time, but the two parted ways downstairs.",
|
||||
},
|
||||
{
|
||||
ID: "UNASSIGNED",
|
||||
LogID: "UNASSIGNED",
|
||||
Time: parseDate(nil, "2018-05-11 11:21:00"),
|
||||
Kind: "scene",
|
||||
Nick: "=Scene=",
|
||||
Position: 2,
|
||||
Text: "The weather outside is not pleasant in the slightest. The storm is still going on, visibility is reduced and the worst gusts of wind can be felt inside the apartment as a faint shudder.",
|
||||
},
|
||||
{
|
||||
ID: "UNASSIGNED",
|
||||
LogID: "UNASSIGNED",
|
||||
Time: parseDate(nil, "2018-05-11 11:27:00"),
|
||||
Kind: "text",
|
||||
Nick: "Test",
|
||||
Position: 3,
|
||||
Text: "Stuff and things.",
|
||||
},
|
||||
}
|
||||
|
||||
func TestParseLog(t *testing.T) {
|
||||
log, posts, err := mirclike.ParseLog(testLog, parseDate(t, "2018-05-11 00:00:00"), false)
|
||||
if err != nil {
|
||||
t.Fatal("ParseLog", err)
|
||||
}
|
||||
|
||||
assert.Equal(t, testLogPosts, posts)
|
||||
assert.Equal(t, posts[0].Time, log.Date, "Log's date should be the first post's.")
|
||||
}
|
||||
|
||||
func TestParseLogErrors(t *testing.T) {
|
||||
_, _, err1 := mirclike.ParseLog("\n\n\n\n\n \n\t\r\n", time.Time{}, false)
|
||||
_, _, err2 := mirclike.ParseLog("\n\n\n\n\n[14:57]* Stuff \n\t\r\n", time.Time{}, true)
|
||||
|
||||
assert.Equal(t, mirclike.ErrEmptyLog, err1)
|
||||
assert.Equal(t, mirclike.ErrNotPost, err2)
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package mirclike
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
)
|
||||
|
||||
// ErrNotPost is returned by parsePost if the line is empty or not a post.
|
||||
var ErrNotPost = errors.New("not a post")
|
||||
|
||||
// ParsePost parses a post from a mirc-like line. If the previous post is included (it can be empty), it will be used
|
||||
// to determine whether midnight has passed.
|
||||
func ParsePost(line string, date time.Time, prev models.Post) (models.Post, error) {
|
||||
// Do basic validation
|
||||
line = strings.Trim(line, " \t\n\r")
|
||||
if len(line) == 0 || !strings.HasPrefix(line, "[") {
|
||||
return models.Post{}, ErrNotPost
|
||||
}
|
||||
|
||||
// Parse timestamp
|
||||
tsEndIndex := strings.IndexByte(line, ']')
|
||||
if tsEndIndex == -1 || len(line) < tsEndIndex+5 {
|
||||
return models.Post{}, ErrNotPost
|
||||
}
|
||||
tsStr := line[1:tsEndIndex]
|
||||
tsSplit := strings.Split(tsStr, ":")
|
||||
tsUnits := make([]int, len(tsSplit))
|
||||
if len(tsSplit) < 2 {
|
||||
return models.Post{}, ErrNotPost
|
||||
}
|
||||
for i := range tsSplit {
|
||||
n, err := strconv.Atoi(tsSplit[i])
|
||||
if err != nil {
|
||||
return models.Post{}, ErrNotPost
|
||||
}
|
||||
|
||||
tsUnits[i] = n
|
||||
}
|
||||
if len(tsUnits) == 2 {
|
||||
tsUnits = append(tsUnits, 0)
|
||||
}
|
||||
|
||||
// Determine timestamp from parsed data and previous post.
|
||||
ts := time.Date(date.Year(), date.Month(), date.Day(), tsUnits[0], tsUnits[1], tsUnits[2], 0, date.Location())
|
||||
if !prev.Time.IsZero() && prev.Time.Sub(ts) > 30*time.Minute {
|
||||
ts = time.Date(prev.Time.Year(), prev.Time.Month(), prev.Time.Day()+1, tsUnits[0], tsUnits[1], tsUnits[2], 0, date.Location())
|
||||
}
|
||||
|
||||
if line[tsEndIndex+2] == '*' {
|
||||
split := strings.SplitN(line[tsEndIndex+4:], " ", 2)
|
||||
if len(split) == 1 {
|
||||
return models.Post{}, ErrNotPost
|
||||
}
|
||||
|
||||
post := models.Post{
|
||||
ID: "UNASSIGNED",
|
||||
LogID: "UNASSIGNED",
|
||||
Time: ts,
|
||||
Kind: "action",
|
||||
Nick: strings.TrimLeft(split[0], "+@!~"),
|
||||
Text: split[1],
|
||||
Position: prev.Position + 1,
|
||||
}
|
||||
|
||||
if post.Nick[0] == '=' {
|
||||
post.Kind = "scene"
|
||||
}
|
||||
|
||||
return post, nil
|
||||
} else if line[tsEndIndex+2] == '<' {
|
||||
split := strings.SplitN(line[tsEndIndex+2:], " ", 2)
|
||||
if len(split) == 1 {
|
||||
return models.Post{}, ErrNotPost
|
||||
}
|
||||
|
||||
post := models.Post{
|
||||
ID: "UNASSIGNED",
|
||||
LogID: "UNASSIGNED",
|
||||
Time: ts,
|
||||
Kind: "text",
|
||||
Nick: strings.TrimLeft(split[0][1:len(split[0])-1], "+@!~"),
|
||||
Text: split[1],
|
||||
Position: prev.Position + 1,
|
||||
}
|
||||
|
||||
if post.Nick[0] == '=' {
|
||||
post.Kind = "scene"
|
||||
}
|
||||
|
||||
return post, nil
|
||||
} else {
|
||||
return models.Post{}, ErrNotPost
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package mirclike_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"git.aiterp.net/rpdata/api/internal/importers/mirclike"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
)
|
||||
|
||||
func TestParsePost(t *testing.T) {
|
||||
table := []struct {
|
||||
Input string
|
||||
TS string
|
||||
Kind string
|
||||
Nick string
|
||||
Text string
|
||||
}{
|
||||
{
|
||||
"[12:34] * Stuff does things.",
|
||||
"12:34:00", "action", "Stuff", "does things.",
|
||||
},
|
||||
{
|
||||
"[12:34] <Stuff> Things said.",
|
||||
"12:34:00", "text", "Stuff", "Things said.",
|
||||
},
|
||||
{
|
||||
"[13:36:59] <Stuff> Things said.",
|
||||
"13:36:59", "text", "Stuff", "Things said.",
|
||||
},
|
||||
{
|
||||
"[23:59] <=Scene=> Scenery and such.",
|
||||
"23:59:00", "scene", "=Scene=", "Scenery and such.",
|
||||
},
|
||||
{
|
||||
"[01:10:11] * =Scene= Scenery and such from the forum or mIRC using my old script.",
|
||||
"01:10:11", "scene", "=Scene=", "Scenery and such from the forum or mIRC using my old script.",
|
||||
},
|
||||
}
|
||||
|
||||
for i, row := range table {
|
||||
t.Run(fmt.Sprintf("Row_%d", i), func(t *testing.T) {
|
||||
post, err := mirclike.ParsePost(row.Input, time.Now(), models.Post{})
|
||||
if err != nil {
|
||||
t.Fatal("Could not parse post:", err)
|
||||
}
|
||||
|
||||
assert.Equal(t, row.TS, post.Time.Format("15:04:05"), "Timestamps should match.")
|
||||
assert.Equal(t, row.Kind, post.Kind, "Kinds should match.")
|
||||
assert.Equal(t, row.Nick, post.Nick, "Kinds should match.")
|
||||
assert.Equal(t, row.Text, post.Text, "Kinds should match.")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePostErrors(t *testing.T) {
|
||||
table := []struct {
|
||||
Input string
|
||||
Err error
|
||||
}{
|
||||
{"[12:34] <Stuff> Things said.", nil},
|
||||
{"[12:34] >Stuff> Things said.", mirclike.ErrNotPost},
|
||||
{"12:34] <Stuff> Things said.", mirclike.ErrNotPost},
|
||||
{"* Stuff Things said.", mirclike.ErrNotPost},
|
||||
{"", mirclike.ErrNotPost},
|
||||
{"[12:34 <Stuff> Things said.", mirclike.ErrNotPost},
|
||||
{"[TE:XT] <Stuff> Things said.", mirclike.ErrNotPost},
|
||||
{"[10] <Stuff> Things said.", mirclike.ErrNotPost},
|
||||
{"[12:34:56:789] <Stuff> Things said.", nil},
|
||||
{"[12:34:56.789] <Stuff> Things said.", mirclike.ErrNotPost},
|
||||
{"[12:34] <Stuff>", mirclike.ErrNotPost},
|
||||
{"[12:34] * Stuff", mirclike.ErrNotPost},
|
||||
{"[12:34] =Scene=", mirclike.ErrNotPost},
|
||||
{"[12:34] <=Scene=>", mirclike.ErrNotPost},
|
||||
}
|
||||
|
||||
for i, row := range table {
|
||||
t.Run(fmt.Sprintf("Row_%d", i), func(t *testing.T) {
|
||||
_, err := mirclike.ParsePost(row.Input, time.Now(), models.Post{})
|
||||
|
||||
assert.Equal(t, row.Err, err, "Error should match")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseNextDay(t *testing.T) {
|
||||
table := []struct {
|
||||
Prev time.Time
|
||||
TS string
|
||||
Time time.Time
|
||||
}{
|
||||
{Prev: parseDate(t, "2019-01-12 12:00:00"), TS: "12:01", Time: parseDate(t, "2019-01-12 12:01:00")},
|
||||
{Prev: parseDate(t, "2019-01-12 12:00:00"), TS: "11:53:13", Time: parseDate(t, "2019-01-12 11:53:13")},
|
||||
{Prev: parseDate(t, "2019-04-08 23:51:59"), TS: "00:09", Time: parseDate(t, "2019-04-09 00:09:00")},
|
||||
{Prev: parseDate(t, "2019-01-12 12:00:00"), TS: "11:29:59", Time: parseDate(t, "2019-01-13 11:29:59")},
|
||||
}
|
||||
|
||||
for i, row := range table {
|
||||
t.Run(fmt.Sprintf("Row_%d", i), func(t *testing.T) {
|
||||
input := fmt.Sprintf("[%s] * Stuff does things.", row.TS)
|
||||
|
||||
post, err := mirclike.ParsePost(input, row.Prev, models.Post{Time: row.Prev})
|
||||
if err != nil {
|
||||
t.Fatal("Could not parse post:", err)
|
||||
}
|
||||
|
||||
assert.Equal(t, row.Time, post.Time)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func formatDate(date time.Time) string {
|
||||
return date.UTC().Format("2006-01-02 15:04:05")
|
||||
}
|
||||
@@ -11,13 +11,13 @@ import (
|
||||
)
|
||||
|
||||
// Channel gets a character by key
|
||||
func (loader *Loader) Channel(key, value string) (models.Channel, error) {
|
||||
func (loader *Loader) Channel(key, value string) (*models.Channel, error) {
|
||||
if !strings.HasPrefix(key, "Channel.") {
|
||||
key = "Channel." + key
|
||||
}
|
||||
|
||||
if loader.loaders[key] == nil {
|
||||
return models.Channel{}, errors.New("unsupported key")
|
||||
return nil, errors.New("unsupported key")
|
||||
}
|
||||
|
||||
loader.loadPrimed(key)
|
||||
@@ -25,15 +25,15 @@ func (loader *Loader) Channel(key, value string) (models.Channel, error) {
|
||||
thunk := loader.loaders[key].Load(loader.ctx, dataloader.StringKey(value))
|
||||
res, err := thunk()
|
||||
if err != nil {
|
||||
return models.Channel{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
channel, ok := res.(models.Channel)
|
||||
if !ok {
|
||||
return channel, errors.New("incorrect type")
|
||||
return nil, errors.New("incorrect type")
|
||||
}
|
||||
|
||||
return channel, nil
|
||||
return &channel, nil
|
||||
}
|
||||
|
||||
// PrimeChannels primes channels for loading along with the first one.
|
||||
|
||||
@@ -26,9 +26,11 @@ func Init() error {
|
||||
}
|
||||
|
||||
sconf := conf.Space
|
||||
err = ConnectSpace(sconf.Host, sconf.AccessKey, sconf.SecretKey, sconf.Bucket, sconf.MaxSize, sconf.Root)
|
||||
if err != nil {
|
||||
return err
|
||||
if sconf.Enabled {
|
||||
err = ConnectSpace(sconf.Host, sconf.AccessKey, sconf.SecretKey, sconf.Bucket, sconf.MaxSize, sconf.Root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
hasInitialized = true
|
||||
|
||||
@@ -43,6 +43,10 @@ func ConnectSpace(host, accessKey, secretKey, bucket string, maxSize int64, root
|
||||
// UploadFile uploads the file to the space. This does not do any checks on it, so the endpoints should
|
||||
// ensure that's all okay.
|
||||
func UploadFile(ctx context.Context, folder string, name string, mimeType string, reader io.Reader, size int64) (string, error) {
|
||||
if spaceClient == nil {
|
||||
return "", errors.New("This functionality is not enabled")
|
||||
}
|
||||
|
||||
path := folder + "/" + name
|
||||
|
||||
if size > spaceMaxSize {
|
||||
@@ -69,6 +73,10 @@ func UploadFile(ctx context.Context, folder string, name string, mimeType string
|
||||
|
||||
// RemoveFile removes a file from the space
|
||||
func RemoveFile(folder string, name string) error {
|
||||
if spaceClient == nil {
|
||||
return errors.New("This functionality is not enabled")
|
||||
}
|
||||
|
||||
path := folder + "/" + name
|
||||
|
||||
return spaceClient.RemoveObject(spaceBucket, spaceRoot+"/"+path)
|
||||
@@ -76,6 +84,10 @@ func RemoveFile(folder string, name string) error {
|
||||
|
||||
// DownloadFile opens a file for download, using the same path format as the UploadFile function. Remember to Close it!
|
||||
func DownloadFile(ctx context.Context, path string) (io.ReadCloser, error) {
|
||||
if spaceClient == nil {
|
||||
return nil, errors.New("This functionality is not enabled")
|
||||
}
|
||||
|
||||
return spaceClient.GetObjectWithContext(ctx, spaceBucket, spaceRoot+"/"+path, minio.GetObjectOptions{})
|
||||
}
|
||||
|
||||
|
||||
@@ -24,12 +24,14 @@ const (
|
||||
ChangeModelTag ChangeModel = "Tag"
|
||||
// ChangeModelChapter is a value of ChangeModel
|
||||
ChangeModelChapter ChangeModel = "Chapter"
|
||||
// ChangeModelComment is a value of ChangeModel
|
||||
ChangeModelComment ChangeModel = "Comment"
|
||||
)
|
||||
|
||||
// IsValid returns true if the underlying string is one of the correct values.
|
||||
func (e ChangeModel) IsValid() bool {
|
||||
switch e {
|
||||
case ChangeModelCharacter, ChangeModelChannel, ChangeModelLog, ChangeModelPost, ChangeModelStory, ChangeModelTag, ChangeModelChapter:
|
||||
case ChangeModelCharacter, ChangeModelChannel, ChangeModelLog, ChangeModelPost, ChangeModelStory, ChangeModelTag, ChangeModelChapter, ChangeModelComment:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
+5
-1
@@ -19,6 +19,7 @@ type Change struct {
|
||||
Stories []Story `bson:"stories"`
|
||||
Tags []Tag `bson:"tags"`
|
||||
Chapters []Chapter `bson:"chapters"`
|
||||
Comments []Comment `bson:"comments"`
|
||||
}
|
||||
|
||||
// ChangeKey is a key for a change that can be used when subscribing to them.
|
||||
@@ -29,7 +30,7 @@ type ChangeKey struct {
|
||||
|
||||
// Objects makes a combined, mixed array of all the models stored in this change.
|
||||
func (change *Change) Objects() []interface{} {
|
||||
data := make([]interface{}, 0, len(change.Logs)+len(change.Characters)+len(change.Channels)+len(change.Posts)+len(change.Stories)+len(change.Tags)+len(change.Chapters))
|
||||
data := make([]interface{}, 0, len(change.Logs)+len(change.Characters)+len(change.Channels)+len(change.Posts)+len(change.Stories)+len(change.Tags)+len(change.Chapters)+len(change.Comments))
|
||||
|
||||
for _, log := range change.Logs {
|
||||
data = append(data, log)
|
||||
@@ -52,6 +53,9 @@ func (change *Change) Objects() []interface{} {
|
||||
for _, chapter := range change.Chapters {
|
||||
data = append(data, chapter)
|
||||
}
|
||||
for _, comment := range change.Comments {
|
||||
data = append(data, comment)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ func TestMany(t *testing.T) {
|
||||
},
|
||||
},
|
||||
models.Post{ID: "P5"},
|
||||
models.Comment{ID: "SCC1234"},
|
||||
}
|
||||
|
||||
expectations := []models.ChangeKey{
|
||||
@@ -33,6 +34,7 @@ func TestMany(t *testing.T) {
|
||||
{Model: "Character", ID: "C17"},
|
||||
{Model: "Channel", ID: "#Stuff"},
|
||||
{Model: "Post", ID: "P5"},
|
||||
{Model: "Comment", ID: "SCC1234"},
|
||||
}
|
||||
|
||||
results := changekeys.Many(data...)
|
||||
|
||||
@@ -28,6 +28,11 @@ func TestOne(t *testing.T) {
|
||||
models.Log{ID: "2018-10-23_210303325_RedrockAgency", ShortID: "L807"},
|
||||
models.ChangeKey{Model: "Log", ID: "2018-10-23_210303325_RedrockAgency"},
|
||||
},
|
||||
{
|
||||
"Comment_SCC1234",
|
||||
models.Comment{ID: "SCC1234"},
|
||||
models.ChangeKey{Model: "Comment", ID: "SCC1234"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, row := range table {
|
||||
|
||||
@@ -16,7 +16,7 @@ var submitMutex sync.Mutex
|
||||
|
||||
func list(query bson.M, limit int) ([]models.Change, error) {
|
||||
changes := make([]models.Change, 0, 64)
|
||||
err := collection.Find(query).Limit(limit).Sort("date").All(&changes)
|
||||
err := collection.Find(query).Limit(limit).Sort("-date").All(&changes)
|
||||
|
||||
return changes, err
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package changes
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
@@ -80,10 +81,16 @@ func Submit(model, op, author string, listed bool, keys []models.ChangeKey, obje
|
||||
change.Chapters = append(change.Chapters, object)
|
||||
case []models.Chapter:
|
||||
change.Chapters = append(change.Chapters, object...)
|
||||
case models.Comment:
|
||||
change.Comments = append(change.Comments, object)
|
||||
case []models.Comment:
|
||||
change.Comments = append(change.Comments, object...)
|
||||
default:
|
||||
log.Printf("Warning: unrecognized object in change: %#+v", object)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Push to subscribers
|
||||
pushToSubscribers(change)
|
||||
|
||||
err = collection.Insert(&change)
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package changes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
)
|
||||
|
||||
var subMutex sync.Mutex
|
||||
var subList []*subscription
|
||||
|
||||
type subscription struct {
|
||||
Keys map[string]bool
|
||||
Channel chan<- *models.Change
|
||||
WildCard bool
|
||||
}
|
||||
|
||||
// Subscribe subscribes to all changes.
|
||||
func Subscribe(ctx context.Context, keys []models.ChangeKey, wildcard bool) <-chan *models.Change {
|
||||
channel := make(chan *models.Change, 64)
|
||||
sub := &subscription{
|
||||
Keys: make(map[string]bool, len(keys)),
|
||||
Channel: channel,
|
||||
WildCard: wildcard,
|
||||
}
|
||||
|
||||
for _, key := range keys {
|
||||
sub.Keys[mapKey(key)] = true
|
||||
}
|
||||
|
||||
subMutex.Lock()
|
||||
subList = append(subList, sub)
|
||||
subMutex.Unlock()
|
||||
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
|
||||
subMutex.Lock()
|
||||
for i := range subList {
|
||||
if subList[i] == sub {
|
||||
subList = append(subList[:i], subList[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
subMutex.Unlock()
|
||||
}()
|
||||
|
||||
return channel
|
||||
}
|
||||
|
||||
func pushToSubscribers(change models.Change) {
|
||||
keys := make([]string, len(change.Keys))
|
||||
for i := range change.Keys {
|
||||
keys[i] = mapKey(change.Keys[i])
|
||||
}
|
||||
|
||||
subMutex.Lock()
|
||||
SubLoop:
|
||||
for _, sub := range subList {
|
||||
changeCopy := change
|
||||
|
||||
if sub.WildCard && change.Listed {
|
||||
select {
|
||||
case sub.Channel <- &changeCopy:
|
||||
default:
|
||||
}
|
||||
} else {
|
||||
for _, key := range keys {
|
||||
if sub.Keys[key] {
|
||||
select {
|
||||
case sub.Channel <- &changeCopy:
|
||||
default:
|
||||
}
|
||||
|
||||
continue SubLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
subMutex.Unlock()
|
||||
}
|
||||
|
||||
func mapKey(ck models.ChangeKey) string {
|
||||
return ck.Model.String() + "." + ck.ID
|
||||
}
|
||||
|
||||
func init() {
|
||||
subList = make([]*subscription, 0, 16)
|
||||
}
|
||||
@@ -8,3 +8,9 @@ type Channel struct {
|
||||
EventName string `bson:"eventName,omitempty"`
|
||||
LocationName string `bson:"locationName,omitempty"`
|
||||
}
|
||||
|
||||
// IsChangeObject is an interface implementation to identify it as a valid
|
||||
// ChangeObject in GQL.
|
||||
func (*Channel) IsChangeObject() {
|
||||
panic("this method is a dummy, and so is its caller")
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// ChapterCommentMode represents the kind of tags.
|
||||
type ChapterCommentMode string
|
||||
|
||||
const (
|
||||
// ChapterCommentModeDisabled is a chapter comment mode, see GraphQL documentation.
|
||||
ChapterCommentModeDisabled ChapterCommentMode = "Disabled"
|
||||
|
||||
// ChapterCommentModeArticle is a chapter comment mode, see GraphQL documentation.
|
||||
ChapterCommentModeArticle ChapterCommentMode = "Article"
|
||||
|
||||
// ChapterCommentModeChat is a chapter comment mode, see GraphQL documentation.
|
||||
ChapterCommentModeChat ChapterCommentMode = "Chat"
|
||||
|
||||
// ChapterCommentModeMessage is a chapter comment mode, see GraphQL documentation.
|
||||
ChapterCommentModeMessage ChapterCommentMode = "Message"
|
||||
)
|
||||
|
||||
// UnmarshalGQL unmarshals
|
||||
func (e *ChapterCommentMode) UnmarshalGQL(v interface{}) error {
|
||||
str, ok := v.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("enums must be strings")
|
||||
}
|
||||
|
||||
*e = ChapterCommentMode(str)
|
||||
switch *e {
|
||||
case ChapterCommentModeDisabled, ChapterCommentModeArticle, ChapterCommentModeChat, ChapterCommentModeMessage:
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("%s is not a valid ChapterCommentMode", str)
|
||||
}
|
||||
}
|
||||
|
||||
// IsEnabled returns true if comments are enabled.
|
||||
func (e ChapterCommentMode) IsEnabled() bool {
|
||||
return e != ChapterCommentModeDisabled && len(e) != 0
|
||||
}
|
||||
|
||||
// MarshalGQL turns it into a JSON string
|
||||
func (e ChapterCommentMode) MarshalGQL(w io.Writer) {
|
||||
// Backwards compatibility: Empty value means disabled.
|
||||
if len(e) == 0 {
|
||||
w.Write(disabledChapterCommentModeBytes)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprint(w, "\""+string(e)+"\"")
|
||||
}
|
||||
|
||||
var disabledChapterCommentModeBytes = []byte(`"Disabled"`)
|
||||
+21
-8
@@ -4,12 +4,25 @@ import "time"
|
||||
|
||||
// A Chapter is a part of a story.
|
||||
type Chapter struct {
|
||||
ID string `bson:"_id"`
|
||||
StoryID string `bson:"storyId"`
|
||||
Title string `bson:"title"`
|
||||
Author string `bson:"author"`
|
||||
Source string `bson:"source"`
|
||||
CreatedDate time.Time `bson:"createdDate"`
|
||||
FictionalDate time.Time `bson:"fictionalDate,omitempty"`
|
||||
EditedDate time.Time `bson:"editedDate"`
|
||||
ID string `bson:"_id"`
|
||||
StoryID string `bson:"storyId"`
|
||||
Title string `bson:"title"`
|
||||
Author string `bson:"author"`
|
||||
Source string `bson:"source"`
|
||||
CreatedDate time.Time `bson:"createdDate"`
|
||||
FictionalDate time.Time `bson:"fictionalDate,omitempty"`
|
||||
EditedDate time.Time `bson:"editedDate"`
|
||||
CommentMode ChapterCommentMode `bson:"commentMode"`
|
||||
CommentsLocked bool `bson:"commentsLocked"`
|
||||
}
|
||||
|
||||
// CanComment returns true if the chapter can be commented to.
|
||||
func (chapter *Chapter) CanComment() bool {
|
||||
return !chapter.CommentsLocked && chapter.CommentMode.IsEnabled()
|
||||
}
|
||||
|
||||
// IsChangeObject is an interface implementation to identify it as a valid
|
||||
// ChangeObject in GQL.
|
||||
func (*Chapter) IsChangeObject() {
|
||||
panic("this method is a dummy, and so is its caller")
|
||||
}
|
||||
+10
-8
@@ -8,15 +8,17 @@ import (
|
||||
)
|
||||
|
||||
// Add adds a new chapter.
|
||||
func Add(story models.Story, title, author, source string, createdDate time.Time, finctionalDate *time.Time) (models.Chapter, error) {
|
||||
func Add(story models.Story, title, author, source string, createdDate time.Time, finctionalDate *time.Time, commentMode models.ChapterCommentMode) (models.Chapter, error) {
|
||||
chapter := models.Chapter{
|
||||
ID: makeChapterID(),
|
||||
StoryID: story.ID,
|
||||
Title: title,
|
||||
Author: author,
|
||||
Source: source,
|
||||
CreatedDate: createdDate,
|
||||
EditedDate: createdDate,
|
||||
ID: makeChapterID(),
|
||||
StoryID: story.ID,
|
||||
Title: title,
|
||||
Author: author,
|
||||
Source: source,
|
||||
CreatedDate: createdDate,
|
||||
EditedDate: createdDate,
|
||||
CommentMode: commentMode,
|
||||
CommentsLocked: false,
|
||||
}
|
||||
|
||||
if finctionalDate != nil {
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
// Edit edits a chapter, and updates EditedDate. While many Edit functions cheat if there's nothing to
|
||||
// change, this functill will due to EditedDate.
|
||||
func Edit(chapter models.Chapter, title, source *string, fictionalDate *time.Time) (models.Chapter, error) {
|
||||
func Edit(chapter models.Chapter, title, source *string, fictionalDate *time.Time, commentMode *models.ChapterCommentMode, commentsLocked *bool) (models.Chapter, error) {
|
||||
now := time.Now()
|
||||
changes := bson.M{"editedDate": now}
|
||||
|
||||
@@ -28,6 +28,14 @@ func Edit(chapter models.Chapter, title, source *string, fictionalDate *time.Tim
|
||||
changes["fictionalDate"] = *fictionalDate
|
||||
edited.FictionalDate = *fictionalDate
|
||||
}
|
||||
if commentMode != nil && *commentMode != chapter.CommentMode {
|
||||
changes["commentMode"] = *commentMode
|
||||
edited.CommentMode = *commentMode
|
||||
}
|
||||
if commentsLocked != nil && *commentsLocked != chapter.CommentsLocked {
|
||||
changes["commentsLocked"] = *commentsLocked
|
||||
edited.CommentsLocked = *commentsLocked
|
||||
}
|
||||
|
||||
err := collection.UpdateId(chapter.ID, bson.M{"$set": changes})
|
||||
if err != nil {
|
||||
|
||||
@@ -29,3 +29,9 @@ func (character *Character) HasNick(nick string) bool {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IsChangeObject is an interface implementation to identify it as a valid
|
||||
// ChangeObject in GQL.
|
||||
func (*Character) IsChangeObject() {
|
||||
panic("this method is a dummy, and so is its caller")
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
// A Comment is a comment on a chapter.
|
||||
type Comment struct {
|
||||
ID string `bson:"_id"`
|
||||
ChapterID string `bson:"chapterId"`
|
||||
Subject string `bson:"subject"`
|
||||
Author string `bson:"author"`
|
||||
CharacterName string `bson:"characterName"`
|
||||
CharacterID string `bson:"characterId"`
|
||||
FictionalDate time.Time `bson:"fictionalDate"`
|
||||
CreatedDate time.Time `bson:"createdDate"`
|
||||
EditedDate time.Time `bson:"editeddDate"`
|
||||
Source string `bson:"source"`
|
||||
}
|
||||
|
||||
// IsChangeObject is an interface implementation to identify it as a valid
|
||||
// ChangeObject in GQL.
|
||||
func (*Comment) IsChangeObject() {
|
||||
panic("this method is a dummy, and so is its caller")
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package comments
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
)
|
||||
|
||||
// Add adds a comment.
|
||||
func Add(chapter models.Chapter, subject, author, source, characterName string, character *models.Character, createdDate time.Time, fictionalDate time.Time) (models.Comment, error) {
|
||||
characterID := ""
|
||||
if character != nil {
|
||||
characterID = character.ID
|
||||
}
|
||||
|
||||
comment := models.Comment{
|
||||
ID: makeCommentID(),
|
||||
ChapterID: chapter.ID,
|
||||
Subject: subject,
|
||||
Author: author,
|
||||
CharacterName: characterName,
|
||||
CharacterID: characterID,
|
||||
FictionalDate: fictionalDate,
|
||||
CreatedDate: createdDate,
|
||||
EditedDate: createdDate,
|
||||
Source: source,
|
||||
}
|
||||
|
||||
err := collection.Insert(comment)
|
||||
if err != nil {
|
||||
return models.Comment{}, err
|
||||
}
|
||||
|
||||
return comment, nil
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package comments
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"strconv"
|
||||
|
||||
"git.aiterp.net/rpdata/api/internal/store"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"github.com/globalsign/mgo"
|
||||
)
|
||||
|
||||
var collection *mgo.Collection
|
||||
|
||||
func find(query interface{}) (models.Comment, error) {
|
||||
comment := models.Comment{}
|
||||
err := collection.Find(query).One(&comment)
|
||||
|
||||
return comment, err
|
||||
}
|
||||
|
||||
func list(query interface{}, limit int) ([]models.Comment, error) {
|
||||
allocSize := 32
|
||||
if limit >= 0 {
|
||||
allocSize = limit
|
||||
} else {
|
||||
limit = 0
|
||||
}
|
||||
|
||||
comments := make([]models.Comment, 0, allocSize)
|
||||
err := collection.Find(query).Sort("createdDate").Limit(limit).All(&comments)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return comments, nil
|
||||
}
|
||||
|
||||
func makeCommentID() string {
|
||||
result := "SCC"
|
||||
offset := 0
|
||||
data := make([]byte, 48)
|
||||
|
||||
rand.Read(data)
|
||||
for len(result) < 32 {
|
||||
result += strconv.FormatUint(binary.LittleEndian.Uint64(data[offset:]), 36)
|
||||
offset += 8
|
||||
|
||||
if offset >= 48 {
|
||||
rand.Read(data)
|
||||
offset = 0
|
||||
}
|
||||
}
|
||||
|
||||
return result[:32]
|
||||
}
|
||||
|
||||
func init() {
|
||||
store.HandleInit(func(db *mgo.Database) {
|
||||
collection = db.C("story.comments")
|
||||
|
||||
collection.EnsureIndexKey("chapterId")
|
||||
collection.EnsureIndexKey("author")
|
||||
collection.EnsureIndexKey("createdDate")
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package comments
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"github.com/globalsign/mgo/bson"
|
||||
)
|
||||
|
||||
// Edit edits a comment.
|
||||
func Edit(comment models.Comment, source, characterName, characterID, subject *string, fictionalDate *time.Time) (models.Comment, error) {
|
||||
changes := make(bson.M, 6)
|
||||
|
||||
comment.EditedDate = time.Now()
|
||||
changes["editedDate"] = comment.EditedDate
|
||||
|
||||
if source != nil {
|
||||
comment.Source = *source
|
||||
changes["source"] = *source
|
||||
}
|
||||
if characterName != nil {
|
||||
comment.CharacterName = *characterName
|
||||
changes["characterName"] = *characterName
|
||||
}
|
||||
if characterID != nil {
|
||||
comment.CharacterID = *characterID
|
||||
changes["characterId"] = *characterID
|
||||
}
|
||||
if subject != nil {
|
||||
comment.Subject = *subject
|
||||
changes["subject"] = *subject
|
||||
}
|
||||
if fictionalDate != nil {
|
||||
comment.FictionalDate = *fictionalDate
|
||||
changes["fictionalDate"] = *fictionalDate
|
||||
}
|
||||
|
||||
err := collection.UpdateId(comment.ID, bson.M{"$set": changes})
|
||||
if err != nil {
|
||||
return models.Comment{}, err
|
||||
}
|
||||
|
||||
return comment, nil
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package comments
|
||||
|
||||
import (
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"github.com/globalsign/mgo/bson"
|
||||
)
|
||||
|
||||
// Find finds a comment by ID.
|
||||
func Find(id string) (models.Comment, error) {
|
||||
return find(bson.M{"_id": id})
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package comments
|
||||
|
||||
import (
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"github.com/globalsign/mgo/bson"
|
||||
)
|
||||
|
||||
// ListChapterID lists all comments by chapter-ID
|
||||
func ListChapterID(chapterID string, limit int) ([]models.Comment, error) {
|
||||
return list(bson.M{"chapterId": chapterID}, limit)
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package comments
|
||||
|
||||
import (
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"github.com/globalsign/mgo/bson"
|
||||
)
|
||||
|
||||
// Remove removes one comment.
|
||||
func Remove(comment models.Comment) error {
|
||||
return collection.RemoveId(comment.ID)
|
||||
}
|
||||
|
||||
// RemoveChapter removes all comments for the given chapter.
|
||||
func RemoveChapter(chapter models.Chapter) error {
|
||||
_, err := collection.RemoveAll(bson.M{"chapterId": chapter.ID})
|
||||
return err
|
||||
}
|
||||
@@ -14,3 +14,9 @@ type File struct {
|
||||
Author string `bson:"author" json:"author"`
|
||||
URL string `bson:"url,omitempty" json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// IsChangeObject is an interface implementation to identify it as a valid
|
||||
// ChangeObject in GQL.
|
||||
func (*File) IsChangeObject() {
|
||||
panic("this method is a dummy, and so is its caller")
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// LogImporter describes a model related log importing.
|
||||
type LogImporter string
|
||||
|
||||
const (
|
||||
// LogImporterMircLike is a value of LogImporter
|
||||
LogImporterMircLike LogImporter = "MircLike"
|
||||
// LogImporterForumLog is a value of LogImporter
|
||||
LogImporterForumLog LogImporter = "ForumLog"
|
||||
)
|
||||
|
||||
// IsValid returns true if the underlying string is one of the correct values.
|
||||
func (e LogImporter) IsValid() bool {
|
||||
switch e {
|
||||
case LogImporterForumLog, LogImporterMircLike:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (e LogImporter) String() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
// UnmarshalGQL unmarshals the underlying graphql value.
|
||||
func (e *LogImporter) UnmarshalGQL(v interface{}) error {
|
||||
str, ok := v.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("enums must be strings")
|
||||
}
|
||||
|
||||
*e = LogImporter(str)
|
||||
if !e.IsValid() {
|
||||
return fmt.Errorf("%s is not a valid LogImporter", str)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalGQL marshals the underlying graphql value.
|
||||
func (e LogImporter) MarshalGQL(w io.Writer) {
|
||||
fmt.Fprint(w, strconv.Quote(e.String()))
|
||||
}
|
||||
@@ -14,3 +14,9 @@ type Log struct {
|
||||
Open bool `bson:"open"`
|
||||
CharacterIDs []string `bson:"characterIds"`
|
||||
}
|
||||
|
||||
// IsChangeObject is an interface implementation to identify it as a valid
|
||||
// ChangeObject in GQL.
|
||||
func (*Log) IsChangeObject() {
|
||||
panic("this method is a dummy, and so is its caller")
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package logs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/internal/importers/mirclike"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models/posts"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models/channels"
|
||||
|
||||
"git.aiterp.net/rpdata/api/internal/importers/forumlog"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
)
|
||||
|
||||
// An ImportedLog contains data about an imported log.
|
||||
type ImportedLog struct {
|
||||
Log models.Log
|
||||
Posts []models.Post
|
||||
}
|
||||
|
||||
// Import makes a log and posts object from different formats.
|
||||
func Import(importer models.LogImporter, date time.Time, tz *time.Location, channelName string, data string) ([]ImportedLog, error) {
|
||||
results := make([]ImportedLog, 0, 8)
|
||||
|
||||
eventName := ""
|
||||
if channel, err := channels.FindName(channelName); err != nil {
|
||||
eventName = channel.EventName
|
||||
}
|
||||
|
||||
date = date.In(tz)
|
||||
|
||||
switch importer {
|
||||
case models.LogImporterMircLike:
|
||||
{
|
||||
if date.IsZero() {
|
||||
return nil, errors.New("Date is not optional for mirc-like logs")
|
||||
}
|
||||
|
||||
parsedLog, parsedPosts, err := mirclike.ParseLog(data, date, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log, err := Add(parsedLog.Date, channelName, "", eventName, "", false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
posts, err := posts.AddMany(log, parsedPosts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
results = append(results, ImportedLog{
|
||||
Log: log,
|
||||
Posts: posts,
|
||||
})
|
||||
}
|
||||
case models.LogImporterForumLog:
|
||||
{
|
||||
parseResults, err := forumlog.ParseLogs(data, tz)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, result := range parseResults {
|
||||
log, err := Add(result.Log.Date, channelName, "", eventName, "", false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
posts, err := posts.AddMany(log, result.Posts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
results = append(results, ImportedLog{
|
||||
Log: log,
|
||||
Posts: posts,
|
||||
})
|
||||
}
|
||||
}
|
||||
default:
|
||||
{
|
||||
return nil, errors.New("Invalid importer: " + importer.String())
|
||||
}
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package logs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -8,23 +9,25 @@ import (
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"git.aiterp.net/rpdata/api/models/characters"
|
||||
"git.aiterp.net/rpdata/api/models/posts"
|
||||
"git.aiterp.net/rpdata/api/models/unknownnicks"
|
||||
"github.com/globalsign/mgo/bson"
|
||||
)
|
||||
|
||||
var updateTask = task.New(time.Second*60, RunFullUpdate)
|
||||
|
||||
// UpdateCharacters updates the characters for the given log.
|
||||
func UpdateCharacters(log models.Log) (models.Log, error) {
|
||||
func UpdateCharacters(log models.Log, unknowns map[string]int) (models.Log, error) {
|
||||
posts, err := posts.List(&posts.Filter{LogID: &log.ShortID, Kind: []string{"action", "text", "chars"}, Limit: 0})
|
||||
if err != nil {
|
||||
return models.Log{}, err
|
||||
}
|
||||
|
||||
counts := make(map[string]int)
|
||||
added := make(map[string]bool)
|
||||
removed := make(map[string]bool)
|
||||
for _, post := range posts {
|
||||
if post.Kind == "text" || post.Kind == "action" {
|
||||
if strings.HasPrefix(post.Text, "(") || strings.Contains(post.Nick, "(") || strings.Contains(post.Nick, "[E]") {
|
||||
if strings.HasPrefix(post.Text, "(") || strings.Contains(post.Nick, "(") || strings.Contains(post.Nick, "[E]") || strings.HasSuffix(post.Nick, "|") {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -36,6 +39,7 @@ func UpdateCharacters(log models.Log) (models.Log, error) {
|
||||
}
|
||||
|
||||
added[post.Nick] = true
|
||||
counts[post.Nick]++
|
||||
}
|
||||
if post.Kind == "chars" {
|
||||
tokens := strings.Fields(post.Text)
|
||||
@@ -63,6 +67,10 @@ func UpdateCharacters(log models.Log) (models.Log, error) {
|
||||
|
||||
characterIDs := make([]string, len(characters))
|
||||
for i, char := range characters {
|
||||
if char.Name == "(Hidden)" {
|
||||
continue
|
||||
}
|
||||
|
||||
characterIDs[i] = char.ID
|
||||
}
|
||||
|
||||
@@ -73,6 +81,23 @@ func UpdateCharacters(log models.Log) (models.Log, error) {
|
||||
|
||||
log.CharacterIDs = characterIDs
|
||||
|
||||
if len(nicks) > 0 && unknowns != nil {
|
||||
NickLoop:
|
||||
for nick := range added {
|
||||
if !added[nick] {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, character := range characters {
|
||||
if character.HasNick(nick) {
|
||||
continue NickLoop
|
||||
}
|
||||
}
|
||||
|
||||
unknowns[nick] += counts[nick]
|
||||
}
|
||||
}
|
||||
|
||||
return log, nil
|
||||
}
|
||||
|
||||
@@ -84,9 +109,11 @@ func RunFullUpdate() error {
|
||||
return err
|
||||
}
|
||||
|
||||
unknowns := make(map[string]int, 256)
|
||||
|
||||
log := models.Log{}
|
||||
for iter.Next(&log) {
|
||||
_, err = UpdateCharacters(log)
|
||||
_, err = UpdateCharacters(log, unknowns)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -97,6 +124,11 @@ func RunFullUpdate() error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = unknownnicks.Update(unknowns)
|
||||
if err != nil {
|
||||
return errors.New("Failed to commit unknown nicks update: " + err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -12,3 +12,9 @@ type Post struct {
|
||||
Text string `bson:"text"`
|
||||
Position int `bson:"position"`
|
||||
}
|
||||
|
||||
// IsChangeObject is an interface implementation to identify it as a valid
|
||||
// ChangeObject in GQL.
|
||||
func (*Post) IsChangeObject() {
|
||||
panic("this method is a dummy, and so is its caller")
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package posts
|
||||
|
||||
import (
|
||||
"git.aiterp.net/rpdata/api/internal/counter"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
)
|
||||
|
||||
// AddMany adds multiple posts in on query. Each post gets a new ID and is associated with the log.
|
||||
func AddMany(log models.Log, posts []models.Post) ([]models.Post, error) {
|
||||
docs := make([]interface{}, len(posts))
|
||||
copies := make([]models.Post, len(posts))
|
||||
|
||||
mutex.RLock()
|
||||
defer mutex.RUnlock()
|
||||
|
||||
startPosition, err := counter.NextMany("next_post_id", log.ShortID, len(posts))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i, post := range posts {
|
||||
post.ID = generateID(post.Time)
|
||||
post.LogID = log.ShortID
|
||||
post.Position = startPosition + i
|
||||
|
||||
docs[i] = post
|
||||
copies[i] = post
|
||||
}
|
||||
|
||||
err = collection.Insert(docs...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return copies, nil
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package posts
|
||||
|
||||
import (
|
||||
"git.aiterp.net/rpdata/api/internal/counter"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"github.com/globalsign/mgo/bson"
|
||||
)
|
||||
@@ -20,6 +21,8 @@ func Remove(post models.Post) (models.Post, error) {
|
||||
return models.Post{}, err
|
||||
}
|
||||
|
||||
counter.NextMany("next_post_id", post.LogID, -1)
|
||||
|
||||
return post, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -16,3 +16,9 @@ type Story struct {
|
||||
FictionalDate time.Time `bson:"fictionalDate,omitempty"`
|
||||
UpdatedDate time.Time `bson:"updatedDate"`
|
||||
}
|
||||
|
||||
// IsChangeObject is an interface implementation to identify it as a valid
|
||||
// ChangeObject in GQL.
|
||||
func (*Story) IsChangeObject() {
|
||||
panic("this method is a dummy, and so is its caller")
|
||||
}
|
||||
@@ -10,3 +10,9 @@ type Tag struct {
|
||||
func (tag *Tag) Equal(other Tag) bool {
|
||||
return tag.Kind == other.Kind && tag.Name == other.Name
|
||||
}
|
||||
|
||||
// IsChangeObject is an interface implementation to identify it as a valid
|
||||
// ChangeObject in GQL.
|
||||
func (*Tag) IsChangeObject() {
|
||||
panic("this method is a dummy, and so is its caller")
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package models
|
||||
|
||||
// UnknownNick represents an unknown nick name.
|
||||
type UnknownNick struct {
|
||||
Nick string `bson:"_id"`
|
||||
Score int `bson:"score"`
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package unknownnicks
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"git.aiterp.net/rpdata/api/internal/store"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"github.com/globalsign/mgo"
|
||||
)
|
||||
|
||||
var collection *mgo.Collection
|
||||
|
||||
func list(query interface{}, limit int) ([]models.UnknownNick, error) {
|
||||
size := 64
|
||||
if limit > 0 {
|
||||
size = limit
|
||||
}
|
||||
posts := make([]models.UnknownNick, 0, size)
|
||||
|
||||
err := collection.Find(query).Limit(limit).Sort("-score").All(&posts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return posts, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
store.HandleInit(func(db *mgo.Database) {
|
||||
collection = db.C("logbot3.unkownnicks")
|
||||
|
||||
err := collection.EnsureIndexKey("weight")
|
||||
if err != nil {
|
||||
log.Fatalln("init logbot3.unkownnicks:", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package unknownnicks
|
||||
|
||||
import (
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
)
|
||||
|
||||
// List lists the top N unknown nicks.
|
||||
func List(limit int) ([]models.UnknownNick, error) {
|
||||
return list(nil, limit)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package unknownnicks
|
||||
@@ -0,0 +1,22 @@
|
||||
package unknownnicks
|
||||
|
||||
import (
|
||||
"github.com/globalsign/mgo/bson"
|
||||
)
|
||||
|
||||
// Update updates the map, replacing it with the scores provided here.
|
||||
func Update(updateMap map[string]int) error {
|
||||
_, err := collection.RemoveAll(bson.M{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for nick, score := range updateMap {
|
||||
_, err := collection.UpsertId(nick, bson.M{"$set": bson.M{"score": score}})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user