Compare commits
39
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a5410dff2c | ||
|
|
7e3dc49635 | ||
|
|
aff0d99842 | ||
|
|
00150118d2 | ||
|
|
332a1eb32b | ||
|
|
db4d46c368 | ||
|
|
a8c8610ca2 | ||
|
|
d09c17d30d | ||
|
|
e3e3ea07db | ||
|
|
22592439da | ||
|
|
dfa9fb4025 | ||
|
|
0e2a68a2d4 | ||
|
|
1eefa65527 | ||
|
|
f9b6af58c9 | ||
|
|
56ecf48b57 | ||
|
|
06fbfc90c5 | ||
|
|
39a4fef506 | ||
|
|
f9fe0bb5f8 | ||
|
|
d22ab387dd | ||
|
|
53a86c0b9c | ||
|
|
e0b93c6b4b | ||
|
|
1ea04f1577 | ||
|
|
17fd3c60f8 | ||
|
|
c5b62a42db | ||
|
|
fa0926c311 | ||
|
|
d21efc1c7f | ||
|
|
852030b141 | ||
|
|
f7f0b76379 | ||
|
|
2711654816 | ||
|
|
b60897a597 | ||
|
|
80e6c048c9 | ||
|
|
6310cbbc1e | ||
|
|
89a1c2366c | ||
|
|
ac2b79e88a | ||
|
|
31239330df | ||
|
|
a58535fcb9 | ||
|
|
2a91b6f9bc | ||
|
|
caa7727c02 | ||
|
|
047c9784ae |
+11
-17
@@ -1,34 +1,28 @@
|
||||
## 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 -a -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-logpaste ./cmd/rpdata-logpaste
|
||||
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, and tzdata for logpaste
|
||||
# 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
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/internal/store"
|
||||
"git.aiterp.net/rpdata/api/models/posts"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models/logs"
|
||||
)
|
||||
|
||||
var dateStr string
|
||||
var tzStr string
|
||||
var channel string
|
||||
var fileName string
|
||||
|
||||
func main() {
|
||||
var inputPosts []Post
|
||||
|
||||
flag.StringVar(&dateStr, "date", "2013-04-04", "Date in YYYY-MM-DD form.")
|
||||
flag.StringVar(&tzStr, "tz", "Europe/Oslo", "Time zone for the date.")
|
||||
flag.StringVar(&channel, "channel", "#RedrockAgency", "Channel it's set in.")
|
||||
flag.StringVar(&fileName, "file", "-", "File to read from (- = stdin).")
|
||||
flag.Parse()
|
||||
|
||||
tz, err := time.LoadLocation(tzStr)
|
||||
if err != nil {
|
||||
log.Fatalln("Parse timezone:", err)
|
||||
}
|
||||
|
||||
date, err := time.ParseInLocation("2006-01-02", dateStr, tz)
|
||||
if err != nil {
|
||||
log.Fatalln("Parse date:", err)
|
||||
}
|
||||
|
||||
err = store.Init()
|
||||
if err != nil {
|
||||
log.Fatalln("Init store:", err)
|
||||
}
|
||||
|
||||
if fileName != "-" {
|
||||
file, fileErr := os.Open(fileName)
|
||||
if fileErr != nil {
|
||||
log.Fatalln("Open file:", fileErr)
|
||||
}
|
||||
|
||||
inputPosts, err = parsePosts(file, date)
|
||||
file.Close()
|
||||
} else {
|
||||
inputPosts, err = parsePosts(os.Stdin, date)
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatalln("Read inputPosts:", err)
|
||||
}
|
||||
|
||||
if len(inputPosts) == 0 {
|
||||
log.Fatalln("Input file contained no posts.")
|
||||
}
|
||||
|
||||
l, err := logs.Add(inputPosts[0].Time, channel, "", "", "", false)
|
||||
if err != nil {
|
||||
log.Fatalln("Add log:", err)
|
||||
}
|
||||
|
||||
log.Println("Added log", l.ID)
|
||||
|
||||
for _, inputPost := range inputPosts {
|
||||
p, err := posts.Add(l, inputPost.Time, inputPost.Kind, inputPost.Nick, inputPost.Text)
|
||||
if err != nil {
|
||||
log.Fatalln("Add post:", err)
|
||||
}
|
||||
|
||||
log.Println("Added post", p.ID)
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ErrNotPost is returned by parsePost if the line is empty or not a post.
|
||||
var ErrNotPost = errors.New("not a post")
|
||||
|
||||
// ErrTooShort is returned by parsePost if the line too short to be a post.
|
||||
var ErrTooShort = errors.New("post too short")
|
||||
|
||||
// ErrInvalidTimestamp is returned by parsePost if the line is empty or not a post.
|
||||
var ErrInvalidTimestamp = errors.New("invalid timestamp")
|
||||
|
||||
// A Post is a part of a log.
|
||||
type Post struct {
|
||||
Time time.Time
|
||||
Kind string
|
||||
Nick string
|
||||
Text string
|
||||
}
|
||||
|
||||
func parsePosts(reader io.Reader, date time.Time) ([]Post, error) {
|
||||
prev := Post{}
|
||||
posts := make([]Post, 0, 8)
|
||||
bufReader := bufio.NewReader(reader)
|
||||
|
||||
for {
|
||||
line, err := bufReader.ReadString('\n')
|
||||
if err != nil && err != io.EOF {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(line) > 8 {
|
||||
post, err := parsePost(strings.Trim(line, " \n\r"), date, prev)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
posts = append(posts, post)
|
||||
prev = post
|
||||
}
|
||||
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return posts, nil
|
||||
}
|
||||
|
||||
func parsePost(line string, date time.Time, prev Post) (Post, error) {
|
||||
// Do basic validation
|
||||
line = strings.Trim(line, " \t\n\r")
|
||||
if len(line) == 0 || !strings.HasPrefix(line, "[") {
|
||||
return Post{}, ErrNotPost
|
||||
}
|
||||
|
||||
// Parse timestamp
|
||||
tsEndIndex := strings.IndexByte(line, ']')
|
||||
if tsEndIndex == -1 || len(line) < tsEndIndex+5 {
|
||||
return Post{}, ErrNotPost
|
||||
}
|
||||
tsStr := line[1:tsEndIndex]
|
||||
tsSplit := strings.Split(tsStr, ":")
|
||||
tsUnits := make([]int, len(tsSplit))
|
||||
if len(tsSplit) < 2 {
|
||||
return Post{}, ErrNotPost
|
||||
}
|
||||
for i := range tsSplit {
|
||||
n, err := strconv.Atoi(tsSplit[i])
|
||||
if err != nil {
|
||||
return Post{}, ErrNotPost
|
||||
}
|
||||
|
||||
tsUnits[i] = n
|
||||
}
|
||||
if len(tsUnits) == 2 {
|
||||
tsUnits = append(tsUnits, 0)
|
||||
}
|
||||
|
||||
ts := time.Date(date.Year(), date.Month(), date.Day(), tsUnits[0], tsUnits[1], tsUnits[2], 0, date.Location())
|
||||
if !prev.Time.IsZero() && prev.Time.After(ts) {
|
||||
ts = ts.AddDate(0, 0, 1)
|
||||
}
|
||||
|
||||
if line[tsEndIndex+2] == '*' {
|
||||
split := strings.SplitN(line[tsEndIndex+4:], " ", 2)
|
||||
|
||||
post := Post{
|
||||
Time: ts,
|
||||
Kind: "action",
|
||||
Nick: strings.TrimLeft(split[0], "+@!~"),
|
||||
Text: split[1],
|
||||
}
|
||||
|
||||
if post.Nick[0] == '=' {
|
||||
post.Kind = "scene"
|
||||
}
|
||||
|
||||
return post, nil
|
||||
} else if line[tsEndIndex+2] == '<' {
|
||||
split := strings.SplitN(line[tsEndIndex+2:], " ", 2)
|
||||
|
||||
post := Post{
|
||||
Time: ts,
|
||||
Kind: "text",
|
||||
Nick: strings.TrimLeft(split[0][1:len(split[0])-1], "+@!~"),
|
||||
Text: split[1],
|
||||
}
|
||||
|
||||
if post.Nick[0] == '=' {
|
||||
post.Kind = "scene"
|
||||
}
|
||||
|
||||
return post, nil
|
||||
} else {
|
||||
return Post{}, ErrNotPost
|
||||
}
|
||||
}
|
||||
@@ -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
-2
@@ -7,12 +7,13 @@ import (
|
||||
)
|
||||
|
||||
//go:generate ./combine.sh
|
||||
//go:generate gorunpkg github.com/99designs/gqlgen -v
|
||||
//go:generate go run github.com/99designs/gqlgen -v
|
||||
|
||||
// New creates a new GraphQL schema.
|
||||
func New() graphql.ExecutableSchema {
|
||||
return NewExecutableSchema(Config{
|
||||
Resolvers: &rootResolver{},
|
||||
Resolvers: &rootResolver{},
|
||||
Complexity: complexity(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -26,10 +27,18 @@ func (r *rootResolver) Mutation() MutationResolver {
|
||||
return queries.MutationResolver
|
||||
}
|
||||
|
||||
func (r *rootResolver) Subscription() SubscriptionResolver {
|
||||
return queries.SubscriptionResolver
|
||||
}
|
||||
|
||||
func (r *rootResolver) Log() LogResolver {
|
||||
return &types.LogResolver
|
||||
}
|
||||
|
||||
func (r *rootResolver) Comment() CommentResolver {
|
||||
return &types.CommentResolver
|
||||
}
|
||||
|
||||
func (r *rootResolver) Chapter() ChapterResolver {
|
||||
return &types.ChapterResolver
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
|
||||
// Queries
|
||||
|
||||
func (r *resolver) Channel(ctx context.Context, name string) (models.Channel, error) {
|
||||
func (r *resolver) Channel(ctx context.Context, name string) (*models.Channel, error) {
|
||||
return channels.FindName(name)
|
||||
}
|
||||
|
||||
@@ -24,10 +24,10 @@ func (r *resolver) Channels(ctx context.Context, filter *channels.Filter) ([]mod
|
||||
|
||||
// Mutations
|
||||
|
||||
func (r *mutationResolver) AddChannel(ctx context.Context, input input.ChannelAddInput) (models.Channel, error) {
|
||||
func (r *mutationResolver) AddChannel(ctx context.Context, input input.ChannelAddInput) (*models.Channel, error) {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Authenticated() || !token.Permitted("channel.add") {
|
||||
return models.Channel{}, errors.New("You are not permitted to add channels")
|
||||
return nil, errors.New("You are not permitted to add channels")
|
||||
}
|
||||
|
||||
logged := false
|
||||
@@ -49,7 +49,7 @@ 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)
|
||||
@@ -57,20 +57,20 @@ func (r *mutationResolver) AddChannel(ctx context.Context, input input.ChannelAd
|
||||
return channel, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) EditChannel(ctx context.Context, input input.ChannelEditInput) (models.Channel, error) {
|
||||
func (r *mutationResolver) EditChannel(ctx context.Context, input input.ChannelEditInput) (*models.Channel, error) {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Authenticated() || !token.Permitted("channel.edit") {
|
||||
return models.Channel{}, errors.New("You are not permitted to edit channels")
|
||||
return nil, errors.New("You are not permitted to edit channels")
|
||||
}
|
||||
|
||||
channel, err := channels.FindName(input.Name)
|
||||
if err != nil {
|
||||
return models.Channel{}, errors.New("Channel not found")
|
||||
return nil, errors.New("Channel not found")
|
||||
}
|
||||
|
||||
channel, err = channels.Edit(channel, input.Logged, input.Hub, input.EventName, input.LocationName)
|
||||
channel, err = channels.Edit(*channel, input.Logged, input.Hub, input.EventName, input.LocationName)
|
||||
if err != nil {
|
||||
return models.Channel{}, errors.New("Failed to edit channel: " + err.Error())
|
||||
return nil, errors.New("Failed to edit channel: " + err.Error())
|
||||
}
|
||||
|
||||
go changes.Submit("Channel", "edit", token.UserID, true, changekeys.Listed(channel), channel)
|
||||
|
||||
+39
-28
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"git.aiterp.net/rpdata/api/models/changekeys"
|
||||
"git.aiterp.net/rpdata/api/models/changes"
|
||||
"git.aiterp.net/rpdata/api/models/comments"
|
||||
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/models/stories"
|
||||
@@ -18,70 +19,75 @@ import (
|
||||
|
||||
// Queries
|
||||
|
||||
func (r *resolver) Chapter(ctx context.Context, id string) (models.Chapter, error) {
|
||||
func (r *resolver) Chapter(ctx context.Context, id string) (*models.Chapter, error) {
|
||||
return chapters.FindID(id)
|
||||
}
|
||||
|
||||
// Mutations
|
||||
|
||||
func (r *mutationResolver) AddChapter(ctx context.Context, input input.ChapterAddInput) (models.Chapter, error) {
|
||||
func (r *mutationResolver) AddChapter(ctx context.Context, input input.ChapterAddInput) (*models.Chapter, error) {
|
||||
story, err := stories.FindID(input.StoryID)
|
||||
if err != nil {
|
||||
return models.Chapter{}, errors.New("Story not found")
|
||||
return nil, errors.New("Story not found")
|
||||
}
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.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)
|
||||
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 +105,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 +137,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 +170,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
|
||||
}
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
package queries
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models/stories"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models/characters"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models/changekeys"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models/changes"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graph2/input"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/models/chapters"
|
||||
"git.aiterp.net/rpdata/api/models/comments"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
)
|
||||
|
||||
// Queries
|
||||
|
||||
func (r *resolver) Comment(ctx context.Context, id string) (models.Comment, error) {
|
||||
return comments.Find(id)
|
||||
}
|
||||
|
||||
// Mutations
|
||||
|
||||
func (r *mutationResolver) AddComment(ctx context.Context, input input.CommentAddInput) (models.Comment, error) {
|
||||
chapter, err := chapters.FindID(input.ChapterID)
|
||||
if err != nil {
|
||||
return models.Comment{}, errors.New("Chapter not found")
|
||||
}
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Permitted("member", "story.edit") {
|
||||
return models.Comment{}, errors.New("Unauthorized")
|
||||
}
|
||||
|
||||
if !chapter.CanComment() {
|
||||
return models.Comment{}, errors.New("Comments are disabled or locked")
|
||||
}
|
||||
|
||||
var characterPtr *models.Character
|
||||
if input.CharacterID != nil {
|
||||
character, err := characters.FindID(*input.CharacterID)
|
||||
if err != nil {
|
||||
return models.Comment{}, errors.New("Character not found")
|
||||
} else if character.Author != token.UserID {
|
||||
return models.Comment{}, errors.New("That is not your character")
|
||||
}
|
||||
|
||||
characterPtr = &character
|
||||
}
|
||||
|
||||
fictionalDate := time.Time{}
|
||||
if input.FictionalDate != nil {
|
||||
fictionalDate = *input.FictionalDate
|
||||
}
|
||||
|
||||
subject := ""
|
||||
if input.Subject != nil {
|
||||
subject = *input.Subject
|
||||
}
|
||||
|
||||
comment, err := comments.Add(chapter, subject, token.UserID, input.Source, input.CharacterName, characterPtr, time.Now(), fictionalDate)
|
||||
if err != nil {
|
||||
return models.Comment{}, errors.New("Failed to add comment: " + err.Error())
|
||||
}
|
||||
|
||||
go func() {
|
||||
story, err := stories.FindID(chapter.StoryID)
|
||||
if err != nil {
|
||||
log.Println("WARNING: Couldn't log comment change:", err)
|
||||
return
|
||||
}
|
||||
|
||||
changes.Submit("Comment", "add", token.UserID, true, changekeys.Many(comment, chapter, models.Story{ID: chapter.StoryID}), comment, chapter, story)
|
||||
}()
|
||||
|
||||
return comment, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) EditComment(ctx context.Context, input input.CommentEditInput) (models.Comment, error) {
|
||||
comment, err := comments.Find(input.CommentID)
|
||||
if err != nil {
|
||||
return models.Comment{}, errors.New("Comment not found")
|
||||
}
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.PermittedUser(comment.Author, "member", "story.edit") {
|
||||
return models.Comment{}, errors.New("You cannot edit this comment")
|
||||
}
|
||||
|
||||
chapter, err := chapters.FindID(comment.ChapterID)
|
||||
if err != nil {
|
||||
return models.Comment{}, errors.New("Comment's chapter not found")
|
||||
}
|
||||
|
||||
if !chapter.CanComment() {
|
||||
return models.Comment{}, errors.New("Comments are disabled or locked")
|
||||
}
|
||||
|
||||
if input.ClearFictionalDate != nil && *input.ClearFictionalDate == true {
|
||||
input.FictionalDate = &time.Time{}
|
||||
}
|
||||
|
||||
if input.CharacterID != nil && *input.CharacterID != "" {
|
||||
character, err := characters.FindID(*input.CharacterID)
|
||||
if err != nil {
|
||||
return models.Comment{}, errors.New("Character not found")
|
||||
} else if character.Author != token.UserID {
|
||||
return models.Comment{}, errors.New("That is not your character")
|
||||
}
|
||||
}
|
||||
|
||||
comment, err = comments.Edit(comment, input.Source, input.CharacterName, input.CharacterID, input.Subject, input.FictionalDate)
|
||||
if err != nil {
|
||||
return models.Comment{}, errors.New("Could not post comment: " + err.Error())
|
||||
}
|
||||
|
||||
go func() {
|
||||
story, err := stories.FindID(chapter.StoryID)
|
||||
if err != nil {
|
||||
log.Println("WARNING: Couldn't log comment change:", err)
|
||||
return
|
||||
}
|
||||
|
||||
changes.Submit("Comment", "edit", token.UserID, true, changekeys.Many(comment, chapter, models.Story{ID: chapter.StoryID}), comment, chapter, story)
|
||||
}()
|
||||
|
||||
return comment, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) RemoveComment(ctx context.Context, input input.CommentRemoveInput) (models.Comment, error) {
|
||||
comment, err := comments.Find(input.CommentID)
|
||||
if err != nil {
|
||||
return models.Comment{}, errors.New("Comment not found")
|
||||
}
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.PermittedUser(comment.Author, "member", "story.edit") {
|
||||
return models.Comment{}, errors.New("You cannot remove this comment")
|
||||
}
|
||||
|
||||
chapter, err := chapters.FindID(comment.ChapterID)
|
||||
if err != nil {
|
||||
return models.Comment{}, errors.New("Comment's chapter not found")
|
||||
}
|
||||
|
||||
if !chapter.CanComment() {
|
||||
return models.Comment{}, errors.New("Comments are disabled or locked")
|
||||
}
|
||||
|
||||
err = comments.Remove(comment)
|
||||
if err != nil {
|
||||
return models.Comment{}, errors.New("Failed to remove comment: " + err.Error())
|
||||
}
|
||||
|
||||
go func() {
|
||||
story, err := stories.FindID(chapter.StoryID)
|
||||
if err != nil {
|
||||
log.Println("WARNING: Couldn't log comment change:", err)
|
||||
return
|
||||
}
|
||||
|
||||
changes.Submit("Comment", "remove", token.UserID, true, changekeys.Many(comment, chapter, models.Story{ID: chapter.StoryID}), comment, chapter, story)
|
||||
}()
|
||||
|
||||
return comment, nil
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models/channels"
|
||||
|
||||
@@ -96,6 +97,50 @@ func (r *mutationResolver) AddLog(ctx context.Context, input input.LogAddInput)
|
||||
return log, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) ImportLog(ctx context.Context, input input.LogImportInput) ([]models.Log, error) {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Authenticated() || !token.Permitted("log.add") {
|
||||
return nil, errors.New("You are not permitted to add logs")
|
||||
}
|
||||
|
||||
date := time.Time{}
|
||||
if input.Date != nil {
|
||||
date = *input.Date
|
||||
}
|
||||
|
||||
tz := time.UTC
|
||||
if input.Timezone != nil {
|
||||
parsedTZ, err := time.LoadLocation(*input.Timezone)
|
||||
if err != nil {
|
||||
return nil, errors.New("Unknown timezone: " + *input.Timezone)
|
||||
}
|
||||
|
||||
tz = parsedTZ
|
||||
}
|
||||
|
||||
results, err := logs.Import(input.Importer, date, tz, input.ChannelName, input.Data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newLogs := make([]models.Log, 0, len(results))
|
||||
for _, result := range results {
|
||||
go func(result logs.ImportedLog) {
|
||||
changes.Submit("Log", "add", token.UserID, true, changekeys.Many(result.Log), result.Log)
|
||||
changes.Submit("Post", "add", token.UserID, true, changekeys.Many(result.Log, result.Posts), result.Posts)
|
||||
}(result)
|
||||
|
||||
log, err := logs.UpdateCharacters(result.Log, nil)
|
||||
if err != nil {
|
||||
log = result.Log
|
||||
}
|
||||
|
||||
newLogs = append(newLogs, log)
|
||||
}
|
||||
|
||||
return newLogs, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) EditLog(ctx context.Context, input input.LogEditInput) (models.Log, error) {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Authenticated() || !token.Permitted("log.edit") {
|
||||
|
||||
@@ -59,7 +59,7 @@ func (r *mutationResolver) AddPost(ctx context.Context, input input.PostAddInput
|
||||
return models.Post{}, 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
|
||||
@@ -83,7 +83,7 @@ func (r *mutationResolver) EditPost(ctx context.Context, input input.PostEditInp
|
||||
return
|
||||
}
|
||||
|
||||
logs.UpdateCharacters(log)
|
||||
logs.UpdateCharacters(log, nil)
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ 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)
|
||||
}()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+41
-29
@@ -15,7 +15,7 @@ import (
|
||||
"git.aiterp.net/rpdata/api/models/stories"
|
||||
)
|
||||
|
||||
func (r *resolver) Story(ctx context.Context, id string) (models.Story, error) {
|
||||
func (r *resolver) Story(ctx context.Context, id string) (*models.Story, error) {
|
||||
return stories.FindID(id)
|
||||
}
|
||||
|
||||
@@ -38,16 +38,16 @@ func (r *resolver) Stories(ctx context.Context, filter *stories.Filter) ([]model
|
||||
|
||||
// Mutations
|
||||
|
||||
func (r *mutationResolver) AddStory(ctx context.Context, input input.StoryAddInput) (models.Story, error) {
|
||||
func (r *mutationResolver) AddStory(ctx context.Context, input input.StoryAddInput) (*models.Story, error) {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if token == nil || !token.Permitted("member", "story.add") {
|
||||
return models.Story{}, errors.New("Permission denied")
|
||||
return nil, errors.New("Permission denied")
|
||||
}
|
||||
|
||||
author := token.UserID
|
||||
if input.Author != nil && *input.Author != author {
|
||||
if !token.Permitted("story.add") {
|
||||
return models.Story{}, errors.New("You are not permitted to add a story in another author's name")
|
||||
return nil, errors.New("You are not permitted to add a story in another author's name")
|
||||
}
|
||||
|
||||
author = *input.Author
|
||||
@@ -63,7 +63,7 @@ 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)
|
||||
@@ -71,21 +71,27 @@ func (r *mutationResolver) AddStory(ctx context.Context, input input.StoryAddInp
|
||||
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)
|
||||
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)
|
||||
@@ -93,21 +99,27 @@ func (r *mutationResolver) AddStoryTag(ctx context.Context, input input.StoryTag
|
||||
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)
|
||||
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)
|
||||
@@ -115,25 +127,25 @@ func (r *mutationResolver) RemoveStoryTag(ctx context.Context, input input.Story
|
||||
return story, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) EditStory(ctx context.Context, input input.StoryEditInput) (models.Story, error) {
|
||||
func (r *mutationResolver) EditStory(ctx context.Context, input input.StoryEditInput) (*models.Story, error) {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
|
||||
story, err := stories.FindID(input.ID)
|
||||
if err != nil {
|
||||
return models.Story{}, errors.New("Story not found")
|
||||
return nil, errors.New("Story not found")
|
||||
}
|
||||
|
||||
if !token.PermittedUser(story.Author, "member", "story.edit") {
|
||||
return models.Story{}, errors.New("You are not permitted to remove this story")
|
||||
return nil, errors.New("You are not permitted to remove this story")
|
||||
}
|
||||
|
||||
if input.ClearFictionalDate != nil && *input.ClearFictionalDate {
|
||||
input.FictionalDate = &time.Time{}
|
||||
}
|
||||
|
||||
story, err = stories.Edit(story, input.Name, input.Category, input.Listed, input.Open, input.FictionalDate)
|
||||
story, err = stories.Edit(*story, input.Name, input.Category, input.Listed, input.Open, input.FictionalDate)
|
||||
if err != nil {
|
||||
return 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)
|
||||
@@ -141,26 +153,26 @@ func (r *mutationResolver) EditStory(ctx context.Context, input input.StoryEditI
|
||||
return story, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) RemoveStory(ctx context.Context, input input.StoryRemoveInput) (models.Story, error) {
|
||||
func (r *mutationResolver) RemoveStory(ctx context.Context, input input.StoryRemoveInput) (*models.Story, error) {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
|
||||
story, err := stories.FindID(input.ID)
|
||||
if err != nil {
|
||||
return models.Story{}, errors.New("Story not found")
|
||||
return nil, errors.New("Story not found")
|
||||
}
|
||||
|
||||
if !token.PermittedUser(story.Author, "member", "story.remove") {
|
||||
return models.Story{}, errors.New("You are not permitted to remove this story")
|
||||
return nil, errors.New("You are not permitted to remove this story")
|
||||
}
|
||||
|
||||
story, err = stories.Remove(story)
|
||||
story, err = stories.Remove(*story)
|
||||
if err != nil {
|
||||
return models.Story{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = chapters.RemoveStory(story)
|
||||
err = chapters.RemoveStory(*story)
|
||||
if err != nil {
|
||||
return models.Story{}, errors.New("Failed to remove chapters, but story is removed: " + err.Error())
|
||||
return nil, errors.New("Failed to remove chapters, but story is removed: " + err.Error())
|
||||
}
|
||||
|
||||
go changes.Submit("Story", "remove", token.UserID, story.Listed, changekeys.Listed(story), story)
|
||||
|
||||
@@ -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!
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package types
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graph2/input"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
@@ -12,9 +13,13 @@ 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 cObj, ok := object.(input.ChangeObject); ok {
|
||||
results = append(results, cObj)
|
||||
} else {
|
||||
log.Printf("Warning: %T is not a change object", object)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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,88 @@
|
||||
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 {
|
||||
if sub.WildCard && change.Listed {
|
||||
select {
|
||||
case sub.Channel <- change:
|
||||
default:
|
||||
}
|
||||
} else {
|
||||
for _, key := range keys {
|
||||
if sub.Keys[key] {
|
||||
select {
|
||||
case sub.Channel <- change:
|
||||
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,8 @@ type Channel struct {
|
||||
EventName string `bson:"eventName,omitempty"`
|
||||
LocationName string `bson:"locationName,omitempty"`
|
||||
}
|
||||
|
||||
// IsChangeObject is a dummy interface implementation
|
||||
func (*Channel) IsChangeObject() {
|
||||
// Dummy interface implementation
|
||||
}
|
||||
|
||||
@@ -11,9 +11,9 @@ import (
|
||||
var ErrInvalidName = errors.New("Invalid channel name")
|
||||
|
||||
// Add creates a new channel.
|
||||
func Add(name string, logged, hub bool, event, location string) (models.Channel, error) {
|
||||
func Add(name string, logged, hub bool, event, location string) (*models.Channel, error) {
|
||||
if len(name) < 3 && !strings.HasPrefix(name, "#") {
|
||||
return models.Channel{}, ErrInvalidName
|
||||
return nil, ErrInvalidName
|
||||
}
|
||||
|
||||
channel := models.Channel{
|
||||
@@ -26,8 +26,8 @@ func Add(name string, logged, hub bool, event, location string) (models.Channel,
|
||||
|
||||
err := collection.Insert(channel)
|
||||
if err != nil {
|
||||
return models.Channel{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return channel, nil
|
||||
return &channel, nil
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
// Edit edits a channels
|
||||
func Edit(channel models.Channel, logged, hub *bool, eventName, locationName *string) (models.Channel, error) {
|
||||
func Edit(channel models.Channel, logged, hub *bool, eventName, locationName *string) (*models.Channel, error) {
|
||||
mutation := bson.M{}
|
||||
|
||||
if logged != nil && *logged != channel.Logged {
|
||||
@@ -27,13 +27,13 @@ func Edit(channel models.Channel, logged, hub *bool, eventName, locationName *st
|
||||
}
|
||||
|
||||
if len(mutation) == 0 {
|
||||
return channel, nil
|
||||
return &channel, nil
|
||||
}
|
||||
|
||||
err := collection.UpdateId(channel.Name, bson.M{"$set": mutation})
|
||||
if err != nil {
|
||||
return models.Channel{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return channel, nil
|
||||
return &channel, nil
|
||||
}
|
||||
|
||||
@@ -10,22 +10,22 @@ import (
|
||||
|
||||
// Ensure adds a channel if it doesn't exist. If logged is set and the found channel isn't logged,
|
||||
// that is changed.
|
||||
func Ensure(name string, logged bool) (models.Channel, error) {
|
||||
func Ensure(name string, logged bool) (*models.Channel, error) {
|
||||
if len(name) < 3 && !strings.HasPrefix(name, "#") {
|
||||
return models.Channel{}, ErrInvalidName
|
||||
return nil, ErrInvalidName
|
||||
}
|
||||
|
||||
channel, err := FindName(name)
|
||||
if err == mgo.ErrNotFound {
|
||||
return Add(name, logged, false, "", "")
|
||||
} else if err != nil {
|
||||
return models.Channel{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if logged && !channel.Logged {
|
||||
channel, err = Edit(channel, &logged, nil, nil, nil)
|
||||
channel, err = Edit(*channel, &logged, nil, nil, nil)
|
||||
if err != nil {
|
||||
return models.Channel{}, err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,12 @@ package channels
|
||||
import "git.aiterp.net/rpdata/api/models"
|
||||
|
||||
// FindName finds a channel by its id (its name).
|
||||
func FindName(name string) (models.Channel, error) {
|
||||
func FindName(name string) (*models.Channel, error) {
|
||||
channel := models.Channel{}
|
||||
err := collection.FindId(name).One(&channel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return channel, err
|
||||
return &channel, nil
|
||||
}
|
||||
|
||||
@@ -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"`)
|
||||
+20
-8
@@ -4,12 +4,24 @@ 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 a dummy interface implementation
|
||||
func (*Chapter) IsChangeObject() {
|
||||
// Dummy interface implementation
|
||||
}
|
||||
|
||||
+12
-10
@@ -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 {
|
||||
@@ -25,7 +27,7 @@ func Add(story models.Story, title, author, source string, createdDate time.Time
|
||||
|
||||
err := collection.Insert(chapter)
|
||||
if err != nil {
|
||||
return models.Chapter{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if createdDate.After(story.UpdatedDate) {
|
||||
@@ -34,5 +36,5 @@ func Add(story models.Story, title, author, source string, createdDate time.Time
|
||||
}
|
||||
}
|
||||
|
||||
return chapter, nil
|
||||
return &chapter, nil
|
||||
}
|
||||
|
||||
@@ -13,11 +13,14 @@ import (
|
||||
var collection *mgo.Collection
|
||||
var storyCollection *mgo.Collection
|
||||
|
||||
func find(query interface{}) (models.Chapter, error) {
|
||||
func find(query interface{}) (*models.Chapter, error) {
|
||||
chapter := models.Chapter{}
|
||||
err := collection.Find(query).One(&chapter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return chapter, err
|
||||
return &chapter, err
|
||||
}
|
||||
|
||||
func list(query interface{}) ([]models.Chapter, error) {
|
||||
|
||||
+11
-3
@@ -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,11 +28,19 @@ 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 {
|
||||
return chapter, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return edited, nil
|
||||
return &edited, nil
|
||||
}
|
||||
|
||||
@@ -6,6 +6,6 @@ import (
|
||||
)
|
||||
|
||||
// FindID finds a chapter by ID
|
||||
func FindID(id string) (models.Chapter, error) {
|
||||
func FindID(id string) (*models.Chapter, error) {
|
||||
return find(bson.M{"_id": id})
|
||||
}
|
||||
|
||||
@@ -8,12 +8,12 @@ import (
|
||||
)
|
||||
|
||||
// Move updates the chapter, moving it to the given story.
|
||||
func Move(chapter models.Chapter, story models.Story) (models.Chapter, error) {
|
||||
func Move(chapter models.Chapter, story models.Story) (*models.Chapter, error) {
|
||||
now := time.Now()
|
||||
|
||||
err := collection.UpdateId(chapter.ID, bson.M{"$set": bson.M{"editedDate": now, "storyId": story.ID}})
|
||||
if err != nil {
|
||||
return models.Chapter{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
chapter.EditedDate = now
|
||||
@@ -24,5 +24,5 @@ func Move(chapter models.Chapter, story models.Story) (models.Chapter, error) {
|
||||
}
|
||||
}
|
||||
|
||||
return chapter, nil
|
||||
return &chapter, nil
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@ import (
|
||||
)
|
||||
|
||||
// Remove removes a chapter.
|
||||
func Remove(chapter models.Chapter) (models.Chapter, error) {
|
||||
func Remove(chapter models.Chapter) (*models.Chapter, error) {
|
||||
if err := collection.RemoveId(chapter.ID); err != nil {
|
||||
return models.Chapter{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return chapter, nil
|
||||
|
||||
@@ -29,3 +29,8 @@ func (character *Character) HasNick(nick string) bool {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IsChangeObject is a dummy interface implementation
|
||||
func (*Character) IsChangeObject() {
|
||||
// Dummy interface implementation
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
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 a dummy interface implementation
|
||||
func (*Comment) IsChangeObject() {
|
||||
// Dummy interface implementation
|
||||
}
|
||||
@@ -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,8 @@ type File struct {
|
||||
Author string `bson:"author" json:"author"`
|
||||
URL string `bson:"url,omitempty" json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// IsChangeObject is a dummy interface implementation
|
||||
func (*File) IsChangeObject() {
|
||||
// Dummy interface implementation
|
||||
}
|
||||
|
||||
@@ -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,8 @@ type Log struct {
|
||||
Open bool `bson:"open"`
|
||||
CharacterIDs []string `bson:"characterIds"`
|
||||
}
|
||||
|
||||
// IsChangeObject is a dummy interface implementation
|
||||
func (*Log) IsChangeObject() {
|
||||
// Dummy interface implementation
|
||||
}
|
||||
|
||||
@@ -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,8 @@ type Post struct {
|
||||
Text string `bson:"text"`
|
||||
Position int `bson:"position"`
|
||||
}
|
||||
|
||||
// IsChangeObject is a dummy interface implementation
|
||||
func (*Post) IsChangeObject() {
|
||||
// Dummy interface implementation
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -11,19 +11,19 @@ import (
|
||||
var ErrTagAlreadyExists = errors.New("Tag already exists on story")
|
||||
|
||||
// AddTag adds a tag to the story. It returns ErrTagAlreadyExists if the tag is already there
|
||||
func AddTag(story models.Story, tag models.Tag) (models.Story, error) {
|
||||
func AddTag(story models.Story, tag models.Tag) (*models.Story, error) {
|
||||
for i := range story.Tags {
|
||||
if story.Tags[i].Equal(tag) {
|
||||
return models.Story{}, ErrTagAlreadyExists
|
||||
return nil, ErrTagAlreadyExists
|
||||
}
|
||||
}
|
||||
|
||||
err := collection.UpdateId(story.ID, bson.M{"$push": bson.M{"tags": tag}})
|
||||
if err != nil {
|
||||
return models.Story{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
story.Tags = append(story.Tags, tag)
|
||||
|
||||
return story, nil
|
||||
return &story, nil
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
// Add creates a new story.
|
||||
func Add(name, author string, category models.StoryCategory, listed, open bool, tags []models.Tag, createdDate, fictionalDate time.Time) (models.Story, error) {
|
||||
func Add(name, author string, category models.StoryCategory, listed, open bool, tags []models.Tag, createdDate, fictionalDate time.Time) (*models.Story, error) {
|
||||
story := models.Story{
|
||||
ID: makeStoryID(),
|
||||
Name: name,
|
||||
@@ -23,8 +23,8 @@ func Add(name, author string, category models.StoryCategory, listed, open bool,
|
||||
|
||||
err := collection.Insert(story)
|
||||
if err != nil {
|
||||
return models.Story{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return story, nil
|
||||
return &story, nil
|
||||
}
|
||||
|
||||
@@ -12,11 +12,14 @@ import (
|
||||
|
||||
var collection *mgo.Collection
|
||||
|
||||
func find(query interface{}) (models.Story, error) {
|
||||
func find(query interface{}) (*models.Story, error) {
|
||||
story := models.Story{}
|
||||
err := collection.Find(query).One(&story)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return story, err
|
||||
return &story, err
|
||||
}
|
||||
|
||||
func list(query interface{}, limit int) ([]models.Story, error) {
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
// Edit edits the story and returns the edited story if it succeeds.
|
||||
func Edit(story models.Story, name *string, category *models.StoryCategory, listed, open *bool, fictionalDate *time.Time) (models.Story, error) {
|
||||
func Edit(story models.Story, name *string, category *models.StoryCategory, listed, open *bool, fictionalDate *time.Time) (*models.Story, error) {
|
||||
changes := bson.M{}
|
||||
|
||||
if name != nil && *name != story.Name {
|
||||
@@ -33,13 +33,13 @@ func Edit(story models.Story, name *string, category *models.StoryCategory, list
|
||||
}
|
||||
|
||||
if len(changes) == 0 {
|
||||
return story, nil
|
||||
return &story, nil
|
||||
}
|
||||
|
||||
err := collection.UpdateId(story.ID, bson.M{"$set": changes})
|
||||
if err != nil {
|
||||
return models.Story{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return story, nil
|
||||
return &story, nil
|
||||
}
|
||||
|
||||
@@ -6,6 +6,6 @@ import (
|
||||
)
|
||||
|
||||
// FindID finds a story by ID
|
||||
func FindID(id string) (models.Story, error) {
|
||||
func FindID(id string) (*models.Story, error) {
|
||||
return find(bson.M{"_id": id})
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
var ErrTagNotExists = errors.New("Tag does not exist on story")
|
||||
|
||||
// RemoveTag removes a tag to the story. It returns ErrTagNotExists if the tag does not exist.
|
||||
func RemoveTag(story models.Story, tag models.Tag) (models.Story, error) {
|
||||
func RemoveTag(story models.Story, tag models.Tag) (*models.Story, error) {
|
||||
index := -1
|
||||
for i := range story.Tags {
|
||||
if story.Tags[i].Equal(tag) {
|
||||
@@ -20,15 +20,15 @@ func RemoveTag(story models.Story, tag models.Tag) (models.Story, error) {
|
||||
}
|
||||
}
|
||||
if index == -1 {
|
||||
return models.Story{}, ErrTagNotExists
|
||||
return nil, ErrTagNotExists
|
||||
}
|
||||
|
||||
err := collection.UpdateId(story.ID, bson.M{"$pull": bson.M{"tags": tag}})
|
||||
if err != nil {
|
||||
return models.Story{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
story.Tags = append(story.Tags[:index], story.Tags[index+1:]...)
|
||||
|
||||
return story, nil
|
||||
return &story, nil
|
||||
}
|
||||
|
||||
@@ -5,6 +5,6 @@ import (
|
||||
)
|
||||
|
||||
// Remove the story from the database
|
||||
func Remove(story models.Story) (models.Story, error) {
|
||||
return story, collection.RemoveId(story.ID)
|
||||
func Remove(story models.Story) (*models.Story, error) {
|
||||
return &story, collection.RemoveId(story.ID)
|
||||
}
|
||||
|
||||
@@ -16,3 +16,8 @@ type Story struct {
|
||||
FictionalDate time.Time `bson:"fictionalDate,omitempty"`
|
||||
UpdatedDate time.Time `bson:"updatedDate"`
|
||||
}
|
||||
|
||||
// IsChangeObject is a dummy interface implementation
|
||||
func (*Story) IsChangeObject() {
|
||||
// Dummy interface implementation
|
||||
}
|
||||
|
||||
@@ -10,3 +10,8 @@ type Tag struct {
|
||||
func (tag *Tag) Equal(other Tag) bool {
|
||||
return tag.Kind == other.Kind && tag.Name == other.Name
|
||||
}
|
||||
|
||||
// IsChangeObject is a dummy interface implementation
|
||||
func (*Tag) IsChangeObject() {
|
||||
// Dummy interface implementation
|
||||
}
|
||||
|
||||
@@ -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