Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a606b79736 | ||
|
|
87559997a9 | ||
|
|
b641f7d516 | ||
|
|
b117aea7f0 | ||
|
|
33ac47a12c | ||
|
|
18a83073e8 | ||
|
|
f14386eceb | ||
|
|
6748ff8bc2 | ||
|
|
e4761ad4a0 | ||
|
|
21b7b44159 | ||
|
|
663a95c70d | ||
|
|
525191a0f9 | ||
|
|
410e07a6a2 | ||
|
|
9aa7304982 | ||
|
|
0f06a3750f | ||
|
|
0bc86ced98 | ||
|
|
c5ea598ea1 | ||
|
|
b6b7b8f1b6 | ||
|
|
e9b00b61fd | ||
|
|
c9084287a4 | ||
|
|
570d0e2bd2 | ||
|
|
6ad0b9d467 | ||
|
|
63bc262091 | ||
|
|
c4eabd69ac | ||
|
|
df982fac17 | ||
|
|
6fa2dc9800 | ||
|
|
6ce38d61b1 | ||
|
|
dc3f77f8da | ||
|
|
6321ea1893 | ||
|
|
9b346fb97b | ||
|
|
ed21b7c8cd | ||
|
|
47b7877ab4 | ||
|
|
757b0bc616 | ||
|
|
5dbed6d632 | ||
|
|
c8ced52e1a | ||
|
|
8a313b3bb2 | ||
|
|
e690e7ab97 | ||
|
|
0e70e37038 | ||
|
|
3b10c4d9b1 | ||
|
|
690eb4f121 | ||
|
|
2eed815993 | ||
|
|
8c4670580c | ||
|
|
8fbaea2078 | ||
|
|
2a0e6534ff | ||
|
|
4bd75b8240 | ||
|
|
ed38d576b2 | ||
|
|
51b2c9bfe0 | ||
|
|
c0687d76ad | ||
|
|
60dcd8a80f | ||
|
|
5bcc7479ff | ||
|
|
2c00358f4f | ||
|
|
acb6b637a5 | ||
|
|
f46b6f0774 | ||
|
|
73a1c79903 | ||
|
|
e194b6536e | ||
|
|
f6ad0602c2 | ||
|
|
021f67ea4b | ||
|
|
13f82399ef | ||
|
|
2a1423d28f | ||
|
|
7b253748fe | ||
|
|
53c3d4597b | ||
|
|
cdf4e6f667 | ||
|
|
6fa9f64f2d | ||
|
|
2fb391321e | ||
|
|
015bdfd2e8 | ||
|
|
9925907e93 | ||
|
|
c7c418aeab | ||
|
|
d5573e7f39 | ||
|
|
e794311eb8 | ||
|
|
aff0d99842 | ||
|
|
00150118d2 | ||
|
|
332a1eb32b | ||
|
|
db4d46c368 | ||
|
|
a8c8610ca2 | ||
|
|
d09c17d30d | ||
|
|
e3e3ea07db | ||
|
|
22592439da | ||
|
|
dfa9fb4025 | ||
|
|
0e2a68a2d4 | ||
|
|
1eefa65527 | ||
|
|
f9b6af58c9 | ||
|
|
56ecf48b57 | ||
|
|
06fbfc90c5 | ||
|
|
39a4fef506 | ||
|
|
f9fe0bb5f8 | ||
|
|
d22ab387dd | ||
|
|
53a86c0b9c | ||
|
|
e0b93c6b4b | ||
|
|
1ea04f1577 | ||
|
|
17fd3c60f8 | ||
|
|
c5b62a42db | ||
|
|
fa0926c311 | ||
|
|
d21efc1c7f | ||
|
|
852030b141 | ||
|
|
f7f0b76379 | ||
|
|
2711654816 | ||
|
|
b60897a597 | ||
|
|
80e6c048c9 | ||
|
|
6310cbbc1e | ||
|
|
89a1c2366c | ||
|
|
ac2b79e88a | ||
|
|
31239330df | ||
|
|
a58535fcb9 | ||
|
|
2a91b6f9bc | ||
|
|
caa7727c02 | ||
|
|
047c9784ae | ||
|
|
d038c1cfea | ||
|
|
7e812bd615 | ||
|
|
947765884e | ||
|
|
ffec008820 | ||
|
|
138b3e3eab | ||
|
|
929abfdbe4 | ||
|
|
a8cd595876 | ||
|
|
9ed3b32b42 | ||
|
|
6bae52d652 | ||
|
|
cee370c2ac | ||
|
|
9affdbd20e | ||
|
|
cf545cde3b | ||
|
|
b414cf118f | ||
|
|
4979b02104 | ||
|
|
b7d5059d60 | ||
|
|
9fbdc767ad | ||
|
|
daeaaf5fa9 | ||
|
|
d3277c7476 | ||
|
|
90681c67b0 | ||
|
|
bebd99833b | ||
|
|
34f86d5037 | ||
|
|
8a85313762 | ||
|
|
017e68eae7 | ||
|
|
1955ad0517 | ||
|
|
34ba5d5d3a | ||
|
|
8c8a251080 | ||
|
|
73fe125870 | ||
|
|
04ec85aadf | ||
|
|
59233ee549 | ||
|
|
7aa0de9135 | ||
|
|
6dfbee7ff2 | ||
|
|
8a07c2ffdb | ||
|
|
0e9536b0a8 | ||
|
|
b8e5ed5813 | ||
|
|
2317f66920 | ||
|
|
fc488b938d | ||
|
|
ceb2617571 | ||
|
|
aeb5b26627 | ||
|
|
6aafc67790 | ||
|
|
c6f8fd9faa | ||
|
|
efa98b80cc | ||
|
|
707dcb43eb | ||
|
|
9bcc28a0ea | ||
|
|
2fceaa62ed | ||
|
|
86125c7ce9 | ||
|
|
bb28795cd5 | ||
|
|
815f30597e | ||
|
|
cd1f122502 | ||
|
|
0e0d802a32 | ||
|
|
3c42043ab6 | ||
|
|
8388ee2446 | ||
|
|
260c2e7f76 | ||
|
|
a4034b0faa | ||
|
|
c3a6798050 | ||
|
|
2ab933149e | ||
|
|
07b94b3cab | ||
|
|
79171bf9a2 | ||
|
|
bac57bc378 | ||
|
|
875dac9ecb | ||
|
|
0768cfd0bd | ||
|
|
2b05d90f4a | ||
|
|
0444911e73 | ||
|
|
1be4088a8f | ||
|
|
9978d7380b | ||
|
|
2e5162a29b | ||
|
|
dac0060155 | ||
|
|
9d7fd498a9 | ||
|
|
e0af2685ee | ||
|
|
3184b60310 | ||
|
|
675c09ef04 | ||
|
|
9707320960 | ||
|
|
20e53b4f5e | ||
|
|
8bb4dea358 | ||
|
|
75b2382f09 | ||
|
|
1ca25eff8e | ||
|
|
bf10ed1fe3 | ||
|
|
54136be912 | ||
|
|
228c2bfeb4 | ||
|
|
2f98be5747 | ||
|
|
2a40f97282 | ||
|
|
e5b4184106 | ||
|
|
c348a90b70 | ||
|
|
132cf28f0f | ||
|
|
d5df624188 | ||
|
|
ca98648274 | ||
|
|
4642a8abec | ||
|
|
0ec923d686 | ||
|
|
388d5378e7 | ||
|
|
67e9e8f1e6 | ||
|
|
06bc7e5a1b | ||
|
|
237190e498 | ||
|
|
b9f0fef966 | ||
|
|
ba1c1e0a4b | ||
|
|
18e77b8205 | ||
|
|
d8871259a1 | ||
|
|
26d7e36631 | ||
|
|
51489ed901 |
+48
-16
@@ -1,21 +1,53 @@
|
||||
workspace:
|
||||
base: /go
|
||||
path: src/git.aiterp.net/rpdata/api
|
||||
name: rpdata-api
|
||||
|
||||
pipeline:
|
||||
docker-release:
|
||||
kind: pipeline
|
||||
type: docker
|
||||
|
||||
steps:
|
||||
- name: go-test
|
||||
image: golang:1.14
|
||||
commands:
|
||||
- go generate ./...
|
||||
- go test -v ./...
|
||||
|
||||
# - name: postgres-migrate
|
||||
# image: golang:1.15
|
||||
# depends_on: []
|
||||
# environment:
|
||||
# DB_CONNECT:
|
||||
# from_secret: db_connect
|
||||
# commands:
|
||||
# - go get -u github.com/pressly/goose/...
|
||||
# - cd migrations/postgres
|
||||
# - goose postgres "$DB_CONNECT" up
|
||||
# when:
|
||||
# event:
|
||||
# - tag
|
||||
|
||||
- name: docker-release-tag
|
||||
image: plugins/docker
|
||||
repo: vmaple/rpdata
|
||||
tags: latest
|
||||
auto_tag: true
|
||||
secrets: [ docker_username, docker_password ]
|
||||
settings:
|
||||
auto_tag: true
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: r.vmaple.dev/rpdata/rpdata-api
|
||||
registry: r.vmaple.dev
|
||||
when:
|
||||
event: tag
|
||||
ref: refs/tags/*
|
||||
docker-dev:
|
||||
event:
|
||||
- tag
|
||||
|
||||
- name: docker-release-latest
|
||||
image: plugins/docker
|
||||
repo: vmaple/rpdata
|
||||
tags: trunk
|
||||
secrets: [ docker_username, docker_password ]
|
||||
settings:
|
||||
auto_tag: false
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: r.vmaple.dev/rpdata/rpdata-api
|
||||
registry: r.vmaple.dev
|
||||
when:
|
||||
event: push
|
||||
event:
|
||||
- tag
|
||||
|
||||
+17
-2
@@ -1,7 +1,22 @@
|
||||
vendor
|
||||
schema/bindata.go
|
||||
graphql/schema/bindata.go
|
||||
config.json
|
||||
api
|
||||
debug
|
||||
build
|
||||
.vscode
|
||||
rpdata-graphiql
|
||||
rpdata-*
|
||||
!rpdata-*/
|
||||
.vscode
|
||||
.idea
|
||||
|
||||
/main
|
||||
/main.exe
|
||||
|
||||
/graph2/graphcore/*_gen.go
|
||||
/database/postgres/psqlcore/*.go
|
||||
!/database/postgres/psqlcore/package.go
|
||||
generated.gql
|
||||
generated.go
|
||||
/dump.zip
|
||||
/rpdata-dump-v1/
|
||||
|
||||
+14
-35
@@ -1,50 +1,29 @@
|
||||
## 1. Server
|
||||
# Use Go 1.10
|
||||
FROM golang:1.10 AS build-server
|
||||
## 1. Build Server
|
||||
# Use Go 1.14
|
||||
FROM golang:1.16 AS build-server
|
||||
|
||||
# Load repository into docker
|
||||
WORKDIR /go/src/git.aiterp.net/rpdata/api/
|
||||
WORKDIR /project/
|
||||
COPY . .
|
||||
|
||||
# Load build tools and dependencies
|
||||
RUN go get -u github.com/golang/dep/cmd/dep
|
||||
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-graphiql ./cmd/rpdata-graphiql
|
||||
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
|
||||
RUN go build -a -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-wikifileimport ./cmd/rpdata-wikifileimport
|
||||
RUN go build -a -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-ensurechannels ./cmd/rpdata-ensurechannels
|
||||
RUN go build -a -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-as2storyimport ./cmd/rpdata-as2storyimport
|
||||
RUN go build -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-server ./cmd/rpdata-server
|
||||
RUN go build -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-dump ./cmd/rpdata-dump
|
||||
RUN go build -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-restore ./cmd/rpdata-restore
|
||||
|
||||
## 2. React UI
|
||||
# Use Node.JS 18
|
||||
FROM node:10.5-alpine AS build-id
|
||||
|
||||
# The content is already there
|
||||
WORKDIR /rpdata-ui
|
||||
COPY ./rpdata-ui /rpdata-ui
|
||||
|
||||
# Prepare dependencies
|
||||
RUN npm install
|
||||
|
||||
# Run build
|
||||
RUN npm run build
|
||||
|
||||
## 3. Regroup
|
||||
## 2. Distribute
|
||||
# Use alpine linux
|
||||
FROM alpine:3.7
|
||||
FROM alpine:3.13
|
||||
|
||||
# Install certificates to enable HTTPS GET requests
|
||||
RUN apk add --no-cache ca-certificates
|
||||
# Install certificates to enable HTTPS GET requests, and tzdata for log imports.
|
||||
RUN apk add --no-cache ca-certificates tzdata
|
||||
|
||||
# Copy build files into final container
|
||||
COPY --from=build-server /binaries/* /usr/local/bin/
|
||||
COPY --from=build-id /rpdata-ui/build /var/www/
|
||||
|
||||
# TODO: Use proper server instead of grapihql server
|
||||
CMD ["/usr/local/bin/rpdata-graphiql"]
|
||||
# The server is the main entry point
|
||||
CMD ["/usr/local/bin/rpdata-server"]
|
||||
|
||||
Generated
-178
@@ -1,178 +0,0 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "git.aiterp.net/aiterp/wikiauth"
|
||||
packages = ["."]
|
||||
revision = "33860de804ddcec4fc17998745233b4ec477e7c2"
|
||||
|
||||
[[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/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]]
|
||||
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/sadbox/mediawiki"
|
||||
packages = ["."]
|
||||
revision = "39fea8a1336076a961a300d1d95765dcd17e8a3c"
|
||||
version = "v0.1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/sirupsen/logrus"
|
||||
packages = ["."]
|
||||
revision = "c155da19408a8799da419ed3eeb0cb5db0ad5dbc"
|
||||
version = "v1.0.5"
|
||||
|
||||
[[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]]
|
||||
name = "google.golang.org/appengine"
|
||||
packages = ["cloudsql"]
|
||||
revision = "b1f26356af11148e710935ed1ac8a7f5702c7612"
|
||||
version = "v1.1.0"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "16fd84401bc5796ffeaa895f31d099e1a23b363b15b87386877de0829ac9575b"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
-41
@@ -1,41 +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
|
||||
|
||||
[[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"
|
||||
@@ -1,18 +1,5 @@
|
||||
# RPData API
|
||||
|
||||
Project RPData aims to centralize the AiteRP applications, and partially involve the wiki, into a common API. This will allow for easy linking and inlining between them. The primary consumers of this API will be the Logbot and the upcoming RPData website.
|
||||
Project RPData aims to centralize the AiteRP applications, and partially involve the wiki into a common API. This will allow for easy linking and inlining between them. The primary consumers of this API will be the Logbot and the upcoming RPData website.
|
||||
|
||||
## Progress
|
||||
|
||||
### Complete
|
||||
* Session
|
||||
* Character
|
||||
* Log
|
||||
* Post
|
||||
* File
|
||||
|
||||
### Remaining
|
||||
* Story
|
||||
|
||||
### Future
|
||||
* User (manual DB edits will suffice for now)
|
||||
There is some renovation going on, and the packages `model` and `graphql` are going to be superceded by `models` and `graph2`
|
||||
@@ -1,139 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/internal/store"
|
||||
"git.aiterp.net/rpdata/api/model/story"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
var flagHost = flag.String("host", "127.0.0.1:3306", "SQL host")
|
||||
var flagDB = flag.String("database", "aitestory", "SQL database")
|
||||
var flagUser = flag.String("user", "aitestory", "SQL user")
|
||||
var flagPassword = flag.String("password", "", "SQL password")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
db, err := sqlx.Connect("mysql", fmt.Sprintf("%s:%s@(%s)/%s", *flagUser, *flagPassword, *flagHost, *flagDB))
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
err = db.Ping()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
store.Init()
|
||||
|
||||
results := make([]storyResult, 0, 64)
|
||||
rows, err := db.Queryx("SELECT * FROM page WHERE unlisted=0;")
|
||||
for rows.Next() {
|
||||
result := storyResult{}
|
||||
err := rows.StructScan(&result)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
results = append(results, result)
|
||||
}
|
||||
|
||||
tagResults := make([]tagResult, 0, 256)
|
||||
rows, err = db.Queryx("SELECT page_id,type,name FROM page_tag LEFT JOIN tag ON tag_id=tag.id;")
|
||||
for rows.Next() {
|
||||
result := tagResult{}
|
||||
err := rows.StructScan(&result)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
tagResults = append(tagResults, result)
|
||||
}
|
||||
|
||||
for _, result := range results {
|
||||
fictionalDate, err := time.Parse("2006-01-02 15:04:05", result.FictionalDate)
|
||||
if err != nil {
|
||||
if result.FictionalDate != "0000-00-00 00:00:00" {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
if fictionalDate.Year() < 1800 {
|
||||
fictionalDate = time.Time{}
|
||||
}
|
||||
|
||||
publishDate, err := time.Parse("2006-01-02 15:04:05", result.PublishDate)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
tags := make([]story.Tag, 0, 8)
|
||||
for _, tagResult := range tagResults {
|
||||
if tagResult.PageID == result.ID {
|
||||
tags = append(tags, story.Tag{Kind: tagResult.Type, Name: tagResult.Name})
|
||||
}
|
||||
}
|
||||
|
||||
story, err := story.New(result.Name, result.Author[5:], result.Category, false, false, tags, publishDate, fictionalDate)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
// Change the story title
|
||||
title := result.Name
|
||||
if strings.HasPrefix(result.Source, "#") {
|
||||
firstNewline := strings.Index(result.Source, "\n")
|
||||
result.Name = strings.Replace(result.Source[1:firstNewline], "\r", "", -1)
|
||||
result.Source = result.Source[firstNewline+1:]
|
||||
|
||||
result.Source = strings.Replace(result.Source, "\r\n", "\n", -1)
|
||||
if strings.HasPrefix(result.Source, "\r") || strings.HasPrefix(result.Source, "\n") {
|
||||
result.Source = result.Source[1:]
|
||||
}
|
||||
}
|
||||
|
||||
chapter, err := story.AddChapter(title, result.Author[5:], result.Source, publishDate, fictionalDate)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
if !result.Unlisted {
|
||||
listed := true
|
||||
err := story.Edit(nil, nil, &listed, nil, nil)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println(result.ID, "->", story.ID, chapter.ID)
|
||||
}
|
||||
}
|
||||
|
||||
type tagResult struct {
|
||||
PageID string `db:"page_id"`
|
||||
Type string `db:"type"`
|
||||
Name string `db:"name"`
|
||||
}
|
||||
|
||||
type storyResult struct {
|
||||
ID string `db:"id"`
|
||||
Name string `db:"name"`
|
||||
Author string `db:"author"`
|
||||
Category string `db:"category"`
|
||||
FictionalDate string `db:"fictional_date"`
|
||||
PublishDate string `db:"publish_date"`
|
||||
EditDate string `db:"edit_date"`
|
||||
Unlisted bool `db:"unlisted"`
|
||||
Dated bool `db:"dated"`
|
||||
Spesific bool `db:"specific"`
|
||||
Indexed bool `db:"indexed"`
|
||||
Published bool `db:"published"`
|
||||
Type string `db:"type"`
|
||||
Source string `db:"source"`
|
||||
Cache string `db:"cache"`
|
||||
BackgroundURL *string `db:"background_url"`
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"git.aiterp.net/rpdata/api/database"
|
||||
"git.aiterp.net/rpdata/api/database/mongodb"
|
||||
"git.aiterp.net/rpdata/api/internal/config"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
var flagDriver = flag.String("driver", "mongodb", "The database driver to use.")
|
||||
var flagHost = flag.String("host", "127.0.0.1", "The host to connect to.")
|
||||
var flagPort = flag.Int("port", 27017, "The port to connect on.")
|
||||
var flagDb = flag.String("db", "rpdata", "The database name")
|
||||
var flagUsername = flag.String("username", "", "")
|
||||
var flagPassword = flag.String("password", "", "")
|
||||
var flagMechanism = flag.String("mechanism", "", "")
|
||||
var flagOutputFile = flag.String("outfile", "dump.zip", "The file to write to.")
|
||||
var flagIncludeKeys = flag.Bool("include-keys", false, "Whether to include the keys.")
|
||||
var flagIncludeUsers = flag.Bool("include-users", false, "Whether to include the keys.")
|
||||
var flagIncludeUnlisted = flag.Bool("include-unlisted", false, "Whether to include unlisted stuff.")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
trueValue := true
|
||||
truePtr := &trueValue
|
||||
|
||||
cfg := config.Database{
|
||||
Driver: *flagDriver,
|
||||
Host: *flagHost,
|
||||
Port: *flagPort,
|
||||
Db: *flagDb,
|
||||
Username: *flagUsername,
|
||||
Password: *flagPassword,
|
||||
Mechanism: *flagMechanism,
|
||||
}
|
||||
|
||||
blacklist := make(map[string]bool)
|
||||
|
||||
mongodb.DisableFixes = true
|
||||
|
||||
db, err := database.Init(cfg)
|
||||
if err != nil {
|
||||
log.Fatalln("Failed to open database:", err)
|
||||
}
|
||||
|
||||
file, err := os.OpenFile(*flagOutputFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
log.Fatalln("Failed to open output file:", err)
|
||||
}
|
||||
|
||||
zipWriter := zip.NewWriter(file)
|
||||
defer func() {
|
||||
err = zipWriter.Close()
|
||||
if err != nil {
|
||||
log.Fatalln("Failed to close output file:", err)
|
||||
}
|
||||
}()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute*30)
|
||||
defer cancel()
|
||||
|
||||
log.Println("Dumping characters...")
|
||||
characters, err := db.Characters().List(ctx, models.CharacterFilter{})
|
||||
if err != nil {
|
||||
log.Println("Failed to get characters:", err)
|
||||
}
|
||||
if len(characters) == 0 {
|
||||
log.Println("No characters to dump.")
|
||||
}
|
||||
for i, character := range characters {
|
||||
err := writeJsonFile(zipWriter, "character", i, character.ID, time.Now(), character)
|
||||
if err != nil {
|
||||
log.Println("Failed to write character", character.ID, ":", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("Dumping channels...")
|
||||
channels, err := db.Channels().List(ctx, models.ChannelFilter{})
|
||||
if err != nil {
|
||||
log.Println("Failed to get channels:", err)
|
||||
}
|
||||
if len(channels) == 0 {
|
||||
log.Println("No channels to dump.")
|
||||
}
|
||||
for i, channel := range channels {
|
||||
err := writeJsonFile(zipWriter, "channel", i, channel.Name, time.Now(), channel)
|
||||
if err != nil {
|
||||
log.Println("Failed to write channel", channel.Name, ":", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("Dumping changes...")
|
||||
changes, err := db.Changes().List(ctx, models.ChangeFilter{})
|
||||
if err != nil {
|
||||
log.Println("Failed to get changes:", err)
|
||||
}
|
||||
if len(changes) == 0 {
|
||||
log.Println("No changes to dump.")
|
||||
}
|
||||
for i, change := range changes {
|
||||
if !*flagIncludeUnlisted && !change.Listed {
|
||||
continue
|
||||
}
|
||||
|
||||
err := writeJsonFile(zipWriter, "change", i, change.ID, change.Date, change)
|
||||
if err != nil {
|
||||
log.Println("Failed to write change", change.ID, ":", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("Dumping stories...")
|
||||
stories, err := db.Stories().List(ctx, models.StoryFilter{})
|
||||
if err != nil {
|
||||
log.Println("Failed to get stories:", err)
|
||||
}
|
||||
unlistedStories, err := db.Stories().List(ctx, models.StoryFilter{Unlisted: truePtr})
|
||||
if err != nil {
|
||||
log.Println("Failed to get unlisted stories:", err)
|
||||
} else {
|
||||
stories = append(stories, unlistedStories...)
|
||||
}
|
||||
if len(stories) == 0 {
|
||||
log.Println("No stories to dump.")
|
||||
}
|
||||
for i, story := range stories {
|
||||
if !*flagIncludeUnlisted && !story.Listed {
|
||||
blacklist[story.ID] = true
|
||||
continue
|
||||
}
|
||||
|
||||
err := writeJsonFile(zipWriter, "story", i, story.ID, story.CreatedDate, story)
|
||||
if err != nil {
|
||||
log.Println("Failed to write story", story.ID, ":", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("Dumping chapters...")
|
||||
chapters, err := db.Chapters().List(ctx, models.ChapterFilter{})
|
||||
if err != nil {
|
||||
log.Println("Failed to get chapters:", err)
|
||||
}
|
||||
if len(chapters) == 0 {
|
||||
log.Println("No chapters to dump.")
|
||||
}
|
||||
for i, chapter := range chapters {
|
||||
if !*flagIncludeUnlisted && blacklist[chapter.StoryID] {
|
||||
blacklist[chapter.ID] = true
|
||||
continue
|
||||
}
|
||||
|
||||
err := writeJsonFile(zipWriter, "chapter", i, chapter.ID, chapter.CreatedDate, chapter)
|
||||
if err != nil {
|
||||
log.Println("Failed to write chapter", chapter.ID, ":", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("Dumping comments...")
|
||||
comments, err := db.Comments().List(ctx, models.CommentFilter{})
|
||||
if err != nil {
|
||||
log.Println("Failed to get comments:", err)
|
||||
}
|
||||
if len(comments) == 0 {
|
||||
log.Println("No comments to dump.")
|
||||
}
|
||||
for i, comment := range comments {
|
||||
if !*flagIncludeUnlisted && blacklist[comment.ChapterID] {
|
||||
continue
|
||||
}
|
||||
|
||||
err := writeJsonFile(zipWriter, "comment", i, comment.ID, comment.CreatedDate, comment)
|
||||
if err != nil {
|
||||
log.Println("Failed to write comment", comment.ID, ":", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("Dumping posts...")
|
||||
posts, err := db.Posts().List(ctx, models.PostFilter{})
|
||||
if err != nil {
|
||||
log.Println("Failed to get posts:", err)
|
||||
}
|
||||
if len(posts) == 0 {
|
||||
log.Println("No posts to dump.")
|
||||
}
|
||||
for i, post := range posts {
|
||||
err := writeJsonFile(zipWriter, "post", i, post.ID, post.Time, post)
|
||||
if err != nil {
|
||||
log.Println("Failed to write post", post.ID, ":", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("Dumping logs...")
|
||||
logs, err := db.Logs().List(ctx, models.LogFilter{})
|
||||
if err != nil {
|
||||
log.Println("Failed to get logs:", err)
|
||||
}
|
||||
if len(logs) == 0 {
|
||||
log.Println("No logs to dump.")
|
||||
}
|
||||
for i, logEntry := range logs {
|
||||
err := writeJsonFile(zipWriter, "log", i, logEntry.ID, logEntry.Date, logEntry)
|
||||
if err != nil {
|
||||
log.Println("Failed to write post", logEntry.ID, ":", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if *flagIncludeUsers {
|
||||
log.Println("Dumping users...")
|
||||
users, err := db.Users().List(ctx)
|
||||
if err != nil {
|
||||
log.Println("Failed to get users:", err)
|
||||
}
|
||||
if len(users) == 0 {
|
||||
log.Println("No users to dump.")
|
||||
}
|
||||
for i, userEntry := range users {
|
||||
err := writeJsonFile(zipWriter, "user", i, userEntry.ID, time.Now(), userEntry)
|
||||
if err != nil {
|
||||
log.Println("Failed to write user", userEntry.ID, ":", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if *flagIncludeKeys {
|
||||
log.Println("Dumping keys...")
|
||||
keys, err := db.Keys().List(ctx, models.KeyFilter{})
|
||||
if err != nil {
|
||||
log.Println("Failed to get users:", err)
|
||||
}
|
||||
if len(keys) == 0 {
|
||||
log.Println("No users to dump.")
|
||||
}
|
||||
for i, keyEntry := range keys {
|
||||
err := writeJsonFile(zipWriter, "key", i, keyEntry.ID, time.Now(), keyEntry)
|
||||
if err != nil {
|
||||
log.Println("Failed to write key", keyEntry.ID, ":", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func writeJsonFile(zw *zip.Writer, model string, idx int, id string, t time.Time, data interface{}) error {
|
||||
w, err := zw.CreateHeader(&zip.FileHeader{
|
||||
Name: fmt.Sprintf("rpdata_dump_v1/%s/%06d_%s.json", model, idx, id),
|
||||
Modified: t,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return json.NewEncoder(w).Encode(data)
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"git.aiterp.net/rpdata/api/model/channel"
|
||||
|
||||
"git.aiterp.net/rpdata/api/internal/store"
|
||||
"git.aiterp.net/rpdata/api/model/log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
err := store.Init()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return
|
||||
}
|
||||
|
||||
logs, err := log.List(32768)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return
|
||||
}
|
||||
|
||||
added := make(map[string]bool, 1024)
|
||||
for _, log := range logs {
|
||||
if added[log.Channel] {
|
||||
continue
|
||||
}
|
||||
|
||||
_, err := channel.Ensure(log.Channel, false)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, log.ID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
added[log.Channel] = true
|
||||
|
||||
fmt.Println(log.Channel, "ensured")
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
config.json
|
||||
debug
|
||||
@@ -1,150 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graphql/loader"
|
||||
"git.aiterp.net/rpdata/api/graphql/resolver"
|
||||
"git.aiterp.net/rpdata/api/graphql/schema"
|
||||
"git.aiterp.net/rpdata/api/internal/session"
|
||||
"git.aiterp.net/rpdata/api/internal/store"
|
||||
"git.aiterp.net/rpdata/api/model/change"
|
||||
"git.aiterp.net/rpdata/api/model/file"
|
||||
logModel "git.aiterp.net/rpdata/api/model/log"
|
||||
graphql "github.com/graph-gophers/graphql-go"
|
||||
"github.com/graph-gophers/graphql-go/relay"
|
||||
)
|
||||
|
||||
func main() {
|
||||
err := store.Init()
|
||||
if err != nil {
|
||||
log.Fatalln("Failed to init store:", err)
|
||||
}
|
||||
|
||||
n, err := logModel.UpdateAllCharacters()
|
||||
if err != nil {
|
||||
log.Println("Charcter updated stopped:", err)
|
||||
}
|
||||
log.Println("Updated characters on", n, "logs")
|
||||
|
||||
schema, err := graphql.ParseSchema(schema.String(), &resolver.Resolver{}, graphql.MaxParallelism(48))
|
||||
if err != nil {
|
||||
log.Fatalln("Failed to parse schema:", err)
|
||||
}
|
||||
|
||||
http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" {
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(page)
|
||||
}))
|
||||
|
||||
relayHandler := &relay.Handler{Schema: schema}
|
||||
http.HandleFunc("/graphql", func(w http.ResponseWriter, r *http.Request) {
|
||||
r = session.Load(w, r)
|
||||
|
||||
l := loader.New()
|
||||
r = r.WithContext(l.ToContext(r.Context()))
|
||||
|
||||
relayHandler.ServeHTTP(w, r)
|
||||
})
|
||||
|
||||
http.HandleFunc("/upload", func(w http.ResponseWriter, r *http.Request) {
|
||||
r = session.Load(w, r)
|
||||
sess := session.FromContext(r.Context())
|
||||
user := sess.User()
|
||||
if user == nil || !user.Permitted("file.upload") {
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
if err := r.ParseMultipartForm(16384); err != nil {
|
||||
http.Error(w, "Internal (1): "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
formFile, header, err := r.FormFile("file")
|
||||
if err != nil || header == nil || formFile == nil {
|
||||
http.Error(w, "No file provided", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
file, err := file.Upload(r.Context(), header.Filename, header.Header.Get("Content-Type"), user.ID, header.Size, formFile)
|
||||
if err != nil {
|
||||
http.Error(w, "Internal (2): "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
json, err := json.Marshal(file)
|
||||
if err != nil {
|
||||
http.Error(w, "Internal (3): "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(json)
|
||||
})
|
||||
|
||||
change.InitializeTimeline()
|
||||
go func() {
|
||||
index := int64(0)
|
||||
|
||||
for {
|
||||
index++
|
||||
ch, err := change.Timeline.Get(context.Background(), index)
|
||||
if err == change.ErrExpired {
|
||||
continue
|
||||
} else if err != nil {
|
||||
log.Println("Timeline:", err)
|
||||
break
|
||||
}
|
||||
|
||||
log.Printf("Change: time=%s model=%s op=%s id=%s author=%s\n", ch.Time.Local().Format("[Mon Jan 02 15:04 MST]"), ch.Model, ch.Op, ch.ObjectID, ch.Author)
|
||||
}
|
||||
}()
|
||||
|
||||
log.Fatal(http.ListenAndServe(":17000", nil))
|
||||
}
|
||||
|
||||
var page = []byte(`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/graphiql/0.10.2/graphiql.css" />
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/1.1.0/fetch.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.5.4/react.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.5.4/react-dom.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/graphiql/0.10.2/graphiql.js"></script>
|
||||
</head>
|
||||
<body style="width: 100%; height: 100%; margin: 0; overflow: hidden;">
|
||||
<div id="graphiql" style="height: 100vh;">Loading...</div>
|
||||
<script>
|
||||
function graphQLFetcher(graphQLParams) {
|
||||
return fetch("/graphql", {
|
||||
method: "post",
|
||||
body: JSON.stringify(graphQLParams),
|
||||
credentials: "include",
|
||||
}).then(function (response) {
|
||||
return response.text();
|
||||
}).then(function (responseBody) {
|
||||
try {
|
||||
return JSON.parse(responseBody);
|
||||
} catch (error) {
|
||||
return responseBody;
|
||||
}
|
||||
});
|
||||
}
|
||||
ReactDOM.render(
|
||||
React.createElement(GraphiQL, {fetcher: graphQLFetcher}),
|
||||
document.getElementById("graphiql")
|
||||
);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`)
|
||||
@@ -1,3 +0,0 @@
|
||||
characters.json
|
||||
characters.cson
|
||||
debug
|
||||
@@ -1,55 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type charInfo struct {
|
||||
Nicks []string `json:"nicks"`
|
||||
Name string `json:"name"`
|
||||
Author string `json:"player"`
|
||||
ShortName string `json:"first"`
|
||||
}
|
||||
|
||||
func load(reader io.Reader) ([]charInfo, error) {
|
||||
data := make(map[string]interface{})
|
||||
err := json.NewDecoder(reader).Decode(&data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
links := make(map[string]string, len(data))
|
||||
infos := make([]charInfo, 0, 64)
|
||||
|
||||
for key, value := range data {
|
||||
if info, ok := value.(map[string]interface{}); ok {
|
||||
name := info["name"].(string)
|
||||
author := info["player"].(string)
|
||||
shortName, ok := info["first"].(string)
|
||||
if !ok {
|
||||
shortName = strings.SplitN(name, " ", 2)[0]
|
||||
}
|
||||
|
||||
infos = append(infos, charInfo{
|
||||
Nicks: []string{key},
|
||||
Name: name,
|
||||
Author: author,
|
||||
ShortName: shortName,
|
||||
})
|
||||
} else if nick, ok := value.(string); ok {
|
||||
links[key] = nick
|
||||
}
|
||||
}
|
||||
|
||||
for key, value := range links {
|
||||
for i := range infos {
|
||||
if infos[i].Nicks[0] == value {
|
||||
infos[i].Nicks = append(infos[i].Nicks, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return infos, nil
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"git.aiterp.net/rpdata/api/internal/store"
|
||||
"git.aiterp.net/rpdata/api/model/character"
|
||||
)
|
||||
|
||||
var fileName = flag.String("file", "./characters.json", "json file to load")
|
||||
|
||||
func main() {
|
||||
file, err := os.Open(*fileName)
|
||||
if err != nil {
|
||||
log.Fatalln("Open:", err)
|
||||
}
|
||||
|
||||
infos, err := load(file)
|
||||
if err != nil {
|
||||
log.Fatalln("Parse:", err)
|
||||
}
|
||||
|
||||
err = store.Init()
|
||||
if err != nil {
|
||||
log.Fatalln("Store:", err)
|
||||
}
|
||||
|
||||
charsAdded, charsFailed := 0, 0
|
||||
nicksAdded, nicksFailed := 0, 0
|
||||
|
||||
for _, info := range infos {
|
||||
char, err := character.New(info.Nicks[0], info.Name, info.ShortName, info.Author, "")
|
||||
if err != nil {
|
||||
log.Println(info.Nicks[0], "failed to insert:", err)
|
||||
charsFailed++
|
||||
|
||||
char, err = character.FindNick(info.Nicks[0])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
log.Println(info.Nicks[0], "added")
|
||||
charsAdded++
|
||||
}
|
||||
|
||||
for _, alt := range info.Nicks[1:] {
|
||||
err := char.AddNick(alt)
|
||||
if err != nil {
|
||||
log.Println(info.Nicks[0], "failed to add nick", alt, "error:", err)
|
||||
nicksFailed++
|
||||
} else {
|
||||
log.Println(info.Nicks[0], "addded nick", alt)
|
||||
nicksAdded++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("Characters – %d/%d", charsAdded, charsFailed+charsAdded)
|
||||
log.Printf("Alt. Nicks – %d/%d", nicksAdded, nicksFailed+nicksAdded)
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
logs
|
||||
debug
|
||||
@@ -1,85 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type logLine struct {
|
||||
Verb string
|
||||
Args []string
|
||||
Text string
|
||||
}
|
||||
|
||||
func parseFile(reader io.Reader) []logLine {
|
||||
bufReader := bufio.NewReader(reader)
|
||||
results := make([]logLine, 0, 512)
|
||||
|
||||
for {
|
||||
line, err := bufReader.ReadString('\n')
|
||||
if err == io.EOF {
|
||||
if len(line) < 2 {
|
||||
break
|
||||
}
|
||||
} else if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if len(line) <= 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
line = strings.Replace(line, "\n", "", 1)
|
||||
line = strings.Replace(line, "\r", "", 1)
|
||||
|
||||
results = append(results, parseLine(line))
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func parseLine(line string) logLine {
|
||||
textSplit := strings.SplitN(line, " :", 2)
|
||||
tokens := strings.Split(textSplit[0], " ")
|
||||
|
||||
ll := logLine{
|
||||
Verb: tokens[0],
|
||||
}
|
||||
|
||||
if len(tokens) > 1 {
|
||||
ll.Args = tokens[1:]
|
||||
}
|
||||
|
||||
if len(textSplit) > 1 {
|
||||
ll.Text = textSplit[1]
|
||||
}
|
||||
|
||||
return ll
|
||||
}
|
||||
|
||||
func parseFilename(fname string) (date time.Time, channel string, err error) {
|
||||
date, err = time.ParseInLocation("2006-01-02_150405", fname[:17], time.Local)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ms, err := strconv.Atoi(fname[17:20])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
date = date.Add(time.Duration(ms) * time.Millisecond)
|
||||
|
||||
if len(fname) < 23 {
|
||||
err = errors.New("filename too short")
|
||||
return
|
||||
}
|
||||
|
||||
channel = fname[21:]
|
||||
|
||||
return
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/internal/store"
|
||||
logModel "git.aiterp.net/rpdata/api/model/log"
|
||||
)
|
||||
|
||||
var prefixReplacer = strings.NewReplacer("+", "", "@", "", "!", "", "%", "")
|
||||
|
||||
func main() {
|
||||
err := store.Init()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
for _, filepath := range os.Args[1:] {
|
||||
name := strings.Replace(path.Base(filepath), ".txt", "", 1)
|
||||
file, err := os.Open(filepath)
|
||||
if err != nil {
|
||||
log.Println(filepath, err)
|
||||
continue
|
||||
}
|
||||
logLines := parseFile(file)
|
||||
file.Close()
|
||||
|
||||
// Get title and event
|
||||
title := ""
|
||||
event := ""
|
||||
for _, line := range logLines {
|
||||
if line.Verb == "TITLE" {
|
||||
title = line.Text
|
||||
}
|
||||
if line.Verb == "TAG" {
|
||||
event = line.Text
|
||||
}
|
||||
}
|
||||
|
||||
date, channel, err := parseFilename(name)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
l, err := logModel.New(date, channel, title, event, "", false)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
|
||||
_, err = l.NewPost(time.Now(), "annotation.info", "rpdata-lb2logimport", "This logfile is imported from aitelogs2 and may contain errors or wrong timestamps.")
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
for _, line := range logLines {
|
||||
if line.Verb != "CHARS" {
|
||||
continue
|
||||
}
|
||||
|
||||
_, err = l.NewPost(l.Date, "chars", prefixReplacer.Replace(line.Args[0]), line.Text)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, line := range logLines {
|
||||
if line.Verb != "SCENE" && line.Verb != "ACTION" && line.Verb != "TEXT" {
|
||||
continue
|
||||
}
|
||||
|
||||
postTime, err := time.ParseInLocation("2006-01-02 15:04:05", date.Format("2006-01-02")+" "+line.Args[1], time.Local)
|
||||
diff := postTime.Sub(date)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
if diff < 0 {
|
||||
if diff > -time.Second {
|
||||
postTime = postTime.Add(diff)
|
||||
} else {
|
||||
postTime = postTime.Add(time.Hour * 24)
|
||||
}
|
||||
}
|
||||
|
||||
if line.Args[0][0] == '=' {
|
||||
line.Verb = "SCENE"
|
||||
}
|
||||
|
||||
_, err = l.NewPost(postTime, strings.ToLower(line.Verb), prefixReplacer.Replace(line.Args[0]), line.Text)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
err = l.UpdateCharacters()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
fmt.Println(l.ID, "completed")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"git.aiterp.net/rpdata/api/database"
|
||||
"git.aiterp.net/rpdata/api/database/mongodb"
|
||||
"git.aiterp.net/rpdata/api/internal/config"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var flagDriver = flag.String("driver", "postgres", "The database driver to use.")
|
||||
var flagHost = flag.String("host", "127.0.0.1", "The host to connect to.")
|
||||
var flagPort = flag.Int("port", 5432, "The port to connect on.")
|
||||
var flagDb = flag.String("db", "rpdata", "The database name")
|
||||
var flagUsername = flag.String("username", "", "")
|
||||
var flagPassword = flag.String("password", "", "")
|
||||
var flagMechanism = flag.String("mechanism", "", "")
|
||||
var flagInputFile = flag.String("infile", "dump.zip", "The file to read from.")
|
||||
var flagIncludeKeys = flag.Bool("include-keys", false, "Whether to include the keys.")
|
||||
var flagReplace = flag.Bool("replace", false, "Replace existing content")
|
||||
var flagOnly = flag.String("only", "", "Only restore these models (e.g. \"log,post\")")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
cfg := config.Database{
|
||||
Driver: *flagDriver,
|
||||
Host: *flagHost,
|
||||
Port: *flagPort,
|
||||
Db: *flagDb,
|
||||
Username: *flagUsername,
|
||||
Password: *flagPassword,
|
||||
Mechanism: *flagMechanism,
|
||||
RestoreIDs: true,
|
||||
}
|
||||
|
||||
var only map[string]bool
|
||||
if *flagOnly != "" {
|
||||
only = make(map[string]bool)
|
||||
|
||||
for _, part := range strings.Split(*flagOnly, ",") {
|
||||
only[strings.Trim(part, " \t\n\r")] = true
|
||||
}
|
||||
}
|
||||
|
||||
mongodb.DisableFixes = true
|
||||
|
||||
db, err := database.Init(cfg)
|
||||
if err != nil {
|
||||
log.Fatalln("Failed to open database:", err)
|
||||
}
|
||||
|
||||
cfg2 := cfg
|
||||
cfg2.RestoreIDs = false
|
||||
|
||||
zipReader, err := zip.OpenReader(*flagInputFile)
|
||||
if err != nil {
|
||||
log.Fatalln("Failed to open input file:", err)
|
||||
}
|
||||
defer func() {
|
||||
err = zipReader.Close()
|
||||
if err != nil {
|
||||
log.Fatalln("Failed to close input file:", err)
|
||||
}
|
||||
}()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute*30)
|
||||
defer cancel()
|
||||
|
||||
postMap := make(map[string][]*models.Post)
|
||||
|
||||
for _, file := range zipReader.File {
|
||||
if strings.HasSuffix(file.Name, "/") {
|
||||
continue
|
||||
}
|
||||
|
||||
parts := strings.Split(file.Name, "/")
|
||||
if len(parts) < 3 || parts[0] != "rpdata_dump_v1" {
|
||||
log.Fatalln("Unrecognized file path:", file.Name)
|
||||
}
|
||||
|
||||
if only != nil && !only[parts[1]] {
|
||||
continue
|
||||
}
|
||||
|
||||
reader, err := file.Open()
|
||||
if err != nil {
|
||||
log.Fatalln("Unrecognized file:", file.Name, err)
|
||||
}
|
||||
|
||||
hideList := make(map[string]bool)
|
||||
|
||||
log.Println("Loaded", parts[1], parts[2], "from archive.")
|
||||
|
||||
switch parts[1] {
|
||||
case "character":
|
||||
{
|
||||
character := models.Character{}
|
||||
err := json.NewDecoder(reader).Decode(&character)
|
||||
if err != nil {
|
||||
log.Fatalln("Could not parse character:", parts[2], err)
|
||||
}
|
||||
|
||||
if *flagReplace {
|
||||
_ = db.Characters().Delete(ctx, character)
|
||||
}
|
||||
|
||||
_, err = db.Characters().Insert(ctx, character)
|
||||
if err != nil {
|
||||
log.Fatalln("Could not insert character:", parts[2], err)
|
||||
}
|
||||
|
||||
log.Println("Character", character.Name, "inserted.")
|
||||
}
|
||||
case "channel":
|
||||
{
|
||||
channel := models.Channel{}
|
||||
err := json.NewDecoder(reader).Decode(&channel)
|
||||
if err != nil {
|
||||
log.Fatalln("Could not parse channel:", parts[2], err)
|
||||
}
|
||||
|
||||
if *flagReplace {
|
||||
_ = db.Channels().Remove(ctx, channel)
|
||||
}
|
||||
|
||||
_, err = db.Channels().Insert(ctx, channel)
|
||||
if err != nil {
|
||||
log.Fatalln("Could not insert channel:", parts[2], err)
|
||||
}
|
||||
|
||||
log.Println("Channel", channel.Name, "inserted.")
|
||||
}
|
||||
case "change":
|
||||
{
|
||||
change := models.Change{}
|
||||
err := json.NewDecoder(reader).Decode(&change)
|
||||
if err != nil {
|
||||
log.Fatalln("Could not parse character:", parts[2], err)
|
||||
}
|
||||
|
||||
if *flagReplace {
|
||||
_ = db.Changes().Remove(ctx, change)
|
||||
}
|
||||
|
||||
_, err = db.Changes().Insert(ctx, change)
|
||||
if err != nil {
|
||||
log.Fatalln("Could not insert character:", parts[2], err)
|
||||
}
|
||||
|
||||
if change.Listed {
|
||||
log.Println("Change", change.ID, "inserted.")
|
||||
} else {
|
||||
log.Println("Unlisted change inserted.")
|
||||
}
|
||||
}
|
||||
case "story":
|
||||
{
|
||||
story := models.Story{}
|
||||
err := json.NewDecoder(reader).Decode(&story)
|
||||
if err != nil {
|
||||
log.Fatalln("Could not parse story:", parts[2], err)
|
||||
}
|
||||
|
||||
if *flagReplace {
|
||||
_ = db.Stories().Delete(ctx, story)
|
||||
}
|
||||
|
||||
_, err = db.Stories().Insert(ctx, story)
|
||||
if err != nil {
|
||||
log.Fatalln("Could not insert story:", parts[2], err)
|
||||
}
|
||||
|
||||
if story.Listed {
|
||||
log.Println("Story", story.Name, "inserted.")
|
||||
} else {
|
||||
log.Println("Unlisted story inserted.")
|
||||
hideList[story.ID] = true
|
||||
}
|
||||
}
|
||||
case "chapter":
|
||||
{
|
||||
chapter := models.Chapter{}
|
||||
err := json.NewDecoder(reader).Decode(&chapter)
|
||||
if err != nil {
|
||||
log.Fatalln("Could not parse story:", parts[2], err)
|
||||
}
|
||||
|
||||
if *flagReplace {
|
||||
_ = db.Chapters().Delete(ctx, chapter)
|
||||
}
|
||||
|
||||
_, err = db.Chapters().Insert(ctx, chapter)
|
||||
if err != nil {
|
||||
log.Fatalln("Could not insert story:", parts[2], err)
|
||||
}
|
||||
|
||||
if !hideList[chapter.StoryID] {
|
||||
log.Println("Chapter", fmt.Sprintf("%s (id: %s)", chapter.Title, chapter.ID), "inserted.")
|
||||
} else {
|
||||
log.Println("Unlisted chapter inserted.")
|
||||
hideList[chapter.ID] = true
|
||||
}
|
||||
}
|
||||
case "comment":
|
||||
{
|
||||
comment := models.Comment{}
|
||||
err := json.NewDecoder(reader).Decode(&comment)
|
||||
if err != nil {
|
||||
log.Fatalln("Could not parse story:", parts[2], err)
|
||||
}
|
||||
|
||||
if *flagReplace {
|
||||
_ = db.Comments().Delete(ctx, comment)
|
||||
}
|
||||
|
||||
_, err = db.Comments().Insert(ctx, comment)
|
||||
if err != nil {
|
||||
log.Fatalln("Could not insert story:", parts[2], err)
|
||||
}
|
||||
|
||||
if !hideList[comment.ChapterID] {
|
||||
log.Println("Comment", comment.Subject, "inserted.")
|
||||
} else {
|
||||
log.Println("Unlisted comment inserted.")
|
||||
}
|
||||
}
|
||||
|
||||
case "log":
|
||||
{
|
||||
logg := models.Log{}
|
||||
err := json.NewDecoder(reader).Decode(&logg)
|
||||
if err != nil {
|
||||
log.Fatalln("Could not parse log:", parts[2], err)
|
||||
}
|
||||
|
||||
if *flagReplace {
|
||||
_ = db.Logs().Delete(ctx, logg)
|
||||
}
|
||||
|
||||
_, err = db.Logs().Insert(ctx, logg)
|
||||
if err != nil {
|
||||
log.Fatalln("Could not insert log:", parts[2], err)
|
||||
}
|
||||
|
||||
log.Println("Log", logg.Date.Format(time.RFC3339)[:16], logg.ChannelName, "inserted.")
|
||||
}
|
||||
case "post":
|
||||
{
|
||||
post := models.Post{}
|
||||
err := json.NewDecoder(reader).Decode(&post)
|
||||
if err != nil {
|
||||
log.Fatalln("Could not parse post:", parts[2], err)
|
||||
}
|
||||
|
||||
postMap[post.LogID] = append(postMap[post.LogID], &post)
|
||||
}
|
||||
|
||||
case "user":
|
||||
{
|
||||
user := models.User{}
|
||||
err := json.NewDecoder(reader).Decode(&user)
|
||||
if err != nil {
|
||||
log.Fatalln("Could not parse post:", parts[2], err)
|
||||
}
|
||||
|
||||
_, err = db.Users().Insert(ctx, user)
|
||||
if err != nil {
|
||||
log.Fatalln("Could not insert user:", parts[2], err)
|
||||
}
|
||||
|
||||
log.Println("User", user.ID, "inserted.")
|
||||
}
|
||||
case "key":
|
||||
{
|
||||
if !*flagIncludeKeys {
|
||||
break
|
||||
}
|
||||
|
||||
key := models.Key{}
|
||||
err := json.NewDecoder(reader).Decode(&key)
|
||||
if err != nil {
|
||||
log.Fatalln("Could not parse key:", parts[2], err)
|
||||
}
|
||||
|
||||
_, err = db.Keys().Insert(ctx, key)
|
||||
if err != nil {
|
||||
log.Fatalln("Could not insert key:", parts[2], err)
|
||||
}
|
||||
|
||||
log.Println("Key", key.ID, "inserted.")
|
||||
}
|
||||
}
|
||||
|
||||
_ = reader.Close()
|
||||
}
|
||||
|
||||
if only == nil || only["post"] {
|
||||
for _, posts := range postMap {
|
||||
log.Printf("Inserting %d posts for log %s...", len(posts), posts[0].LogID)
|
||||
|
||||
_, err = db.Posts().InsertMany(ctx, posts...)
|
||||
if err != nil {
|
||||
log.Fatalln("Could not insert post for logId:", posts[0].LogID, err)
|
||||
}
|
||||
|
||||
log.Println("\tDone!")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"git.aiterp.net/rpdata/api/database"
|
||||
"git.aiterp.net/rpdata/api/graph2"
|
||||
"git.aiterp.net/rpdata/api/internal/config"
|
||||
"git.aiterp.net/rpdata/api/internal/instrumentation"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"git.aiterp.net/rpdata/api/services"
|
||||
"git.aiterp.net/rpdata/api/space"
|
||||
"github.com/99designs/gqlgen/handler"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"log"
|
||||
"net/http"
|
||||
"runtime/debug"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cfg := config.Global()
|
||||
|
||||
db, err := database.Init(cfg.Database)
|
||||
if err != nil {
|
||||
log.Fatalln("Failed to init db:", err)
|
||||
}
|
||||
defer db.Close(context.Background())
|
||||
|
||||
var spaceClient *space.Client
|
||||
if cfg.Space.Enabled {
|
||||
spaceClient, err = space.Connect(cfg.Space)
|
||||
if err != nil {
|
||||
log.Fatalln("Failed to init space:", err)
|
||||
}
|
||||
|
||||
log.Println("Space loaded")
|
||||
} else {
|
||||
log.Println("Space is disabled, file upload will not work!")
|
||||
}
|
||||
|
||||
serviceBundle := services.NewBundle(db, spaceClient)
|
||||
|
||||
instrumentation.Register()
|
||||
|
||||
http.Handle("/", handler.Playground("RPData API", "/graphql"))
|
||||
http.Handle("/graphql", queryHandler(serviceBundle))
|
||||
|
||||
http.Handle("/metrics", promhttp.Handler())
|
||||
|
||||
go func() {
|
||||
err := serviceBundle.Logs.RefreshAllLogCharacters(context.Background())
|
||||
if err != nil {
|
||||
log.Println("Refresh Characters:", err)
|
||||
}
|
||||
}()
|
||||
|
||||
go logListedChanges(serviceBundle.Changes)
|
||||
|
||||
log.Fatal(http.ListenAndServe(":8081", nil))
|
||||
}
|
||||
|
||||
func logListedChanges(changes *services.ChangeService) {
|
||||
sub := changes.Subscribe(context.Background(), models.ChangeFilter{})
|
||||
|
||||
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(services *services.Bundle) http.HandlerFunc {
|
||||
handler := handler.GraphQL(
|
||||
graph2.New(services),
|
||||
handler.UploadMaxSize(10485760),
|
||||
handler.UploadMaxMemory(1048576),
|
||||
handler.RecoverFunc(func(ctx context.Context, err interface{}) error {
|
||||
log.Println(err)
|
||||
log.Println(string(debug.Stack()))
|
||||
|
||||
return fmt.Errorf("The server failed to serve your request due to an internal error, it has been logged")
|
||||
}),
|
||||
handler.ComplexityLimit(300),
|
||||
handler.RequestMiddleware(instrumentation.RequestMiddleware()),
|
||||
handler.ResolverMiddleware(instrumentation.ResolverMiddleware()),
|
||||
)
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
r = services.Auth.RequestWithToken(r)
|
||||
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/internal/config"
|
||||
"git.aiterp.net/rpdata/api/internal/store"
|
||||
"git.aiterp.net/rpdata/api/model/file"
|
||||
)
|
||||
|
||||
func main() {
|
||||
client := http.Client{Timeout: time.Second * 30}
|
||||
|
||||
res, err := client.Get(config.Global().Wiki.URL + "?action=query&list=allimages&ailimit=5&aiprop=dimensions|mime|url|user|timestamp&format=json&ailimit=500")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
if res.StatusCode != 200 {
|
||||
log.Fatalln(res.Status)
|
||||
}
|
||||
|
||||
data := ResponseData{}
|
||||
err = json.NewDecoder(res.Body).Decode(&data)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
store.Init()
|
||||
|
||||
for _, info := range data.Query.Allimages {
|
||||
existing, err := file.FindName("wiki", info.Name, info.User)
|
||||
if err == nil && existing.ID != "" {
|
||||
log.Println("Skipping", info.Name, "because it already exists")
|
||||
continue
|
||||
}
|
||||
|
||||
ts, err := time.Parse(time.RFC3339, info.Timestamp)
|
||||
if err != nil {
|
||||
log.Println("Skipping", info.Name, "error:", err)
|
||||
continue
|
||||
}
|
||||
|
||||
file, err := file.Insert(info.Name, "wiki", info.Mime, info.User, ts, info.Size, info.URL)
|
||||
if err != nil {
|
||||
log.Println("Skipping", info.Name, "error:", err)
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Println(file.Name, "inserted ( id:", file.ID, ")")
|
||||
}
|
||||
}
|
||||
|
||||
// The ResponseData from the wiki looks like this
|
||||
type ResponseData struct {
|
||||
Batchcomplete string `json:"batchcomplete"`
|
||||
Query struct {
|
||||
Allimages []struct {
|
||||
Descriptionshorturl string `json:"descriptionshorturl"`
|
||||
Descriptionurl string `json:"descriptionurl"`
|
||||
Height int `json:"height"`
|
||||
Mime string `json:"mime"`
|
||||
Name string `json:"name"`
|
||||
Ns int `json:"ns"`
|
||||
Size int64 `json:"size"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
Title string `json:"title"`
|
||||
URL string `json:"url"`
|
||||
User string `json:"user"`
|
||||
Width int `json:"width"`
|
||||
} `json:"allimages"`
|
||||
} `json:"query"`
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"git.aiterp.net/rpdata/api/database/postgres"
|
||||
|
||||
"git.aiterp.net/rpdata/api/database/mongodb"
|
||||
"git.aiterp.net/rpdata/api/internal/config"
|
||||
"git.aiterp.net/rpdata/api/repositories"
|
||||
)
|
||||
|
||||
// ErrDriverUnrecognized is returned if the driver is not recognized
|
||||
var ErrDriverUnrecognized = errors.New("driver not recognized, check installed version or your configuration")
|
||||
|
||||
type Database interface {
|
||||
Changes() repositories.ChangeRepository
|
||||
Channels() repositories.ChannelRepository
|
||||
Characters() repositories.CharacterRepository
|
||||
Tags() repositories.TagRepository
|
||||
Logs() repositories.LogRepository
|
||||
Posts() repositories.PostRepository
|
||||
Stories() repositories.StoryRepository
|
||||
Chapters() repositories.ChapterRepository
|
||||
Comments() repositories.CommentRepository
|
||||
Keys() repositories.KeyRepository
|
||||
Users() repositories.UserRepository
|
||||
Files() repositories.FileRepository
|
||||
|
||||
Close(ctx context.Context) error
|
||||
}
|
||||
|
||||
// Init sets up the database.
|
||||
func Init(config config.Database) (Database, error) {
|
||||
switch config.Driver {
|
||||
case "mongo", "mgo", "mongodb":
|
||||
return mongodb.Init(config)
|
||||
case "postgres", "psql", "pq", "postgressql":
|
||||
return postgres.Connect(config)
|
||||
default:
|
||||
return nil, ErrDriverUnrecognized
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package mongodb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"git.aiterp.net/rpdata/api/repositories"
|
||||
"github.com/globalsign/mgo"
|
||||
"github.com/globalsign/mgo/bson"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type changeRepository struct {
|
||||
restoreIDs bool
|
||||
changes *mgo.Collection
|
||||
idCounter *counter
|
||||
}
|
||||
|
||||
func (r *changeRepository) Find(ctx context.Context, id string) (*models.Change, error) {
|
||||
change := new(models.Change)
|
||||
err := r.changes.FindId(id).One(change)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return change, nil
|
||||
}
|
||||
|
||||
func (r *changeRepository) List(ctx context.Context, filter models.ChangeFilter) ([]*models.Change, error) {
|
||||
query := bson.M{}
|
||||
limit := 0
|
||||
if filter.EarliestDate != nil && !filter.EarliestDate.IsZero() {
|
||||
query["date"] = bson.M{"$gte": *filter.EarliestDate}
|
||||
}
|
||||
if filter.LatestDate != nil && !filter.LatestDate.IsZero() {
|
||||
dateBson, ok := query["date"].(bson.M)
|
||||
if !ok {
|
||||
dateBson = bson.M{}
|
||||
}
|
||||
|
||||
dateBson["$gte"] = *filter.EarliestDate
|
||||
}
|
||||
if len(filter.Keys) > 0 {
|
||||
query["keys"] = bson.M{"$in": filter.Keys}
|
||||
} else if !r.restoreIDs {
|
||||
query["listed"] = true
|
||||
}
|
||||
if filter.Author != nil && *filter.Author != "" {
|
||||
query["author"] = *filter.Author
|
||||
}
|
||||
if filter.Limit != nil {
|
||||
limit = *filter.Limit
|
||||
}
|
||||
|
||||
initialSize := 64
|
||||
if limit > 0 && limit < 256 {
|
||||
initialSize = limit
|
||||
}
|
||||
|
||||
changes := make([]*models.Change, 0, initialSize)
|
||||
err := r.changes.Find(query).Sort("-date").Limit(limit).All(&changes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return changes, nil
|
||||
}
|
||||
|
||||
func (r *changeRepository) Insert(ctx context.Context, change models.Change) (*models.Change, error) {
|
||||
if !r.restoreIDs || change.ID == "" {
|
||||
next, err := r.idCounter.Increment(1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
change.ID = "Change_" + strconv.Itoa(next)
|
||||
} else {
|
||||
tokens := strings.Split(change.ID, "_")
|
||||
if len(tokens) != 2 || tokens[0] != "Change" {
|
||||
return nil, errors.New("Invalid change ID")
|
||||
}
|
||||
|
||||
n, err := strconv.Atoi(tokens[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_ = r.idCounter.Bump(n)
|
||||
}
|
||||
|
||||
err := r.changes.Insert(&change)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &change, nil
|
||||
}
|
||||
|
||||
func (r *changeRepository) Remove(ctx context.Context, change models.Change) error {
|
||||
return r.changes.RemoveId(change.ID)
|
||||
}
|
||||
|
||||
func newChangeRepository(db *mgo.Database, restoreIDs bool) (repositories.ChangeRepository, error) {
|
||||
collection := db.C("common.changes")
|
||||
|
||||
// Delete the old index if it exists.
|
||||
_ = collection.DropIndexName("date_1")
|
||||
|
||||
err := collection.EnsureIndex(mgo.Index{
|
||||
Name: "expiry",
|
||||
Key: []string{"date"},
|
||||
ExpireAfter: time.Hour * 2400, // 100 days
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = collection.EnsureIndexKey("author")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = collection.EnsureIndexKey("keys")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &changeRepository{
|
||||
restoreIDs: restoreIDs,
|
||||
changes: collection,
|
||||
idCounter: newCounter(db, "auto_increment", "Change"),
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package mongodb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"github.com/globalsign/mgo"
|
||||
"github.com/globalsign/mgo/bson"
|
||||
)
|
||||
|
||||
func newChannelRepository(db *mgo.Database) (*channelRepository, error) {
|
||||
collection := db.C("common.channels")
|
||||
|
||||
err := collection.EnsureIndexKey("logged")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = collection.EnsureIndexKey("event")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = collection.EnsureIndexKey("location")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &channelRepository{
|
||||
channels: collection,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type channelRepository struct {
|
||||
channels *mgo.Collection
|
||||
}
|
||||
|
||||
func (r *channelRepository) Find(ctx context.Context, name string) (*models.Channel, error) {
|
||||
channel := new(models.Channel)
|
||||
err := r.channels.FindId(name).One(channel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return channel, nil
|
||||
}
|
||||
|
||||
func (r *channelRepository) List(ctx context.Context, filter models.ChannelFilter) ([]*models.Channel, error) {
|
||||
query := bson.M{}
|
||||
if filter.Logged != nil {
|
||||
query["logged"] = *filter.Logged
|
||||
}
|
||||
if filter.EventName != nil {
|
||||
query["eventName"] = *filter.EventName
|
||||
}
|
||||
if filter.LocationName != nil {
|
||||
query["locationName"] = *filter.LocationName
|
||||
}
|
||||
if len(filter.Names) > 0 {
|
||||
query["_id"] = bson.M{"$in": filter.Names}
|
||||
}
|
||||
|
||||
channels := make([]*models.Channel, 0, 32)
|
||||
err := r.channels.Find(query).Limit(filter.Limit).Sort("_id").All(&channels)
|
||||
if err != nil {
|
||||
if err == mgo.ErrNotFound {
|
||||
return channels, nil
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return channels, nil
|
||||
}
|
||||
|
||||
func (r *channelRepository) Insert(ctx context.Context, channel models.Channel) (*models.Channel, error) {
|
||||
err := r.channels.Insert(channel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &channel, nil
|
||||
}
|
||||
|
||||
func (r *channelRepository) Update(ctx context.Context, channel models.Channel, update models.ChannelUpdate) (*models.Channel, error) {
|
||||
updateBson := bson.M{}
|
||||
if update.Logged != nil {
|
||||
updateBson["logged"] = *update.Logged
|
||||
channel.Logged = *update.Logged
|
||||
}
|
||||
if update.LocationName != nil {
|
||||
updateBson["locationName"] = *update.LocationName
|
||||
channel.LocationName = *update.LocationName
|
||||
}
|
||||
if update.EventName != nil {
|
||||
updateBson["eventName"] = *update.EventName
|
||||
channel.EventName = *update.EventName
|
||||
}
|
||||
|
||||
err := r.channels.UpdateId(channel.Name, bson.M{"$set": updateBson})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &channel, nil
|
||||
}
|
||||
|
||||
func (r *channelRepository) Remove(ctx context.Context, channel models.Channel) error {
|
||||
return r.channels.RemoveId(channel.Name)
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package mongodb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"git.aiterp.net/rpdata/api/internal/generate"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"git.aiterp.net/rpdata/api/repositories"
|
||||
"github.com/globalsign/mgo"
|
||||
"github.com/globalsign/mgo/bson"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type chapterRepository struct {
|
||||
restoreIDs bool
|
||||
chapters *mgo.Collection
|
||||
comments *mgo.Collection
|
||||
}
|
||||
|
||||
func newChapterRepository(db *mgo.Database, restoreIDs bool) (repositories.ChapterRepository, error) {
|
||||
collection := db.C("story.chapters")
|
||||
|
||||
err := collection.EnsureIndexKey("storyId")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = collection.EnsureIndexKey("author")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = collection.EnsureIndexKey("createdDate")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &chapterRepository{
|
||||
restoreIDs: restoreIDs,
|
||||
chapters: collection,
|
||||
comments: db.C("story.comments"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *chapterRepository) Find(ctx context.Context, id string) (*models.Chapter, error) {
|
||||
chapter := new(models.Chapter)
|
||||
err := r.chapters.FindId(id).One(chapter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return chapter, nil
|
||||
}
|
||||
|
||||
func (r *chapterRepository) List(ctx context.Context, filter models.ChapterFilter) ([]*models.Chapter, error) {
|
||||
query := bson.M{}
|
||||
if filter.StoryID != nil {
|
||||
query["storyId"] = *filter.StoryID
|
||||
}
|
||||
|
||||
chapters := make([]*models.Chapter, 0, 32)
|
||||
err := r.chapters.Find(query).Sort("createdDate").Limit(filter.Limit).All(&chapters)
|
||||
if err != nil {
|
||||
if err == mgo.ErrNotFound {
|
||||
return chapters, nil
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return chapters, nil
|
||||
}
|
||||
|
||||
func (r *chapterRepository) Insert(ctx context.Context, chapter models.Chapter) (*models.Chapter, error) {
|
||||
if !r.restoreIDs {
|
||||
chapter.ID = generate.ChapterID()
|
||||
} else {
|
||||
if len(chapter.ID) != len(generate.ChapterID()) && strings.HasPrefix(chapter.ID, "S") {
|
||||
return nil, errors.New("invalid story id")
|
||||
}
|
||||
}
|
||||
|
||||
err := r.chapters.Insert(chapter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &chapter, nil
|
||||
}
|
||||
|
||||
func (r *chapterRepository) Update(ctx context.Context, chapter models.Chapter, update models.ChapterUpdate) (*models.Chapter, error) {
|
||||
updateBson := bson.M{}
|
||||
if update.Title != nil {
|
||||
updateBson["title"] = *update.Title
|
||||
chapter.Title = *update.Title
|
||||
}
|
||||
if update.Source != nil {
|
||||
updateBson["source"] = *update.Source
|
||||
chapter.Source = *update.Source
|
||||
}
|
||||
if update.FictionalDate != nil {
|
||||
updateBson["fictionalDate"] = *update.FictionalDate
|
||||
chapter.FictionalDate = *update.FictionalDate
|
||||
}
|
||||
if update.CommentMode != nil {
|
||||
updateBson["commentMode"] = *update.CommentMode
|
||||
chapter.CommentMode = *update.CommentMode
|
||||
}
|
||||
if update.CommentsLocked != nil {
|
||||
updateBson["commentsLocked"] = *update.CommentsLocked
|
||||
chapter.CommentsLocked = *update.CommentsLocked
|
||||
}
|
||||
|
||||
err := r.chapters.UpdateId(chapter.ID, bson.M{"$set": updateBson})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &chapter, nil
|
||||
}
|
||||
|
||||
func (r *chapterRepository) Move(ctx context.Context, chapter models.Chapter, from, to models.Story) (*models.Chapter, error) {
|
||||
err := r.chapters.UpdateId(chapter.ID, bson.M{"$set": bson.M{"storyId": to.ID}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
chapter.StoryID = to.ID
|
||||
|
||||
return &chapter, nil
|
||||
}
|
||||
|
||||
func (r *chapterRepository) Delete(ctx context.Context, chapter models.Chapter) error {
|
||||
err := r.chapters.RemoveId(chapter.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c, err := r.comments.RemoveAll(bson.M{"chapterId": chapter.ID})
|
||||
if err != nil {
|
||||
log.Println("Failed to remove comments:", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Printf("Removed chapter %s (%d comments)", chapter.ID, c.Removed)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
package mongodb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/globalsign/mgo"
|
||||
"github.com/globalsign/mgo/bson"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"git.aiterp.net/rpdata/api/repositories"
|
||||
)
|
||||
|
||||
type characterRepository struct {
|
||||
characters *mgo.Collection
|
||||
cidCounter *counter
|
||||
restoreIDs bool
|
||||
}
|
||||
|
||||
func newCharacterRepository(db *mgo.Database, restoreIDs bool) (repositories.CharacterRepository, error) {
|
||||
collection := db.C("common.characters")
|
||||
|
||||
err := collection.EnsureIndexKey("name")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = collection.EnsureIndexKey("shortName")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = collection.EnsureIndexKey("author")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = collection.EnsureIndex(mgo.Index{
|
||||
Key: []string{"nicks"},
|
||||
Unique: true,
|
||||
DropDups: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = collection.EnsureIndex(mgo.Index{
|
||||
Key: []string{"$text:description"},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &characterRepository{
|
||||
restoreIDs: restoreIDs,
|
||||
characters: collection,
|
||||
cidCounter: newCounter(db, "auto_increment", "Character"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *characterRepository) Find(ctx context.Context, id string) (*models.Character, error) {
|
||||
character := new(models.Character)
|
||||
err := r.characters.FindId(id).One(character)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return character, nil
|
||||
}
|
||||
|
||||
func (r *characterRepository) FindNick(ctx context.Context, nick string) (*models.Character, error) {
|
||||
character := new(models.Character)
|
||||
err := r.characters.Find(bson.M{"nick": nick}).One(character)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return character, nil
|
||||
}
|
||||
|
||||
func (r *characterRepository) FindName(ctx context.Context, name string) (*models.Character, error) {
|
||||
query := bson.M{
|
||||
"$or": []bson.M{
|
||||
{"shortName": name},
|
||||
{"name": name},
|
||||
},
|
||||
}
|
||||
|
||||
// Look for all characters matching query
|
||||
characters := make([]*models.Character, 0, 8)
|
||||
err := r.characters.Find(query).All(&characters)
|
||||
if err != nil {
|
||||
if err == mgo.ErrNotFound {
|
||||
return nil, repositories.ErrNotFound
|
||||
}
|
||||
|
||||
return nil, err
|
||||
} else if len(characters) == 0 {
|
||||
return nil, repositories.ErrNotFound
|
||||
}
|
||||
|
||||
// Prioritize exact match
|
||||
for _, character := range characters {
|
||||
if character.Name == name {
|
||||
return character, nil
|
||||
}
|
||||
}
|
||||
|
||||
return characters[0], nil
|
||||
}
|
||||
|
||||
func (r *characterRepository) List(ctx context.Context, filter models.CharacterFilter) ([]*models.Character, error) {
|
||||
query := bson.M{}
|
||||
if filter.Author != nil {
|
||||
query["author"] = *filter.Author
|
||||
}
|
||||
if len(filter.IDs) > 0 {
|
||||
query["_id"] = bson.M{"$in": filter.IDs}
|
||||
}
|
||||
if len(filter.Nicks) > 0 {
|
||||
query["nicks"] = bson.M{"$in": filter.Nicks}
|
||||
}
|
||||
if len(filter.Names) > 0 {
|
||||
query["$or"] = []bson.M{
|
||||
{"name": bson.M{"$in": filter.Names}},
|
||||
{"shortName": bson.M{"$in": filter.Names}},
|
||||
}
|
||||
}
|
||||
if filter.Search != nil {
|
||||
query["$text"] = bson.M{"$search": *filter.Search}
|
||||
}
|
||||
|
||||
characters := make([]*models.Character, 0, 32)
|
||||
err := r.characters.Find(query).Limit(filter.Limit).All(&characters)
|
||||
if err != nil {
|
||||
if err == mgo.ErrNotFound {
|
||||
return characters, nil
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sort.Slice(characters, func(i, j int) bool {
|
||||
ni, _ := strconv.Atoi(characters[i].ID[1:])
|
||||
nj, _ := strconv.Atoi(characters[j].ID[1:])
|
||||
|
||||
return ni < nj
|
||||
})
|
||||
|
||||
return characters, nil
|
||||
}
|
||||
|
||||
func (r *characterRepository) Insert(ctx context.Context, character models.Character) (*models.Character, error) {
|
||||
if !r.restoreIDs {
|
||||
nextId, err := r.cidCounter.Increment(1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
character.ID = "C" + strconv.Itoa(nextId)
|
||||
} else {
|
||||
n, err := strconv.Atoi(character.ID[1:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = r.cidCounter.Bump(n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
err := r.characters.Insert(&character)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &character, nil
|
||||
}
|
||||
|
||||
func (r *characterRepository) Update(ctx context.Context, character models.Character, update models.CharacterUpdate) (*models.Character, error) {
|
||||
updateBson := bson.M{}
|
||||
if update.Name != nil {
|
||||
updateBson["name"] = *update.Name
|
||||
character.Name = *update.Name
|
||||
}
|
||||
if update.ShortName != nil {
|
||||
updateBson["shortName"] = *update.ShortName
|
||||
character.ShortName = *update.ShortName
|
||||
}
|
||||
if update.Description != nil {
|
||||
updateBson["description"] = *update.Description
|
||||
character.Description = *update.Description
|
||||
}
|
||||
|
||||
err := r.characters.UpdateId(character.ID, bson.M{"$set": updateBson})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &character, nil
|
||||
}
|
||||
|
||||
func (r *characterRepository) AddNick(ctx context.Context, character models.Character, nick string) (*models.Character, error) {
|
||||
if character.HasNick(nick) {
|
||||
return nil, errors.New("nick already exist")
|
||||
}
|
||||
|
||||
match := bson.M{
|
||||
"_id": character.ID,
|
||||
"nicks": bson.M{"$ne": nick},
|
||||
}
|
||||
|
||||
err := r.characters.Update(match, bson.M{"$push": bson.M{"nicks": nick}})
|
||||
if err == mgo.ErrNotFound {
|
||||
return nil, repositories.ErrNotFound
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newNicks := make([]string, len(character.Nicks), len(character.Nicks)+1)
|
||||
copy(newNicks, character.Nicks)
|
||||
character.Nicks = append(newNicks, nick)
|
||||
|
||||
return &character, nil
|
||||
}
|
||||
|
||||
func (r *characterRepository) RemoveNick(ctx context.Context, character models.Character, nick string) (*models.Character, error) {
|
||||
if !character.HasNick(nick) {
|
||||
return nil, errors.New("nick does not exist")
|
||||
}
|
||||
|
||||
match := bson.M{
|
||||
"_id": character.ID,
|
||||
"nicks": nick,
|
||||
}
|
||||
|
||||
err := r.characters.Update(match, bson.M{"$pull": bson.M{"nicks": nick}})
|
||||
if err == mgo.ErrNotFound {
|
||||
return nil, repositories.ErrNotFound
|
||||
} else if mErr, ok := err.(*mgo.LastError); ok && mErr.Code == 11000 {
|
||||
return nil, errors.New("The nick belongs to another character already")
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newNicks := make([]string, len(character.Nicks), len(character.Nicks)+1)
|
||||
copy(newNicks, character.Nicks)
|
||||
for i := range newNicks {
|
||||
if newNicks[i] == nick {
|
||||
newNicks = append(newNicks[:i], newNicks[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
character.Nicks = newNicks
|
||||
|
||||
return &character, nil
|
||||
}
|
||||
|
||||
func (r *characterRepository) Delete(ctx context.Context, character models.Character) error {
|
||||
return r.characters.RemoveId(character.ID)
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package mongodb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"git.aiterp.net/rpdata/api/internal/generate"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"git.aiterp.net/rpdata/api/repositories"
|
||||
"github.com/globalsign/mgo"
|
||||
"github.com/globalsign/mgo/bson"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type commentRepository struct {
|
||||
restoreIDs bool
|
||||
comments *mgo.Collection
|
||||
}
|
||||
|
||||
func newCommentRepository(db *mgo.Database, restoreIDs bool) (repositories.CommentRepository, error) {
|
||||
collection := db.C("story.comments")
|
||||
|
||||
err := collection.EnsureIndexKey("chapterId")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = collection.EnsureIndexKey("author")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = collection.EnsureIndexKey("createdDate")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r := &commentRepository{
|
||||
restoreIDs: restoreIDs,
|
||||
comments: collection,
|
||||
}
|
||||
|
||||
go r.fixFieldTypo()
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (r *commentRepository) Find(ctx context.Context, id string) (*models.Comment, error) {
|
||||
comment := new(models.Comment)
|
||||
err := r.comments.FindId(id).One(comment)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return comment, nil
|
||||
}
|
||||
|
||||
func (r *commentRepository) List(ctx context.Context, filter models.CommentFilter) ([]*models.Comment, error) {
|
||||
query := bson.M{}
|
||||
if filter.ChapterID != nil {
|
||||
query["chapterId"] = *filter.ChapterID
|
||||
}
|
||||
|
||||
comments := make([]*models.Comment, 0, 32)
|
||||
err := r.comments.Find(query).Sort("createdDate").Limit(filter.Limit).All(&comments)
|
||||
if err != nil {
|
||||
if err == mgo.ErrNotFound {
|
||||
return comments, nil
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return comments, nil
|
||||
}
|
||||
|
||||
func (r *commentRepository) Insert(ctx context.Context, comment models.Comment) (*models.Comment, error) {
|
||||
if !r.restoreIDs {
|
||||
comment.ID = generate.CommentID()
|
||||
} else {
|
||||
if len(comment.ID) != len(generate.CommentID()) && strings.HasPrefix(comment.ID, "SSC") {
|
||||
return nil, errors.New("invalid story id")
|
||||
}
|
||||
}
|
||||
|
||||
err := r.comments.Insert(comment)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &comment, nil
|
||||
}
|
||||
|
||||
func (r *commentRepository) Update(ctx context.Context, comment models.Comment, update models.CommentUpdate) (*models.Comment, error) {
|
||||
updateBson := bson.M{}
|
||||
if update.Subject != nil {
|
||||
updateBson["subject"] = *update.Subject
|
||||
comment.Subject = *update.Subject
|
||||
}
|
||||
if update.Source != nil {
|
||||
updateBson["source"] = *update.Source
|
||||
comment.Source = *update.Source
|
||||
}
|
||||
if update.FictionalDate != nil {
|
||||
updateBson["fictionalDate"] = *update.FictionalDate
|
||||
comment.FictionalDate = *update.FictionalDate
|
||||
}
|
||||
if update.CharacterID != nil {
|
||||
updateBson["characterId"] = *update.CharacterID
|
||||
comment.CharacterID = *update.CharacterID
|
||||
}
|
||||
if update.CharacterName != nil {
|
||||
updateBson["characterName"] = *update.CharacterName
|
||||
comment.CharacterName = *update.CharacterName
|
||||
}
|
||||
|
||||
err := r.comments.UpdateId(comment.ID, bson.M{"$set": updateBson})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &comment, nil
|
||||
}
|
||||
|
||||
func (r *commentRepository) Delete(ctx context.Context, comment models.Comment) error {
|
||||
return r.comments.RemoveId(comment.ID)
|
||||
}
|
||||
|
||||
func (r *commentRepository) fixFieldTypo() {
|
||||
c, err := r.comments.UpdateAll(bson.M{
|
||||
"editeddDate": bson.M{"$ne": nil},
|
||||
}, bson.M{
|
||||
"$rename": bson.M{"editeddDate": "editedDate"},
|
||||
})
|
||||
if err != nil {
|
||||
if err == mgo.ErrNotFound {
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("Failed to run name typo fix:", err)
|
||||
return
|
||||
}
|
||||
if c.Updated > 0 {
|
||||
log.Println("Fixed editeddDate field name typo in", c.Updated, "comments")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
package mongodb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/globalsign/mgo/bson"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/internal/config"
|
||||
"git.aiterp.net/rpdata/api/repositories"
|
||||
"github.com/globalsign/mgo"
|
||||
)
|
||||
|
||||
var DisableFixes bool
|
||||
|
||||
type MongoDB struct {
|
||||
session *mgo.Session
|
||||
|
||||
changes repositories.ChangeRepository
|
||||
channels *channelRepository
|
||||
characters repositories.CharacterRepository
|
||||
tags repositories.TagRepository
|
||||
logs *logRepository
|
||||
posts *postRepository
|
||||
files *fileRepository
|
||||
stories repositories.StoryRepository
|
||||
chapters repositories.ChapterRepository
|
||||
comments repositories.CommentRepository
|
||||
keys repositories.KeyRepository
|
||||
users repositories.UserRepository
|
||||
}
|
||||
|
||||
func (m *MongoDB) Changes() repositories.ChangeRepository {
|
||||
return m.changes
|
||||
}
|
||||
|
||||
func (m *MongoDB) Characters() repositories.CharacterRepository {
|
||||
return m.characters
|
||||
}
|
||||
|
||||
func (m *MongoDB) Tags() repositories.TagRepository {
|
||||
return m.tags
|
||||
}
|
||||
|
||||
func (m *MongoDB) Logs() repositories.LogRepository {
|
||||
return m.logs
|
||||
}
|
||||
|
||||
func (m *MongoDB) Posts() repositories.PostRepository {
|
||||
return m.posts
|
||||
}
|
||||
|
||||
func (m *MongoDB) Channels() repositories.ChannelRepository {
|
||||
return m.channels
|
||||
}
|
||||
|
||||
func (m *MongoDB) Files() repositories.FileRepository {
|
||||
return m.files
|
||||
}
|
||||
|
||||
func (m *MongoDB) Stories() repositories.StoryRepository {
|
||||
return m.stories
|
||||
}
|
||||
|
||||
func (m *MongoDB) Chapters() repositories.ChapterRepository {
|
||||
return m.chapters
|
||||
}
|
||||
|
||||
func (m *MongoDB) Comments() repositories.CommentRepository {
|
||||
return m.comments
|
||||
}
|
||||
|
||||
func (m *MongoDB) Keys() repositories.KeyRepository {
|
||||
return m.keys
|
||||
}
|
||||
|
||||
func (m *MongoDB) Users() repositories.UserRepository {
|
||||
return m.users
|
||||
}
|
||||
|
||||
func (m *MongoDB) Close(ctx context.Context) error {
|
||||
m.session.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Init initializes the mongodb database
|
||||
func Init(cfg config.Database) (*MongoDB, error) {
|
||||
port := cfg.Port
|
||||
if port <= 0 {
|
||||
port = 27017
|
||||
}
|
||||
|
||||
session, err := mgo.DialWithInfo(&mgo.DialInfo{
|
||||
Addrs: []string{fmt.Sprintf("%s:%d", cfg.Host, port)},
|
||||
Timeout: 30 * time.Second,
|
||||
Database: cfg.Db,
|
||||
Username: cfg.Username,
|
||||
Password: cfg.Password,
|
||||
Mechanism: cfg.Mechanism,
|
||||
Source: cfg.Db,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
db := session.DB(cfg.Db)
|
||||
|
||||
characters, err := newCharacterRepository(db, cfg.RestoreIDs)
|
||||
if err != nil {
|
||||
session.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
channels, err := newChannelRepository(db)
|
||||
if err != nil {
|
||||
session.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
changes, err := newChangeRepository(db, cfg.RestoreIDs)
|
||||
if err != nil {
|
||||
session.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logs, err := newLogRepository(db, cfg.RestoreIDs)
|
||||
if err != nil {
|
||||
session.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
posts, err := newPostRepository(db, cfg.RestoreIDs)
|
||||
if err != nil {
|
||||
session.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
files, err := newFileRepository(db)
|
||||
if err != nil {
|
||||
session.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stories, err := newStoryRepository(db, cfg.RestoreIDs)
|
||||
if err != nil {
|
||||
session.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
chapters, err := newChapterRepository(db, cfg.RestoreIDs)
|
||||
if err != nil {
|
||||
session.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
comments, err := newCommentRepository(db, cfg.RestoreIDs)
|
||||
if err != nil {
|
||||
session.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keys, err := newKeyRepository(db, cfg.RestoreIDs)
|
||||
if err != nil {
|
||||
session.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
users, err := newUserRepository(db)
|
||||
if err != nil {
|
||||
session.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !DisableFixes {
|
||||
go posts.fixPositions(logs)
|
||||
}
|
||||
|
||||
return &MongoDB{
|
||||
session: session,
|
||||
|
||||
changes: changes,
|
||||
characters: characters,
|
||||
channels: channels,
|
||||
tags: newTagRepository(db),
|
||||
stories: stories,
|
||||
chapters: chapters,
|
||||
comments: comments,
|
||||
logs: logs,
|
||||
posts: posts,
|
||||
files: files,
|
||||
keys: keys,
|
||||
users: users,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type counter struct {
|
||||
coll *mgo.Collection
|
||||
category string
|
||||
name string
|
||||
}
|
||||
|
||||
func newCounter(db *mgo.Database, category, name string) *counter {
|
||||
return &counter{
|
||||
coll: db.C("core.counters"),
|
||||
category: category,
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *counter) WithName(name string) *counter {
|
||||
return &counter{
|
||||
coll: c.coll,
|
||||
category: c.category,
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *counter) WithCategory(category string) *counter {
|
||||
return &counter{
|
||||
coll: c.coll,
|
||||
category: category,
|
||||
name: c.name,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *counter) With(category, name string) *counter {
|
||||
return &counter{
|
||||
coll: c.coll,
|
||||
category: category,
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *counter) Bump(amount int) error {
|
||||
id := c.category + "." + c.name
|
||||
|
||||
err := c.coll.Update(bson.M{
|
||||
"_id": id,
|
||||
"value": bson.M{"$lt": amount},
|
||||
}, bson.M{
|
||||
"value": amount,
|
||||
})
|
||||
if err != nil {
|
||||
if err == mgo.ErrNotFound {
|
||||
return c.coll.Insert(bson.M{
|
||||
"_id": id,
|
||||
"value": amount,
|
||||
})
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *counter) Increment(amount int) (int, error) {
|
||||
type counterDoc struct {
|
||||
ID string `bson:"_id"`
|
||||
Value int `bson:"value"`
|
||||
}
|
||||
|
||||
id := c.category + "." + c.name
|
||||
doc := counterDoc{}
|
||||
|
||||
_, err := c.coll.Find(bson.M{"_id": id}).Apply(mgo.Change{
|
||||
Update: bson.M{"$inc": bson.M{"value": amount}},
|
||||
Upsert: true,
|
||||
ReturnNew: true,
|
||||
}, &doc)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
return doc.Value, nil
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package mongodb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.aiterp.net/rpdata/api/internal/generate"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"git.aiterp.net/rpdata/api/repositories"
|
||||
"github.com/globalsign/mgo"
|
||||
"github.com/globalsign/mgo/bson"
|
||||
)
|
||||
|
||||
type fileRepository struct {
|
||||
files *mgo.Collection
|
||||
}
|
||||
|
||||
func (r *fileRepository) Find(ctx context.Context, id string) (*models.File, error) {
|
||||
file := new(models.File)
|
||||
err := r.files.FindId(id).One(file)
|
||||
if err != nil {
|
||||
if err == mgo.ErrNotFound {
|
||||
return nil, repositories.ErrNotFound
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return file, nil
|
||||
}
|
||||
|
||||
func (r *fileRepository) List(ctx context.Context, filter models.FileFilter) ([]*models.File, error) {
|
||||
query := bson.M{}
|
||||
if filter.Author != nil {
|
||||
query["author"] = *filter.Author
|
||||
}
|
||||
if filter.MimeTypes != nil {
|
||||
query["mimeType"] = bson.M{"$in": filter.MimeTypes}
|
||||
}
|
||||
if filter.Public != nil {
|
||||
query["public"] = *filter.Public
|
||||
}
|
||||
|
||||
files := make([]*models.File, 0, 16)
|
||||
err := r.files.Find(query).Sort("-date").All(&files)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (r *fileRepository) Insert(ctx context.Context, file models.File) (*models.File, error) {
|
||||
file.ID = generate.FileID()
|
||||
|
||||
err := r.files.Insert(&file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &file, nil
|
||||
}
|
||||
|
||||
func (r *fileRepository) Update(ctx context.Context, file models.File, update models.FileUpdate) (*models.File, error) {
|
||||
updateBson := bson.M{}
|
||||
if update.Public != nil {
|
||||
updateBson["public"] = *update.Public
|
||||
file.Public = *update.Public
|
||||
}
|
||||
if update.Name != nil {
|
||||
updateBson["name"] = *update.Name
|
||||
file.Name = *update.Name
|
||||
}
|
||||
|
||||
err := r.files.UpdateId(file.ID, bson.M{"$set": updateBson})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &file, nil
|
||||
}
|
||||
|
||||
func (r *fileRepository) Delete(ctx context.Context, file models.File) error {
|
||||
return r.files.RemoveId(file.ID)
|
||||
}
|
||||
|
||||
func newFileRepository(db *mgo.Database) (*fileRepository, error) {
|
||||
files := db.C("file.headers")
|
||||
|
||||
err := files.EnsureIndexKey("author")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = files.EnsureIndexKey("mimeType")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = files.EnsureIndexKey("public")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &fileRepository{files: files}, nil
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package mongodb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.aiterp.net/rpdata/api/internal/generate"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"git.aiterp.net/rpdata/api/repositories"
|
||||
"github.com/globalsign/mgo"
|
||||
"github.com/globalsign/mgo/bson"
|
||||
)
|
||||
|
||||
type keyRepository struct {
|
||||
restoreIDs bool
|
||||
keys *mgo.Collection
|
||||
}
|
||||
|
||||
func newKeyRepository(db *mgo.Database, restoreIDs bool) (repositories.KeyRepository, error) {
|
||||
collection := db.C("auth.keys")
|
||||
|
||||
err := collection.EnsureIndexKey("user")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &keyRepository{
|
||||
keys: collection,
|
||||
restoreIDs: restoreIDs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *keyRepository) Find(ctx context.Context, id string) (*models.Key, error) {
|
||||
key := models.Key{}
|
||||
err := r.keys.FindId(id).One(&key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &key, err
|
||||
}
|
||||
|
||||
func (r *keyRepository) List(ctx context.Context, filter models.KeyFilter) ([]*models.Key, error) {
|
||||
query := bson.M{}
|
||||
if filter.UserID != nil {
|
||||
query["user"] = *filter.UserID
|
||||
}
|
||||
|
||||
keys := make([]*models.Key, 0, 4)
|
||||
err := r.keys.Find(query).All(&keys)
|
||||
if err != nil {
|
||||
if err == mgo.ErrNotFound {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
func (r *keyRepository) Insert(ctx context.Context, key models.Key) (*models.Key, error) {
|
||||
if !r.restoreIDs {
|
||||
key.ID = generate.KeyID()
|
||||
}
|
||||
|
||||
err := r.keys.Insert(&key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &key, nil
|
||||
}
|
||||
|
||||
func (r *keyRepository) Delete(ctx context.Context, key models.Key) error {
|
||||
return r.keys.RemoveId(key.ID)
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
package mongodb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"git.aiterp.net/rpdata/api/internal/generate"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"github.com/globalsign/mgo"
|
||||
"github.com/globalsign/mgo/bson"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type logRepository struct {
|
||||
openMutex sync.Mutex
|
||||
restoreIds bool
|
||||
|
||||
logs *mgo.Collection
|
||||
posts *mgo.Collection
|
||||
shortIdCounter *counter
|
||||
}
|
||||
|
||||
func newLogRepository(db *mgo.Database, restoreIds bool) (*logRepository, error) {
|
||||
logs := db.C("logbot3.logs")
|
||||
posts := db.C("logbot3.posts")
|
||||
|
||||
err := logs.EnsureIndexKey("date")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = logs.EnsureIndexKey("channel")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = logs.EnsureIndexKey("characterIds")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = logs.EnsureIndexKey("event")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = logs.EnsureIndex(mgo.Index{Key: []string{"channel", "open"}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = logs.EnsureIndex(mgo.Index{
|
||||
Key: []string{"shortId"},
|
||||
Unique: true,
|
||||
DropDups: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &logRepository{
|
||||
restoreIds: restoreIds,
|
||||
logs: logs,
|
||||
posts: posts,
|
||||
shortIdCounter: newCounter(db, "auto_increment", "Log"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *logRepository) Find(ctx context.Context, id string) (*models.Log, error) {
|
||||
log := new(models.Log)
|
||||
err := r.logs.Find(bson.M{"$or": []bson.M{{"_id": id}, {"shortId": id}}}).One(log)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return log, nil
|
||||
}
|
||||
|
||||
func (r *logRepository) List(ctx context.Context, filter models.LogFilter) ([]*models.Log, error) {
|
||||
query := bson.M{}
|
||||
if filter.Search != nil {
|
||||
searchQuery := bson.M{
|
||||
"$text": bson.M{"$search": *filter.Search},
|
||||
"logId": bson.M{"$ne": nil},
|
||||
}
|
||||
|
||||
logIds := make([]string, 0, 64)
|
||||
err := r.posts.Find(searchQuery).Distinct("logId", &logIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query["shortId"] = bson.M{"$in": logIds}
|
||||
}
|
||||
if filter.Open != nil {
|
||||
r.openMutex.Lock()
|
||||
defer r.openMutex.Unlock()
|
||||
|
||||
query["open"] = filter.Open
|
||||
}
|
||||
if len(filter.Characters) > 0 {
|
||||
query["characterIds"] = bson.M{"$all": filter.Characters}
|
||||
}
|
||||
if len(filter.Channels) > 0 {
|
||||
query["channel"] = bson.M{"$in": filter.Channels}
|
||||
}
|
||||
if len(filter.Events) > 0 {
|
||||
query["event"] = bson.M{"$in": filter.Events}
|
||||
}
|
||||
if filter.MinDate != nil && filter.MaxDate != nil {
|
||||
query["date"] = bson.M{"$gte": *filter.MinDate, "$lte": *filter.MaxDate}
|
||||
} else if filter.MinDate != nil {
|
||||
query["date"] = bson.M{"$gte": *filter.MinDate}
|
||||
} else if filter.MaxDate != nil {
|
||||
query["date"] = bson.M{"$lt": *filter.MaxDate}
|
||||
}
|
||||
|
||||
logs := make([]*models.Log, 0, 32)
|
||||
err := r.logs.Find(query).Sort("-date").Limit(filter.Limit).All(&logs)
|
||||
if err != nil {
|
||||
if err == mgo.ErrNotFound {
|
||||
return logs, nil
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return logs, nil
|
||||
}
|
||||
|
||||
func (r *logRepository) Insert(ctx context.Context, log models.Log) (*models.Log, error) {
|
||||
if !r.restoreIds || log.ID == "" || log.ShortID == "" {
|
||||
nextShortId, err := r.shortIdCounter.Increment(1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.ID = generate.LogID(log)
|
||||
log.ShortID = "L" + strconv.Itoa(nextShortId)
|
||||
} else {
|
||||
n, err := strconv.Atoi(log.ShortID[1:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_ = r.shortIdCounter.Bump(n)
|
||||
}
|
||||
|
||||
if log.Open {
|
||||
// There can be only one open log in the same channel.
|
||||
r.openMutex.Lock()
|
||||
defer r.openMutex.Unlock()
|
||||
|
||||
_, err := r.logs.UpdateAll(bson.M{"channel": log.ChannelName, "open": true}, bson.M{"$set": bson.M{"open": false}})
|
||||
if err != nil {
|
||||
return nil, errors.New("Cannot close other logs: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
err := r.logs.Insert(&log)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &log, nil
|
||||
}
|
||||
|
||||
func (r *logRepository) Update(ctx context.Context, log models.Log, update models.LogUpdate) (*models.Log, error) {
|
||||
updateBson := bson.M{}
|
||||
if update.Open != nil {
|
||||
if *update.Open == true {
|
||||
// There can be only one open log in the same channel.
|
||||
r.openMutex.Lock()
|
||||
defer r.openMutex.Unlock()
|
||||
|
||||
_, err := r.logs.UpdateAll(bson.M{"channel": log.ChannelName, "open": true}, bson.M{"$set": bson.M{"open": false}})
|
||||
if err != nil {
|
||||
return nil, errors.New("Cannot close other logs: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
updateBson["open"] = *update.Open
|
||||
log.Open = *update.Open
|
||||
}
|
||||
if update.Title != nil {
|
||||
updateBson["title"] = *update.Title
|
||||
log.Title = *update.Title
|
||||
}
|
||||
if update.Description != nil {
|
||||
updateBson["description"] = *update.Description
|
||||
log.Description = *update.Description
|
||||
}
|
||||
if update.EventName != nil {
|
||||
updateBson["event"] = *update.EventName
|
||||
log.EventName = *update.EventName
|
||||
}
|
||||
if update.CharacterIDs != nil {
|
||||
updateBson["characterIds"] = update.CharacterIDs
|
||||
log.CharacterIDs = update.CharacterIDs
|
||||
}
|
||||
|
||||
err := r.logs.UpdateId(log.ID, bson.M{"$set": updateBson})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &log, nil
|
||||
}
|
||||
|
||||
func (r *logRepository) Delete(ctx context.Context, log models.Log) error {
|
||||
err := r.logs.RemoveId(log.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _ = r.posts.RemoveAll(bson.M{"logId": log.ShortID})
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
package mongodb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"git.aiterp.net/rpdata/api/internal/generate"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"git.aiterp.net/rpdata/api/repositories"
|
||||
"github.com/globalsign/mgo"
|
||||
"github.com/globalsign/mgo/bson"
|
||||
"log"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type postRepository struct {
|
||||
restoreIDs bool
|
||||
logs *mgo.Collection
|
||||
posts *mgo.Collection
|
||||
|
||||
orderMutex sync.Mutex
|
||||
}
|
||||
|
||||
func newPostRepository(db *mgo.Database, restoreIDs bool) (*postRepository, error) {
|
||||
posts := db.C("logbot3.posts")
|
||||
|
||||
err := posts.EnsureIndexKey("logId")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = posts.EnsureIndexKey("time")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = posts.EnsureIndexKey("kind")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = posts.EnsureIndexKey("position")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = posts.EnsureIndex(mgo.Index{
|
||||
Key: []string{"$text:text"},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &postRepository{
|
||||
restoreIDs: restoreIDs,
|
||||
posts: posts,
|
||||
logs: db.C("logbot3.logs"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *postRepository) Find(ctx context.Context, id string) (*models.Post, error) {
|
||||
post := new(models.Post)
|
||||
err := r.posts.FindId(id).One(post)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return post, nil
|
||||
}
|
||||
|
||||
func (r *postRepository) List(ctx context.Context, filter models.PostFilter) ([]*models.Post, error) {
|
||||
query := bson.M{}
|
||||
if filter.LogID != nil && *filter.LogID != "" {
|
||||
logId := *filter.LogID
|
||||
if !strings.HasPrefix(logId, "L") {
|
||||
// Resolve long id to short id
|
||||
log := new(models.Log)
|
||||
err := r.logs.FindId(logId).Select(bson.M{"logId": 1, "_id": 1}).One(log)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logId = log.ShortID
|
||||
}
|
||||
|
||||
query["logId"] = logId
|
||||
}
|
||||
if len(filter.IDs) > 0 {
|
||||
query["_id"] = bson.M{"$in": filter.IDs}
|
||||
}
|
||||
if len(filter.Kinds) > 0 {
|
||||
query["kind"] = bson.M{"$in": filter.Kinds}
|
||||
}
|
||||
if filter.Search != nil {
|
||||
query["$text"] = bson.M{"$search": *filter.Search}
|
||||
}
|
||||
|
||||
posts := make([]*models.Post, 0, 32)
|
||||
var err error
|
||||
if filter.LogID != nil {
|
||||
err = r.posts.Find(query).Sort("position").Limit(filter.Limit).All(&posts)
|
||||
} else {
|
||||
err = r.posts.Find(query).Limit(filter.Limit).All(&posts)
|
||||
}
|
||||
if err != nil {
|
||||
if err == mgo.ErrNotFound {
|
||||
return []*models.Post{}, nil
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return posts, nil
|
||||
}
|
||||
|
||||
func (r *postRepository) Insert(ctx context.Context, post models.Post) (*models.Post, error) {
|
||||
r.orderMutex.Lock()
|
||||
defer r.orderMutex.Unlock()
|
||||
|
||||
lastPost := new(models.Post)
|
||||
err := r.posts.Find(bson.M{"logId": post.LogID}).Sort("-position").One(lastPost)
|
||||
if err != nil && err != mgo.ErrNotFound {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !r.restoreIDs {
|
||||
post.ID = generate.PostID()
|
||||
} else {
|
||||
if len(post.ID) != len(generate.PostID()) && strings.HasPrefix(post.ID, "P") {
|
||||
return nil, errors.New("invalid story id")
|
||||
}
|
||||
}
|
||||
|
||||
post.Position = lastPost.Position + 1 // Position 1 is first position, so this is safe.
|
||||
|
||||
err = r.posts.Insert(post)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &post, nil
|
||||
}
|
||||
|
||||
func (r *postRepository) InsertMany(ctx context.Context, posts ...*models.Post) ([]*models.Post, error) {
|
||||
if len(posts) == 0 {
|
||||
return []*models.Post{}, nil
|
||||
}
|
||||
|
||||
logId := posts[0].LogID
|
||||
for _, post := range posts {
|
||||
if post.LogID != logId {
|
||||
return nil, repositories.ErrParentMismatch
|
||||
}
|
||||
|
||||
if !r.restoreIDs || post.ID == "" {
|
||||
post.ID = generate.PostID()
|
||||
}
|
||||
}
|
||||
|
||||
r.orderMutex.Lock()
|
||||
defer r.orderMutex.Unlock()
|
||||
|
||||
lastPost := new(models.Post)
|
||||
err := r.posts.Find(bson.M{"logId": posts[0].LogID}).Sort("-position").One(lastPost)
|
||||
if err != nil && err != mgo.ErrNotFound {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
docs := make([]interface{}, len(posts))
|
||||
for i := range posts {
|
||||
posts[i].Position = lastPost.Position + 1 + i
|
||||
docs[i] = posts[i]
|
||||
}
|
||||
|
||||
err = r.posts.Insert(docs...)
|
||||
if err != nil && err != mgo.ErrNotFound {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return posts, nil
|
||||
}
|
||||
|
||||
func (r *postRepository) Update(ctx context.Context, post models.Post, update models.PostUpdate) (*models.Post, error) {
|
||||
updateBson := bson.M{}
|
||||
if update.Time != nil {
|
||||
updateBson["time"] = *update.Time
|
||||
post.Time = *update.Time
|
||||
}
|
||||
if update.Kind != nil {
|
||||
updateBson["kind"] = *update.Kind
|
||||
post.Kind = *update.Kind
|
||||
}
|
||||
if update.Nick != nil {
|
||||
updateBson["nick"] = *update.Nick
|
||||
post.Nick = *update.Nick
|
||||
}
|
||||
if update.Text != nil {
|
||||
updateBson["text"] = *update.Text
|
||||
post.Text = *update.Text
|
||||
}
|
||||
|
||||
err := r.posts.UpdateId(post.ID, bson.M{"$set": updateBson})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &post, nil
|
||||
}
|
||||
|
||||
func (r *postRepository) Move(ctx context.Context, post models.Post, position int) ([]*models.Post, error) {
|
||||
// If only MongoDB transactions weren't awful, this function would have been safe.
|
||||
// Since it isn't, then good luck.
|
||||
|
||||
// Validate lower position bound.
|
||||
if position < 1 {
|
||||
return nil, repositories.ErrInvalidPosition
|
||||
}
|
||||
|
||||
// Determine the operations on adjacent posts
|
||||
var resultFilter bson.M
|
||||
var pushFilter bson.M
|
||||
var increment int
|
||||
if post.Position > position {
|
||||
pushFilter = bson.M{"position": bson.M{
|
||||
"$gte": position,
|
||||
"$lt": post.Position,
|
||||
}}
|
||||
increment = 1
|
||||
resultFilter = bson.M{"position": bson.M{
|
||||
"$lte": post.Position,
|
||||
"$gte": position,
|
||||
}}
|
||||
} else {
|
||||
pushFilter = bson.M{"position": bson.M{
|
||||
"$lte": position,
|
||||
"$gt": post.Position,
|
||||
}}
|
||||
increment = -1
|
||||
resultFilter = bson.M{"position": bson.M{
|
||||
"$lte": position,
|
||||
"$gte": post.Position,
|
||||
}}
|
||||
}
|
||||
pushFilter["logId"] = post.LogID
|
||||
resultFilter["logId"] = post.LogID
|
||||
|
||||
// From here on out, sync is required
|
||||
r.orderMutex.Lock()
|
||||
defer r.orderMutex.Unlock()
|
||||
|
||||
// Detect ninja shenanigans
|
||||
post2 := new(models.Post)
|
||||
err := r.posts.FindId(post.ID).One(post2)
|
||||
if err != nil && post2.Position != post.Position {
|
||||
return nil, repositories.ErrNotFound
|
||||
}
|
||||
|
||||
// Validate upper position bound
|
||||
lastPost := new(models.Post)
|
||||
err = r.posts.Find(bson.M{"logId": post.LogID}).Sort("-position").One(lastPost)
|
||||
if err != nil && err != mgo.ErrNotFound {
|
||||
return nil, err
|
||||
}
|
||||
if position > lastPost.Position {
|
||||
return nil, repositories.ErrInvalidPosition
|
||||
}
|
||||
|
||||
// Move the posts
|
||||
changeInfo, err := r.posts.UpdateAll(pushFilter, bson.M{"$inc": bson.M{"position": increment}})
|
||||
if err != nil && err != mgo.ErrNotFound {
|
||||
return nil, err
|
||||
}
|
||||
err = r.posts.UpdateId(post.ID, bson.M{"$set": bson.M{"position": position}})
|
||||
if err != nil {
|
||||
// Try to undo it
|
||||
_, err := r.posts.UpdateAll(pushFilter, bson.M{"$inc": bson.M{"position": -increment}})
|
||||
if err != nil && err != mgo.ErrNotFound {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
results := make([]*models.Post, 0, changeInfo.Matched+1)
|
||||
err = r.posts.Find(resultFilter).All(&results)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (r *postRepository) Delete(ctx context.Context, post models.Post) error {
|
||||
r.orderMutex.Lock()
|
||||
defer r.orderMutex.Unlock()
|
||||
|
||||
err := r.posts.RemoveId(post.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _ = r.posts.UpdateAll(bson.M{"logId": post.LogID, "position": bson.M{"$gt": post.Position}}, bson.M{"$inc": bson.M{"position": -1}})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *postRepository) fixPositions(logRepo *logRepository) {
|
||||
disorders := make([]int, 0, 16)
|
||||
diffs := make([]int, 0, 16)
|
||||
startTime := time.Now()
|
||||
|
||||
timeout, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||
defer cancel()
|
||||
|
||||
logs, err := logRepo.List(timeout, models.LogFilter{})
|
||||
if err != nil {
|
||||
log.Println("Failed to get logs for position fix:", err)
|
||||
}
|
||||
|
||||
log.Println("Starting log position fixing, this should not take longer than 10 seconds.")
|
||||
|
||||
for _, l := range logs {
|
||||
r.orderMutex.Lock()
|
||||
|
||||
posts, err := r.List(timeout, models.PostFilter{LogID: &l.ShortID})
|
||||
if err != nil {
|
||||
r.orderMutex.Unlock()
|
||||
continue
|
||||
}
|
||||
|
||||
disorders = disorders[:0]
|
||||
diffs = diffs[:0]
|
||||
for i, post := range posts {
|
||||
if post.Position != (i + 1) {
|
||||
disorders = append(disorders, i)
|
||||
diffs = append(diffs, post.Position-(i+1))
|
||||
}
|
||||
}
|
||||
|
||||
if len(disorders) > 0 {
|
||||
log.Println(len(disorders), "order errors detected in", l.ID)
|
||||
|
||||
ops := 0
|
||||
|
||||
for i, post := range posts {
|
||||
if (i + 1) != posts[i].Position {
|
||||
ops++
|
||||
|
||||
err := r.posts.UpdateId(post.ID, bson.M{"$set": bson.M{"position": i + 1}})
|
||||
if err != nil {
|
||||
log.Println(l.ShortID, "fix failed after", ops, "ops:", err)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Println(l.ShortID, "fixed after", ops, "ops.")
|
||||
}
|
||||
|
||||
r.orderMutex.Unlock()
|
||||
}
|
||||
|
||||
log.Println("Log position fixing finished in", time.Since(startTime))
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
package mongodb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"git.aiterp.net/rpdata/api/internal/generate"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"git.aiterp.net/rpdata/api/repositories"
|
||||
"github.com/globalsign/mgo"
|
||||
"github.com/globalsign/mgo/bson"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type storyRepository struct {
|
||||
stories *mgo.Collection
|
||||
chapters *mgo.Collection
|
||||
comments *mgo.Collection
|
||||
restoreIDs bool
|
||||
}
|
||||
|
||||
func newStoryRepository(db *mgo.Database, restoreIDs bool) (repositories.StoryRepository, error) {
|
||||
collection := db.C("story.stories")
|
||||
|
||||
err := collection.EnsureIndexKey("tags")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = collection.EnsureIndexKey("author")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = collection.EnsureIndexKey("updatedDate")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = collection.EnsureIndexKey("fictionalDate")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = collection.EnsureIndexKey("listed")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &storyRepository{
|
||||
stories: collection,
|
||||
restoreIDs: restoreIDs,
|
||||
chapters: db.C("story.chapters"),
|
||||
comments: db.C("story.comments"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *storyRepository) Find(ctx context.Context, id string) (*models.Story, error) {
|
||||
story := new(models.Story)
|
||||
err := r.stories.FindId(id).One(story)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return story, nil
|
||||
}
|
||||
|
||||
func (r *storyRepository) List(ctx context.Context, filter models.StoryFilter) ([]*models.Story, error) {
|
||||
query := bson.M{}
|
||||
if filter.Author != nil {
|
||||
query["author"] = *filter.Author
|
||||
}
|
||||
if filter.Category != nil {
|
||||
query["category"] = *filter.Category
|
||||
}
|
||||
if filter.Open != nil {
|
||||
query["open"] = *filter.Open
|
||||
}
|
||||
if len(filter.Tags) > 0 {
|
||||
query["tags"] = bson.M{"$all": filter.Tags}
|
||||
}
|
||||
if filter.Unlisted != nil {
|
||||
query["listed"] = !*filter.Unlisted
|
||||
} else {
|
||||
query["listed"] = true
|
||||
}
|
||||
if !filter.EarliestFictionalDate.IsZero() && !filter.LatestFictionalDate.IsZero() {
|
||||
query["fictionalDate"] = bson.M{
|
||||
"$gte": filter.EarliestFictionalDate,
|
||||
"$lte": filter.LatestFictionalDate,
|
||||
}
|
||||
} else if !filter.EarliestFictionalDate.IsZero() {
|
||||
query["fictionalDate"] = bson.M{
|
||||
"$gte": filter.EarliestFictionalDate,
|
||||
}
|
||||
} else if !filter.LatestFictionalDate.IsZero() {
|
||||
query["fictionalDate"] = bson.M{
|
||||
"$lte": filter.LatestFictionalDate,
|
||||
}
|
||||
}
|
||||
|
||||
stories := make([]*models.Story, 0, 32)
|
||||
err := r.stories.Find(query).Sort("-updatedDate").Limit(filter.Limit).All(&stories)
|
||||
if err != nil {
|
||||
if err == mgo.ErrNotFound {
|
||||
return stories, nil
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return stories, nil
|
||||
}
|
||||
|
||||
func (r *storyRepository) Insert(ctx context.Context, story models.Story) (*models.Story, error) {
|
||||
if !r.restoreIDs {
|
||||
story.ID = generate.StoryID()
|
||||
} else {
|
||||
if len(story.ID) != len(generate.StoryID()) && strings.HasPrefix(story.ID, "S") {
|
||||
return nil, errors.New("invalid story id")
|
||||
}
|
||||
}
|
||||
|
||||
err := r.stories.Insert(story)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &story, nil
|
||||
}
|
||||
|
||||
func (r *storyRepository) Update(ctx context.Context, story models.Story, update models.StoryUpdate) (*models.Story, error) {
|
||||
updateBson := bson.M{}
|
||||
if update.Name != nil {
|
||||
updateBson["name"] = *update.Name
|
||||
story.Name = *update.Name
|
||||
}
|
||||
if update.Open != nil {
|
||||
updateBson["open"] = *update.Open
|
||||
story.Open = *update.Open
|
||||
}
|
||||
if update.Category != nil {
|
||||
updateBson["category"] = *update.Category
|
||||
story.Category = *update.Category
|
||||
}
|
||||
if update.Author != nil {
|
||||
updateBson["author"] = *update.Author
|
||||
story.Author = *update.Author
|
||||
}
|
||||
if update.FictionalDate != nil {
|
||||
updateBson["fictionalDate"] = *update.FictionalDate
|
||||
story.FictionalDate = *update.FictionalDate
|
||||
}
|
||||
if update.UpdatedDate != nil {
|
||||
updateBson["updatedDate"] = *update.UpdatedDate
|
||||
story.UpdatedDate = *update.UpdatedDate
|
||||
}
|
||||
if update.Listed != nil {
|
||||
updateBson["listed"] = *update.Listed
|
||||
story.Listed = *update.Listed
|
||||
}
|
||||
if update.SortByFictionalDate != nil {
|
||||
updateBson["sortByFictionalDate"] = *update.SortByFictionalDate
|
||||
story.SortByFictionalDate = *update.SortByFictionalDate
|
||||
}
|
||||
|
||||
err := r.stories.UpdateId(story.ID, bson.M{"$set": updateBson})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &story, nil
|
||||
}
|
||||
|
||||
func (r *storyRepository) AddTag(ctx context.Context, story models.Story, tag models.Tag) error {
|
||||
ci, err := r.stories.UpdateAll(bson.M{"_id": story.ID}, bson.M{"$addToSet": bson.M{"tags": tag}})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ci.Updated == 0 {
|
||||
return repositories.ErrTagExists
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *storyRepository) RemoveTag(ctx context.Context, story models.Story, tag models.Tag) error {
|
||||
ci, err := r.stories.UpdateAll(bson.M{"_id": story.ID}, bson.M{"$pull": bson.M{"tags": tag}})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ci.Updated == 0 {
|
||||
return repositories.ErrTagDoesNotExist
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *storyRepository) Delete(ctx context.Context, story models.Story) error {
|
||||
err := r.stories.RemoveId(story.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
chapterIds := make([]string, 0, 8)
|
||||
err = r.chapters.Find(bson.M{"storyId": story.ID}).Distinct("_id", &chapterIds)
|
||||
if err != nil {
|
||||
log.Println("Failed to find chapterIds:", err)
|
||||
return nil
|
||||
}
|
||||
if len(chapterIds) > 0 {
|
||||
c1, err := r.chapters.RemoveAll(bson.M{"storyId": story.ID})
|
||||
if err != nil {
|
||||
log.Println("Failed to remove chapters:", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
c2, err := r.comments.RemoveAll(bson.M{"chapterId": bson.M{"$in": chapterIds}})
|
||||
if err != nil {
|
||||
log.Println("Failed to remove comments:", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Printf("Removed story %s (%d chapters, %d comments)", story.ID, c1.Removed, c2.Removed)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package mongodb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"git.aiterp.net/rpdata/api/repositories"
|
||||
"github.com/globalsign/mgo"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
)
|
||||
|
||||
type tagRepository struct {
|
||||
stories *mgo.Collection
|
||||
}
|
||||
|
||||
func newTagRepository(db *mgo.Database) repositories.TagRepository {
|
||||
return &tagRepository{
|
||||
stories: db.C("story.stories"),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *tagRepository) Find(ctx context.Context, kind models.TagKind, name string) (*models.Tag, error) {
|
||||
tags := make([]*models.Tag, 0, 1)
|
||||
err := r.stories.Find(bson.M{"listed": true, "tags": bson.M{"kind": kind, "name": name}}).Distinct("tag", &tags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if len(tags) == 0 {
|
||||
return nil, repositories.ErrNotFound
|
||||
}
|
||||
|
||||
for _, tag := range tags {
|
||||
if tag.Kind == kind && tag.Name == name {
|
||||
return tag, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, repositories.ErrNotFound
|
||||
}
|
||||
|
||||
func (r *tagRepository) List(ctx context.Context, filter models.TagFilter) ([]*models.Tag, error) {
|
||||
tags := make([]*models.Tag, 0, 64)
|
||||
|
||||
query := bson.M{"listed": true, "tags": bson.M{"$ne": nil}}
|
||||
if filter.Kind != nil {
|
||||
query["tags.kind"] = *filter.Kind
|
||||
}
|
||||
|
||||
err := r.stories.Find(query).Distinct("tags", &tags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if filter.Kind != nil {
|
||||
// While it's unsorted, delete any incorrect tags with replacement.
|
||||
for i := 0; i < len(tags); i++ {
|
||||
if tags[i].Kind != *filter.Kind {
|
||||
tags[i] = tags[len(tags)-1]
|
||||
tags = tags[:len(tags)-1]
|
||||
i--
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(tags, func(i, j int) bool {
|
||||
if filter.Kind == nil {
|
||||
kindCmp := strings.Compare(string(tags[i].Kind), string(tags[j].Kind))
|
||||
if kindCmp != 0 {
|
||||
return kindCmp < 0
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Compare(tags[i].Name, tags[j].Name) < 0
|
||||
})
|
||||
|
||||
return tags, nil
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package mongodb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"git.aiterp.net/rpdata/api/repositories"
|
||||
"github.com/globalsign/mgo"
|
||||
"github.com/globalsign/mgo/bson"
|
||||
)
|
||||
|
||||
type userRepository struct {
|
||||
users *mgo.Collection
|
||||
}
|
||||
|
||||
func newUserRepository(db *mgo.Database) (repositories.UserRepository, error) {
|
||||
collection := db.C("core.users")
|
||||
|
||||
return &userRepository{users: collection}, nil
|
||||
}
|
||||
|
||||
func (r *userRepository) Find(ctx context.Context, id string) (*models.User, error) {
|
||||
user := new(models.User)
|
||||
err := r.users.FindId(id).One(user)
|
||||
if err != nil {
|
||||
if err == mgo.ErrNotFound {
|
||||
return nil, repositories.ErrNotFound
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (r *userRepository) List(ctx context.Context) ([]*models.User, error) {
|
||||
users := make([]*models.User, 0, 8)
|
||||
err := r.users.Find(bson.M{}).All(&users)
|
||||
if err != nil && err != mgo.ErrNotFound {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (r *userRepository) Insert(ctx context.Context, user models.User) (*models.User, error) {
|
||||
err := r.users.Insert(user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.aiterp.net/rpdata/api/database/postgres/psqlcore"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type changeRepository struct {
|
||||
insertWithIDs bool
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func (r *changeRepository) Find(ctx context.Context, id string) (*models.Change, error) {
|
||||
row, err := psqlcore.New(r.db).SelectChangeByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.change(row)
|
||||
}
|
||||
|
||||
func (r *changeRepository) List(ctx context.Context, filter models.ChangeFilter) ([]*models.Change, error) {
|
||||
params := psqlcore.SelectChangesParams{LimitSize: 0}
|
||||
if filter.Keys != nil {
|
||||
params.FilterKeys = true
|
||||
keys := make([]string, len(filter.Keys))
|
||||
for i, ck := range filter.Keys {
|
||||
keys[i] = ck.String()
|
||||
}
|
||||
params.Keys = keys
|
||||
}
|
||||
if filter.Author != nil {
|
||||
params.FilterAuthor = true
|
||||
params.Author = *filter.Author
|
||||
}
|
||||
if filter.EarliestDate != nil {
|
||||
params.FilterEarliestDate = true
|
||||
params.EarliestDate = filter.EarliestDate.In(time.UTC)
|
||||
}
|
||||
if filter.LatestDate != nil {
|
||||
params.FilterLatestDate = true
|
||||
params.LatestDate = filter.LatestDate.In(time.UTC)
|
||||
}
|
||||
if filter.Limit != nil && *filter.Limit <= 0 {
|
||||
params.LimitSize = int32(*filter.Limit)
|
||||
}
|
||||
|
||||
rows, err := psqlcore.New(r.db).SelectChanges(ctx, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.changes(rows)
|
||||
}
|
||||
|
||||
func (r *changeRepository) Insert(ctx context.Context, change models.Change) (*models.Change, error) {
|
||||
tx, err := r.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
|
||||
q := psqlcore.New(tx)
|
||||
|
||||
if !r.insertWithIDs || change.ID == "" {
|
||||
next, err := q.IncrementCounter(ctx, "data_change_id")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
change.ID = fmt.Sprintf("Change_%d", next)
|
||||
} else {
|
||||
if len(change.ID) < 8 {
|
||||
return nil, errors.New("invalid change id")
|
||||
}
|
||||
|
||||
n, err := strconv.Atoi(change.ID[7:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = q.BumpCounter(ctx, psqlcore.BumpCounterParams{ID: "data_change_id", Value: int32(n)})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
data, err := r.dataChange(change)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = q.InsertChange(ctx, psqlcore.InsertChangeParams(*data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &change, tx.Commit()
|
||||
}
|
||||
|
||||
func (r *changeRepository) Remove(ctx context.Context, change models.Change) error {
|
||||
return psqlcore.New(r.db).DeleteChange(ctx, change.ID)
|
||||
}
|
||||
|
||||
func (r *changeRepository) dataChange(change models.Change) (*psqlcore.DataChange, error) {
|
||||
objectsJSON, err := json.Marshal(change.ChangeObjectSet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keys := make([]string, len(change.Keys))
|
||||
for i, ck := range change.Keys {
|
||||
keys[i] = ck.String()
|
||||
}
|
||||
|
||||
return &psqlcore.DataChange{
|
||||
ID: change.ID,
|
||||
Model: string(change.Model),
|
||||
Op: change.Op,
|
||||
Author: change.Author,
|
||||
Listed: change.Listed,
|
||||
Date: change.Date.In(time.UTC),
|
||||
Keys: keys,
|
||||
Objects: objectsJSON,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *changeRepository) change(row psqlcore.DataChange) (*models.Change, error) {
|
||||
objects := models.ChangeObjectSet{}
|
||||
|
||||
err := json.Unmarshal(row.Objects, &objects)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keys := make([]models.ChangeKey, len(row.Keys))
|
||||
for i := range keys {
|
||||
err := keys[i].Decode(row.Keys[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
model := models.ChangeModel(row.Model)
|
||||
if !model.IsValid() {
|
||||
return nil, errors.New("invalid model")
|
||||
}
|
||||
|
||||
return &models.Change{
|
||||
ID: row.ID,
|
||||
Model: model,
|
||||
Op: row.Op,
|
||||
Author: row.Author,
|
||||
Listed: row.Listed,
|
||||
Date: row.Date,
|
||||
Keys: keys,
|
||||
ChangeObjectSet: objects,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *changeRepository) changes(rows []psqlcore.DataChange) ([]*models.Change, error) {
|
||||
results := make([]*models.Change, len(rows))
|
||||
for i, row := range rows {
|
||||
change, err := r.change(row)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
results[i] = change
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"git.aiterp.net/rpdata/api/database/postgres/psqlcore"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
)
|
||||
|
||||
type channelRepository struct {
|
||||
insertWithIDs bool
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func (r *channelRepository) Find(ctx context.Context, name string) (*models.Channel, error) {
|
||||
row, err := psqlcore.New(r.db).SelectChannelByName(ctx, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.channel(row), nil
|
||||
}
|
||||
|
||||
func (r *channelRepository) List(ctx context.Context, filter models.ChannelFilter) ([]*models.Channel, error) {
|
||||
params := psqlcore.SelectChannelsParams{
|
||||
LimitSize: 0,
|
||||
}
|
||||
|
||||
if filter.Names != nil {
|
||||
params.FilterName = true
|
||||
params.Names = filter.Names
|
||||
}
|
||||
if filter.LocationName != nil {
|
||||
params.FilterLocationName = true
|
||||
params.LocationName = *filter.LocationName
|
||||
}
|
||||
if filter.EventName != nil {
|
||||
params.FilterEventName = true
|
||||
params.EventName = *filter.EventName
|
||||
}
|
||||
if filter.Logged != nil {
|
||||
params.FilterLogged = true
|
||||
params.Logged = *filter.Logged
|
||||
}
|
||||
if filter.Limit > 0 {
|
||||
params.LimitSize = int32(filter.Limit)
|
||||
}
|
||||
|
||||
rows, err := psqlcore.New(r.db).SelectChannels(ctx, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.channels(rows), nil
|
||||
}
|
||||
|
||||
func (r *channelRepository) Insert(ctx context.Context, channel models.Channel) (*models.Channel, error) {
|
||||
err := psqlcore.New(r.db).InsertChannel(ctx, psqlcore.InsertChannelParams(channel))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &channel, nil
|
||||
}
|
||||
|
||||
func (r *channelRepository) Update(ctx context.Context, channel models.Channel, update models.ChannelUpdate) (*models.Channel, error) {
|
||||
channel.ApplyUpdate(update)
|
||||
|
||||
err := psqlcore.New(r.db).UpdateChannel(ctx, psqlcore.UpdateChannelParams{
|
||||
Name: channel.Name,
|
||||
Logged: channel.Logged,
|
||||
Hub: channel.Hub,
|
||||
EventName: channel.EventName,
|
||||
LocationName: channel.LocationName,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &channel, nil
|
||||
}
|
||||
|
||||
func (r *channelRepository) Remove(ctx context.Context, channel models.Channel) error {
|
||||
return psqlcore.New(r.db).DeleteChannel(ctx, channel.Name)
|
||||
}
|
||||
|
||||
func (r *channelRepository) channel(row psqlcore.DataChannel) *models.Channel {
|
||||
return &models.Channel{
|
||||
Name: row.Name,
|
||||
Logged: row.Logged,
|
||||
Hub: row.Hub,
|
||||
EventName: row.EventName,
|
||||
LocationName: row.LocationName,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *channelRepository) channels(rows []psqlcore.DataChannel) []*models.Channel {
|
||||
results := make([]*models.Channel, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
results = append(results, r.channel(row))
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"git.aiterp.net/rpdata/api/database/postgres/psqlcore"
|
||||
"git.aiterp.net/rpdata/api/internal/generate"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
)
|
||||
|
||||
type chapterRepository struct {
|
||||
insertWithIDs bool
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func (r *chapterRepository) Find(ctx context.Context, id string) (*models.Chapter, error) {
|
||||
chapter, err := psqlcore.New(r.db).SelectChapter(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.chapter(chapter), nil
|
||||
}
|
||||
|
||||
func (r *chapterRepository) List(ctx context.Context, filter models.ChapterFilter) ([]*models.Chapter, error) {
|
||||
params := psqlcore.SelectChaptersParams{
|
||||
StoryID: "",
|
||||
LimitSize: 0,
|
||||
}
|
||||
if filter.StoryID != nil {
|
||||
params.StoryID = *filter.StoryID
|
||||
}
|
||||
if filter.Limit > 0 {
|
||||
params.LimitSize = int32(filter.Limit)
|
||||
}
|
||||
|
||||
chapters, err := psqlcore.New(r.db).SelectChapters(ctx, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.chapters(chapters), nil
|
||||
}
|
||||
|
||||
func (r *chapterRepository) Insert(ctx context.Context, chapter models.Chapter) (*models.Chapter, error) {
|
||||
if !r.insertWithIDs || len(chapter.ID) < 8 {
|
||||
chapter.ID = generate.ChapterID()
|
||||
}
|
||||
|
||||
err := psqlcore.New(r.db).InsertChapter(ctx, psqlcore.InsertChapterParams{
|
||||
ID: chapter.ID,
|
||||
StoryID: chapter.StoryID,
|
||||
Title: chapter.Title,
|
||||
Author: chapter.Author,
|
||||
Source: chapter.Source,
|
||||
CreatedDate: chapter.CreatedDate.UTC(),
|
||||
FictionalDate: chapter.FictionalDate.UTC(),
|
||||
EditedDate: chapter.EditedDate.UTC(),
|
||||
CommentMode: string(chapter.CommentMode),
|
||||
CommentsLocked: chapter.CommentsLocked,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &chapter, nil
|
||||
}
|
||||
|
||||
func (r *chapterRepository) Update(ctx context.Context, chapter models.Chapter, update models.ChapterUpdate) (*models.Chapter, error) {
|
||||
chapter.ApplyUpdate(update)
|
||||
|
||||
err := psqlcore.New(r.db).UpdateChapter(ctx, psqlcore.UpdateChapterParams{
|
||||
Title: chapter.Title,
|
||||
Source: chapter.Source,
|
||||
FictionalDate: chapter.FictionalDate.UTC(),
|
||||
CommentMode: string(chapter.CommentMode),
|
||||
CommentsLocked: chapter.CommentsLocked,
|
||||
ID: chapter.ID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &chapter, nil
|
||||
}
|
||||
|
||||
func (r *chapterRepository) Move(ctx context.Context, chapter models.Chapter, _, to models.Story) (*models.Chapter, error) {
|
||||
err := psqlcore.New(r.db).UpdateChapterStoryID(ctx, psqlcore.UpdateChapterStoryIDParams{
|
||||
StoryID: to.ID,
|
||||
ID: chapter.ID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
chapter.StoryID = to.ID
|
||||
|
||||
return &chapter, nil
|
||||
}
|
||||
|
||||
func (r *chapterRepository) Delete(ctx context.Context, chapter models.Chapter) error {
|
||||
tx, err := r.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
q := psqlcore.New(tx)
|
||||
|
||||
err = q.DeleteCommentsByChapterID(ctx, chapter.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = q.DeleteChapter(ctx, chapter.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (r *chapterRepository) chapter(chapter psqlcore.StoryChapter) *models.Chapter {
|
||||
return &models.Chapter{
|
||||
ID: chapter.ID,
|
||||
StoryID: chapter.StoryID,
|
||||
Title: chapter.Title,
|
||||
Author: chapter.Author,
|
||||
Source: chapter.Source,
|
||||
CreatedDate: chapter.CreatedDate,
|
||||
FictionalDate: chapter.FictionalDate,
|
||||
EditedDate: chapter.EditedDate,
|
||||
CommentMode: models.ChapterCommentMode(chapter.CommentMode),
|
||||
CommentsLocked: chapter.CommentsLocked,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *chapterRepository) chapters(chapters []psqlcore.StoryChapter) []*models.Chapter {
|
||||
results := make([]*models.Chapter, 0, len(chapters))
|
||||
for _, chapter := range chapters {
|
||||
results = append(results, r.chapter(chapter))
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.aiterp.net/rpdata/api/database/postgres/psqlcore"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var ErrNickConflict = errors.New("nick already in use by another character")
|
||||
|
||||
type characterRepository struct {
|
||||
insertWithIDs bool
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func (r *characterRepository) Find(ctx context.Context, id string) (*models.Character, error) {
|
||||
row, err := psqlcore.New(r.db).SelectCharacterByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.character(row), nil
|
||||
}
|
||||
|
||||
func (r *characterRepository) FindNick(ctx context.Context, nick string) (*models.Character, error) {
|
||||
row, err := psqlcore.New(r.db).SelectCharacterByNick(ctx, nick)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.character(psqlcore.SelectCharacterByIDRow(row)), nil
|
||||
}
|
||||
|
||||
func (r *characterRepository) FindName(ctx context.Context, name string) (*models.Character, error) {
|
||||
row, err := psqlcore.New(r.db).SelectCharacterByName(ctx, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.character(psqlcore.SelectCharacterByIDRow(row)), nil
|
||||
}
|
||||
|
||||
func (r *characterRepository) List(ctx context.Context, filter models.CharacterFilter) ([]*models.Character, error) {
|
||||
params := psqlcore.SelectCharactersParams{
|
||||
LimitSize: 0,
|
||||
}
|
||||
|
||||
if filter.IDs != nil {
|
||||
params.FilterID = true
|
||||
params.Ids = filter.IDs
|
||||
}
|
||||
if filter.Nicks != nil {
|
||||
params.FilterNick = true
|
||||
params.Nicks = filter.Nicks
|
||||
}
|
||||
if filter.Names != nil {
|
||||
params.FilterName = true
|
||||
params.Names = filter.Names
|
||||
}
|
||||
if filter.Author != nil {
|
||||
params.FilterAuthor = true
|
||||
params.Author = *filter.Author
|
||||
}
|
||||
if filter.Search != nil {
|
||||
params.FilterSearch = true
|
||||
params.Search = TSQueryFromSearch(*filter.Search)
|
||||
}
|
||||
if filter.Limit > 0 {
|
||||
params.LimitSize = int32(filter.Limit)
|
||||
}
|
||||
|
||||
rows, err := psqlcore.New(r.db).SelectCharacters(ctx, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.characters(rows), nil
|
||||
}
|
||||
|
||||
func (r *characterRepository) Insert(ctx context.Context, character models.Character) (*models.Character, error) {
|
||||
tx, err := r.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
q := psqlcore.New(tx)
|
||||
|
||||
if !r.insertWithIDs || character.ID == "" {
|
||||
next, err := q.IncrementCounter(ctx, "data_character_id")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
character.ID = fmt.Sprintf("C%d", next)
|
||||
} else {
|
||||
n, err := strconv.Atoi(character.ID[1:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = q.BumpCounter(ctx, psqlcore.BumpCounterParams{ID: "data_character_id", Value: int32(n)})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
rows, err := q.SelectCharacters(ctx, psqlcore.SelectCharactersParams{
|
||||
FilterNick: true,
|
||||
Nicks: character.Nicks,
|
||||
LimitSize: 1,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to select: %s", err)
|
||||
}
|
||||
if len(rows) != 0 {
|
||||
return nil, ErrNickConflict
|
||||
}
|
||||
|
||||
err = q.InsertCharacter(ctx, psqlcore.InsertCharacterParams{
|
||||
ID: character.ID,
|
||||
Nicks: character.Nicks,
|
||||
Name: character.Name,
|
||||
ShortName: character.ShortName,
|
||||
Author: character.Author,
|
||||
Description: character.Description,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &character, nil
|
||||
}
|
||||
|
||||
func (r *characterRepository) Update(ctx context.Context, character models.Character, update models.CharacterUpdate) (*models.Character, error) {
|
||||
character.ApplyUpdate(update)
|
||||
|
||||
err := psqlcore.New(r.db).UpdateCharacter(ctx, psqlcore.UpdateCharacterParams{
|
||||
ID: character.ID,
|
||||
Name: character.Name,
|
||||
ShortName: character.ShortName,
|
||||
Description: character.Description,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &character, nil
|
||||
}
|
||||
|
||||
func (r *characterRepository) AddNick(ctx context.Context, character models.Character, nick string) (*models.Character, error) {
|
||||
tx, err := r.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
|
||||
q := psqlcore.New(tx)
|
||||
|
||||
rows, err := q.SelectCharacters(ctx, psqlcore.SelectCharactersParams{
|
||||
FilterNick: true,
|
||||
Nicks: []string{nick},
|
||||
LimitSize: 1,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(rows) != 0 {
|
||||
return nil, ErrNickConflict
|
||||
}
|
||||
|
||||
err = q.AddCharacterNick(ctx, psqlcore.AddCharacterNickParams{ID: character.ID, Nick: nick})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
character.Nicks = append(character.Nicks, nick)
|
||||
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &character, nil
|
||||
}
|
||||
|
||||
func (r *characterRepository) RemoveNick(ctx context.Context, character models.Character, nick string) (*models.Character, error) {
|
||||
err := psqlcore.New(r.db).RemoveCharacterNick(ctx, psqlcore.RemoveCharacterNickParams{ID: character.ID, Nick: nick})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i, nick2 := range character.Nicks {
|
||||
if nick2 == nick {
|
||||
character.Nicks = append(character.Nicks[:i], character.Nicks[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return &character, nil
|
||||
}
|
||||
|
||||
func (r *characterRepository) Delete(ctx context.Context, character models.Character) error {
|
||||
return psqlcore.New(r.db).DeleteCharacter(ctx, character.ID)
|
||||
}
|
||||
|
||||
func (r *characterRepository) character(row psqlcore.SelectCharacterByIDRow) *models.Character {
|
||||
return &models.Character{
|
||||
ID: strings.Trim(row.ID, " "),
|
||||
Nicks: row.Nicks,
|
||||
Name: row.Name,
|
||||
ShortName: row.ShortName,
|
||||
Author: row.Author,
|
||||
Description: row.Description,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *characterRepository) characters(rows []psqlcore.SelectCharactersRow) []*models.Character {
|
||||
results := make([]*models.Character, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
results = append(results, r.character(psqlcore.SelectCharacterByIDRow(row)))
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"git.aiterp.net/rpdata/api/database/postgres/psqlcore"
|
||||
"git.aiterp.net/rpdata/api/internal/generate"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
)
|
||||
|
||||
type commentRepository struct {
|
||||
insertWithIDs bool
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func (r *commentRepository) Find(ctx context.Context, id string) (*models.Comment, error) {
|
||||
comment, err := psqlcore.New(r.db).SelectComment(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.comment(comment), nil
|
||||
}
|
||||
|
||||
func (r *commentRepository) List(ctx context.Context, filter models.CommentFilter) ([]*models.Comment, error) {
|
||||
params := psqlcore.SelectCommentsParams{
|
||||
ChapterID: "",
|
||||
LimitSize: 0,
|
||||
}
|
||||
if filter.ChapterID != nil {
|
||||
params.ChapterID = *filter.ChapterID
|
||||
}
|
||||
if filter.Limit > 0 {
|
||||
params.LimitSize = int32(filter.Limit)
|
||||
}
|
||||
|
||||
comments, err := psqlcore.New(r.db).SelectComments(ctx, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.comments(comments), nil
|
||||
}
|
||||
|
||||
func (r *commentRepository) Insert(ctx context.Context, comment models.Comment) (*models.Comment, error) {
|
||||
if !r.insertWithIDs || len(comment.ID) < 8 {
|
||||
comment.ID = generate.CommentID()
|
||||
}
|
||||
|
||||
err := psqlcore.New(r.db).InsertComment(ctx, psqlcore.InsertCommentParams{
|
||||
ID: comment.ID,
|
||||
ChapterID: comment.ChapterID,
|
||||
CharacterID: comment.CharacterID,
|
||||
Subject: comment.Subject,
|
||||
Author: comment.Author,
|
||||
CharacterName: comment.CharacterName,
|
||||
Source: comment.Source,
|
||||
CreatedDate: comment.CreatedDate.UTC(),
|
||||
FictionalDate: comment.FictionalDate.UTC(),
|
||||
EditedDate: comment.EditedDate.UTC(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &comment, nil
|
||||
}
|
||||
|
||||
func (r *commentRepository) Update(ctx context.Context, comment models.Comment, update models.CommentUpdate) (*models.Comment, error) {
|
||||
comment.ApplyUpdate(update)
|
||||
|
||||
err := psqlcore.New(r.db).UpdateComment(ctx, psqlcore.UpdateCommentParams{
|
||||
Subject: comment.Subject,
|
||||
Source: comment.Source,
|
||||
FictionalDate: comment.FictionalDate.UTC(),
|
||||
EditedDate: comment.EditedDate.UTC(),
|
||||
CharacterName: comment.CharacterName,
|
||||
CharacterID: comment.CharacterID,
|
||||
ID: comment.ID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &comment, nil
|
||||
}
|
||||
|
||||
func (r *commentRepository) Delete(ctx context.Context, comment models.Comment) error {
|
||||
return psqlcore.New(r.db).DeleteComment(ctx, comment.ID)
|
||||
}
|
||||
|
||||
func (r *commentRepository) comment(comment psqlcore.StoryComment) *models.Comment {
|
||||
return &models.Comment{
|
||||
ID: comment.ID,
|
||||
ChapterID: comment.ChapterID,
|
||||
Subject: comment.Subject,
|
||||
Author: comment.Author,
|
||||
CharacterName: comment.CharacterName,
|
||||
CharacterID: comment.CharacterID,
|
||||
FictionalDate: comment.FictionalDate,
|
||||
CreatedDate: comment.CreatedDate,
|
||||
EditedDate: comment.EditedDate,
|
||||
Source: comment.Source,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *commentRepository) comments(comments []psqlcore.StoryComment) []*models.Comment {
|
||||
results := make([]*models.Comment, 0, len(comments))
|
||||
for _, comment := range comments {
|
||||
results = append(results, r.comment(comment))
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"git.aiterp.net/rpdata/api/database/postgres/psqlcore"
|
||||
"git.aiterp.net/rpdata/api/internal/config"
|
||||
"git.aiterp.net/rpdata/api/repositories"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Connect(cfg config.Database) (*DB, error) {
|
||||
sslMode := "disable"
|
||||
if cfg.SSL {
|
||||
sslMode = "enable"
|
||||
}
|
||||
psqlInfo := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=%s",
|
||||
cfg.Host, cfg.Port, cfg.Username, cfg.Password, cfg.Db, sslMode,
|
||||
)
|
||||
|
||||
timeout, cancel := context.WithTimeout(context.Background(), time.Second*15)
|
||||
defer cancel()
|
||||
|
||||
db, err := sql.Open("postgres", psqlInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := db.PingContext(timeout); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
db.SetMaxIdleConns(32)
|
||||
|
||||
q := psqlcore.New(db)
|
||||
if err := q.EnsureCounter(timeout, "data_character_id"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := q.EnsureCounter(timeout, "data_change_id"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := q.EnsureCounter(timeout, "log_short_id"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &DB{
|
||||
db: db,
|
||||
insertWithIDs: cfg.RestoreIDs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type DB struct {
|
||||
insertWithIDs bool
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func (d *DB) Changes() repositories.ChangeRepository {
|
||||
return &changeRepository{insertWithIDs: d.insertWithIDs, db: d.db}
|
||||
}
|
||||
|
||||
func (d *DB) Channels() repositories.ChannelRepository {
|
||||
return &channelRepository{insertWithIDs: d.insertWithIDs, db: d.db}
|
||||
}
|
||||
|
||||
func (d *DB) Characters() repositories.CharacterRepository {
|
||||
return &characterRepository{insertWithIDs: d.insertWithIDs, db: d.db}
|
||||
}
|
||||
|
||||
func (d *DB) Tags() repositories.TagRepository {
|
||||
return &tagRepository{db: d.db}
|
||||
}
|
||||
|
||||
func (d *DB) Logs() repositories.LogRepository {
|
||||
return &logRepository{insertWithIDs: d.insertWithIDs, db: d.db}
|
||||
}
|
||||
|
||||
func (d *DB) Posts() repositories.PostRepository {
|
||||
return &postRepository{insertWithIDs: d.insertWithIDs, db: d.db}
|
||||
}
|
||||
|
||||
func (d *DB) Stories() repositories.StoryRepository {
|
||||
return &storyRepository{insertWithIDs: d.insertWithIDs, db: d.db}
|
||||
}
|
||||
|
||||
func (d *DB) Chapters() repositories.ChapterRepository {
|
||||
return &chapterRepository{insertWithIDs: d.insertWithIDs, db: d.db}
|
||||
}
|
||||
|
||||
func (d *DB) Comments() repositories.CommentRepository {
|
||||
return &commentRepository{insertWithIDs: d.insertWithIDs, db: d.db}
|
||||
}
|
||||
|
||||
func (d *DB) Keys() repositories.KeyRepository {
|
||||
return &keyRepository{insertWithIDs: d.insertWithIDs, db: d.db}
|
||||
}
|
||||
|
||||
func (d *DB) Users() repositories.UserRepository {
|
||||
return &userRepository{db: d.db}
|
||||
}
|
||||
|
||||
func (d *DB) Files() repositories.FileRepository {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (d *DB) Close(context.Context) error {
|
||||
return d.db.Close()
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"git.aiterp.net/rpdata/api/database/postgres/psqlcore"
|
||||
"git.aiterp.net/rpdata/api/internal/generate"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
)
|
||||
|
||||
type keyRepository struct {
|
||||
db *sql.DB
|
||||
insertWithIDs bool
|
||||
}
|
||||
|
||||
func (r *keyRepository) Find(ctx context.Context, id string) (*models.Key, error) {
|
||||
key, err := psqlcore.New(r.db).SelectKey(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &models.Key{
|
||||
ID: key.ID,
|
||||
Name: key.Name,
|
||||
User: key.User,
|
||||
Secret: key.Secret,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *keyRepository) List(ctx context.Context, filter models.KeyFilter) ([]*models.Key, error) {
|
||||
var keys []psqlcore.Key
|
||||
|
||||
if filter.UserID != nil {
|
||||
keys2, err := psqlcore.New(r.db).SelectKeysByUser(ctx, *filter.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keys = keys2
|
||||
} else {
|
||||
keys2, err := psqlcore.New(r.db).SelectKeys(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keys = keys2
|
||||
}
|
||||
|
||||
results := make([]*models.Key, 0, len(keys))
|
||||
for _, key := range keys {
|
||||
results = append(results, &models.Key{
|
||||
ID: key.ID,
|
||||
Name: key.Name,
|
||||
User: key.User,
|
||||
Secret: key.Secret,
|
||||
})
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (r *keyRepository) Insert(ctx context.Context, key models.Key) (*models.Key, error) {
|
||||
if !r.insertWithIDs || len(key.ID) < 4 {
|
||||
key.ID = generate.KeyID()
|
||||
}
|
||||
|
||||
err := psqlcore.New(r.db).InsertKey(ctx, psqlcore.InsertKeyParams{
|
||||
ID: key.ID,
|
||||
Name: key.Name,
|
||||
UserID: key.User,
|
||||
Secret: key.Secret,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &key, nil
|
||||
}
|
||||
|
||||
func (r *keyRepository) Delete(ctx context.Context, key models.Key) error {
|
||||
return psqlcore.New(r.db).DeleteKey(ctx, key.ID)
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"git.aiterp.net/rpdata/api/database/postgres/psqlcore"
|
||||
"git.aiterp.net/rpdata/api/internal/generate"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type logRepository struct {
|
||||
insertWithIDs bool
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func (r *logRepository) Find(ctx context.Context, id string) (*models.Log, error) {
|
||||
log, err := psqlcore.New(r.db).SelectLog(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.log(log), nil
|
||||
}
|
||||
|
||||
func (r *logRepository) List(ctx context.Context, filter models.LogFilter) ([]*models.Log, error) {
|
||||
q := psqlcore.New(r.db)
|
||||
params := psqlcore.SelectLogsParams{
|
||||
LimitSize: 0,
|
||||
}
|
||||
|
||||
if filter.Search != nil {
|
||||
params.FilterSearch = true
|
||||
params.Search = TSQueryFromSearch(*filter.Search)
|
||||
}
|
||||
if filter.Open != nil {
|
||||
params.FilterOpen = true
|
||||
params.Open = *filter.Open
|
||||
}
|
||||
if len(filter.Characters) > 0 {
|
||||
params.FilterCharacterID = true
|
||||
params.CharacterIds = filter.Characters
|
||||
}
|
||||
if len(filter.Channels) > 0 {
|
||||
params.FilterChannelName = true
|
||||
params.ChannelNames = filter.Channels
|
||||
}
|
||||
if len(filter.Events) > 0 {
|
||||
params.FilterEventName = true
|
||||
params.EventNames = filter.Events
|
||||
}
|
||||
if filter.MinDate != nil {
|
||||
params.FilterEarlistDate = true
|
||||
params.EarliestDate = *filter.MinDate
|
||||
}
|
||||
if filter.MaxDate != nil {
|
||||
params.FilterLastestDate = true
|
||||
params.LatestDate = *filter.MaxDate
|
||||
}
|
||||
if filter.Limit > 0 {
|
||||
params.LimitSize = int32(filter.Limit)
|
||||
}
|
||||
|
||||
logs, err := q.SelectLogs(ctx, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.logs(logs), nil
|
||||
}
|
||||
|
||||
func (r *logRepository) Insert(ctx context.Context, log models.Log) (*models.Log, error) {
|
||||
tx, err := r.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
q := psqlcore.New(tx)
|
||||
|
||||
if !r.insertWithIDs || log.ID == "" {
|
||||
log.ID = generate.LogID(log)
|
||||
}
|
||||
|
||||
if !r.insertWithIDs || log.ShortID == "" {
|
||||
next, err := q.IncrementCounter(ctx, "log_short_id")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.ShortID = fmt.Sprintf("L%d", next)
|
||||
} else {
|
||||
n, err := strconv.Atoi(log.ShortID[1:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = q.BumpCounter(ctx, psqlcore.BumpCounterParams{
|
||||
ID: "log_short_id",
|
||||
Value: int32(n),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if log.CharacterIDs == nil {
|
||||
log.CharacterIDs = []string{}
|
||||
}
|
||||
|
||||
err = q.InsertLog(ctx, psqlcore.InsertLogParams{
|
||||
ID: log.ID,
|
||||
ShortID: log.ShortID,
|
||||
Date: log.Date.UTC(),
|
||||
ChannelName: log.ChannelName,
|
||||
EventName: log.EventName,
|
||||
Title: log.Title,
|
||||
Description: log.Description,
|
||||
Open: log.Open,
|
||||
CharacterIds: log.CharacterIDs,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_ = r.updateTags(log)
|
||||
|
||||
return &log, nil
|
||||
}
|
||||
|
||||
func (r *logRepository) Update(ctx context.Context, log models.Log, update models.LogUpdate) (*models.Log, error) {
|
||||
log.ApplyUpdate(update)
|
||||
|
||||
err := psqlcore.New(r.db).UpdateLog(ctx, psqlcore.UpdateLogParams{
|
||||
Title: log.Title,
|
||||
EventName: log.EventName,
|
||||
Description: log.Description,
|
||||
Open: log.Open,
|
||||
CharacterIds: log.CharacterIDs,
|
||||
ID: log.ID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_ = r.updateTags(log)
|
||||
|
||||
return &log, nil
|
||||
}
|
||||
|
||||
func (r *logRepository) Delete(ctx context.Context, log models.Log) error {
|
||||
tx, err := r.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
q := psqlcore.New(tx)
|
||||
|
||||
err = q.DeleteLog(ctx, log.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = q.DeletePostsByLogShortID(ctx, log.ShortID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (r *logRepository) log(log psqlcore.SelectLogRow) *models.Log {
|
||||
return &models.Log{
|
||||
ID: log.ID,
|
||||
ShortID: log.ShortID,
|
||||
Date: log.Date,
|
||||
ChannelName: log.ChannelName,
|
||||
EventName: log.EventName,
|
||||
Title: log.Title,
|
||||
Description: log.Description,
|
||||
Open: log.Open,
|
||||
CharacterIDs: log.CharacterIds,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *logRepository) logs(logs []psqlcore.SelectLogsRow) []*models.Log {
|
||||
results := make([]*models.Log, 0, len(logs))
|
||||
for _, log := range logs {
|
||||
results = append(results, r.log(psqlcore.SelectLogRow(log)))
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func (r *logRepository) updateTags(log models.Log) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
|
||||
tx, err := r.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
q := psqlcore.New(tx)
|
||||
|
||||
err = q.ClearTagsByTarget(ctx, psqlcore.ClearTagsByTargetParams{TargetKind: "Log", TargetID: log.ID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(log.CharacterIDs) > 0 {
|
||||
err := q.SetCharacterTagsFromIDs(ctx, psqlcore.SetCharacterTagsFromIDsParams{
|
||||
TargetKind: "log",
|
||||
TargetID: log.ID,
|
||||
CharacterIds: log.CharacterIDs,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = q.SetLocationTagFromChannelName(ctx, psqlcore.SetLocationTagFromChannelNameParams{
|
||||
TargetKind: "log",
|
||||
TargetID: log.ID,
|
||||
ChannelName: log.ChannelName,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if log.EventName != "" {
|
||||
err := q.SetTag(ctx, psqlcore.SetTagParams{
|
||||
Tag: "Event:" + log.EventName,
|
||||
TargetKind: "Log",
|
||||
TargetID: log.ID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE TABLE data_character (
|
||||
id CHAR(8) NOT NULL PRIMARY KEY,
|
||||
nicks TEXT[] NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
short_name TEXT NOT NULL,
|
||||
author TEXT NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
ts_vector TSVECTOR NOT NULL
|
||||
);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP TABLE data_character;
|
||||
-- +goose StatementEnd
|
||||
@@ -0,0 +1,9 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE INDEX data_character_index_character_name ON data_character(name);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP INDEX IF EXISTS data_character_index_character_name;
|
||||
-- +goose StatementEnd
|
||||
@@ -0,0 +1,9 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE INDEX data_character_index_character_short_name ON data_character(short_name);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP INDEX IF EXISTS data_character_index_character_short_name;
|
||||
-- +goose StatementEnd
|
||||
@@ -0,0 +1,9 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE INDEX data_character_index_character_author ON data_character(author);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP INDEX IF EXISTS data_character_index_character_author;
|
||||
-- +goose StatementEnd
|
||||
@@ -0,0 +1,12 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE TABLE core_counter (
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
value INTEGER
|
||||
);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP TABLE core_counter;
|
||||
-- +goose StatementEnd
|
||||
@@ -0,0 +1,9 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE INDEX data_character_index_character_nicks on data_character USING GIN (nicks);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP INDEX IF EXISTS data_character_index_character_nicks;
|
||||
-- +goose StatementEnd
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE FUNCTION immutable_array_to_string(arr ANYARRAY, sep TEXT)
|
||||
RETURNS text
|
||||
AS $$
|
||||
SELECT array_to_string(arr, sep);
|
||||
$$
|
||||
LANGUAGE SQL
|
||||
IMMUTABLE;
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP FUNCTION IF EXISTS immutable_array_to_string;
|
||||
-- +goose StatementEnd
|
||||
@@ -0,0 +1,9 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE INDEX data_character_index_search ON data_character USING GIN (ts_vector);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP INDEX IF EXISTS data_character_index_search;
|
||||
-- +goose StatementEnd
|
||||
@@ -0,0 +1,15 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE TABLE data_channel (
|
||||
name TEXT NOT NULL PRIMARY KEY,
|
||||
logged BOOLEAN NOT NULL,
|
||||
hub BOOLEAN NOT NULL,
|
||||
event_name TEXT NOT NULL,
|
||||
location_name TEXT NOT NULL
|
||||
);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP TABLE IF EXISTS data_channel;
|
||||
-- +goose StatementEnd
|
||||
@@ -0,0 +1,18 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE TABLE data_change (
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
model TEXT NOT NULL,
|
||||
op TEXT NOT NULL,
|
||||
author TEXT NOT NULL,
|
||||
listed BOOLEAN NOT NULL,
|
||||
date TIMESTAMP NOT NULL,
|
||||
keys TEXT[] NOT NULL,
|
||||
objects JSONB NOT NULL
|
||||
);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP TABLE IF EXISTS data_change;
|
||||
-- +goose StatementEnd
|
||||
@@ -0,0 +1,9 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE INDEX data_change_index_keys on data_change USING GIN (keys);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP INDEX IF EXISTS data_change_index_keys;
|
||||
-- +goose StatementEnd
|
||||
@@ -0,0 +1,9 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE INDEX data_change_index_date ON data_change (date);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP INDEX IF EXISTS data_change_index_date;
|
||||
-- +goose StatementEnd
|
||||
@@ -0,0 +1,9 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE INDEX data_change_index_author ON data_change (author);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP INDEX IF EXISTS data_change_index_author;
|
||||
-- +goose StatementEnd
|
||||
@@ -0,0 +1,15 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE TABLE common_tag (
|
||||
tag TEXT NOT NULL,
|
||||
target_kind TEXT NOT NULL,
|
||||
target_id TEXT NOT NULL,
|
||||
|
||||
PRIMARY KEY (tag, target_kind, target_id)
|
||||
);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP TABLE common_tag;
|
||||
-- +goose StatementEnd
|
||||
@@ -0,0 +1,9 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE INDEX common_tag_index_target ON common_tag (target_kind, target_id);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP INDEX IF EXISTS common_tag_index_target;
|
||||
-- +goose StatementEnd
|
||||
@@ -0,0 +1,20 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE TABLE story (
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
author TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
category TEXT NOT NULL,
|
||||
open BOOLEAN NOT NULL,
|
||||
listed BOOLEAN NOT NULL,
|
||||
sort_by_fictional_date BOOLEAN NOT NULL,
|
||||
created_date TIMESTAMP NOT NULL,
|
||||
fictional_date TIMESTAMP NOT NULL,
|
||||
updated_date TIMESTAMP NOT NULL
|
||||
);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP TABLE story;
|
||||
-- +goose StatementEnd
|
||||
@@ -0,0 +1,9 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE INDEX story_index_author ON story (author);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP INDEX IF EXISTS story_index_author
|
||||
-- +goose StatementEnd
|
||||
@@ -0,0 +1,9 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE INDEX story_index_fictional_date ON story (fictional_date);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP INDEX IF EXISTS story_index_fictional_date
|
||||
-- +goose StatementEnd
|
||||
@@ -0,0 +1,9 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE INDEX story_index_created_date ON story (created_date);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP INDEX IF EXISTS story_index_created_date
|
||||
-- +goose StatementEnd
|
||||
@@ -0,0 +1,9 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE INDEX story_index_updated_date ON story (updated_date);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP INDEX IF EXISTS story_index_updated_date
|
||||
-- +goose StatementEnd
|
||||
@@ -0,0 +1,20 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE TABLE story_chapter (
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
story_id TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
author TEXT NOT NULL,
|
||||
source TEXT NOT NULL,
|
||||
created_date TIMESTAMP NOT NULL,
|
||||
fictional_date TIMESTAMP NOT NULL,
|
||||
edited_date TIMESTAMP NOT NULL,
|
||||
comment_mode TEXT NOT NULL,
|
||||
comments_locked BOOLEAN NOT NULL
|
||||
);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP TABLE story_chapter;
|
||||
-- +goose StatementEnd
|
||||
@@ -0,0 +1,9 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE INDEX story_chapter_index_story_id ON story_chapter (story_id);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP INDEX IF EXISTS story_chapter_index_story_id;
|
||||
-- +goose StatementEnd
|
||||
@@ -0,0 +1,20 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE TABLE story_comment (
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
chapter_id TEXT NOT NULL,
|
||||
character_id TEXT NOT NULL,
|
||||
subject TEXT NOT NULL,
|
||||
author TEXT NOT NULL,
|
||||
character_name TEXT NOT NULL,
|
||||
source TEXT NOT NULL,
|
||||
created_date TIMESTAMP NOT NULL,
|
||||
fictional_date TIMESTAMP NOT NULL,
|
||||
edited_date TIMESTAMP NOT NULL
|
||||
);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP TABLE story_comment;
|
||||
-- +goose StatementEnd
|
||||
@@ -0,0 +1,9 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE INDEX story_comment_index_chapter_id ON story_comment (chapter_id);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP INDEX IF EXISTS story_comment_index_chapter_id;
|
||||
-- +goose StatementEnd
|
||||
@@ -0,0 +1,19 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE TABLE log (
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
short_id TEXT NOT NULL UNIQUE,
|
||||
character_ids TEXT[] NOT NULL,
|
||||
date TIMESTAMP NOT NULL,
|
||||
channel_name TEXT NOT NULL,
|
||||
event_name TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
open BOOLEAN NOT NULL
|
||||
);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP TABLE log;
|
||||
-- +goose StatementEnd
|
||||
@@ -0,0 +1,20 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE TABLE log_post (
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
log_short_id TEXT NOT NULL,
|
||||
time TIMESTAMP NOT NULL,
|
||||
kind TEXT NOT NULL,
|
||||
nick TEXT NOT NULL,
|
||||
text TEXT NOT NULL,
|
||||
position INT NOT NULL,
|
||||
ts_vector TSVECTOR NOT NULL,
|
||||
|
||||
UNIQUE (log_short_id, position)
|
||||
);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP TABLE log_post;
|
||||
-- +goose StatementEnd
|
||||
@@ -0,0 +1,9 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE INDEX log_post_index_search ON log_post USING GIN (ts_vector);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP INDEX IF EXISTS log_post_index_search;
|
||||
-- +goose StatementEnd
|
||||
@@ -0,0 +1,9 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE INDEX log_index_open ON log (open);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP INDEX IF EXISTS log_index_open
|
||||
-- +goose StatementEnd
|
||||
@@ -0,0 +1,9 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE INDEX log_index_character_ids on log USING GIN (character_ids);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP INDEX IF EXISTS log_index_character_ids;
|
||||
-- +goose StatementEnd
|
||||
@@ -0,0 +1,9 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE INDEX log_index_channel_name ON log (channel_name);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP INDEX IF EXISTS log_index_channel_name
|
||||
-- +goose StatementEnd
|
||||
@@ -0,0 +1,9 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE INDEX log_index_event_name ON log (event_name);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP INDEX IF EXISTS log_index_event_name
|
||||
-- +goose StatementEnd
|
||||
@@ -0,0 +1,9 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE INDEX log_index_date ON log (date);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP INDEX IF EXISTS log_index_date
|
||||
-- +goose StatementEnd
|
||||
@@ -0,0 +1,9 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE INDEX log_post_index_kind ON log_post (kind);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP INDEX IF EXISTS log_post_index_kind
|
||||
-- +goose StatementEnd
|
||||
@@ -0,0 +1,14 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE TABLE key (
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
"user" TEXT NOT NULL,
|
||||
secret TEXT NOT NULL
|
||||
);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP TABLE key;
|
||||
-- +goose StatementEnd
|
||||
@@ -0,0 +1,13 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE TABLE "user" (
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
nick TEXT NOT NULL,
|
||||
permissions TEXT[] NOT NULL
|
||||
);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP TABLE "user";
|
||||
-- +goose StatementEnd
|
||||
@@ -0,0 +1,9 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
ALTER TABLE log ADD COLUMN "ts_vector" TSVECTOR;
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
ALTER TABLE log DROP COLUMN "ts_vector";
|
||||
-- +goose StatementEnd
|
||||
@@ -0,0 +1,9 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE INDEX log_index_search ON log USING GIN (ts_vector);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP INDEX IF EXISTS log_index_search;
|
||||
-- +goose StatementEnd
|
||||
@@ -0,0 +1,303 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"git.aiterp.net/rpdata/api/database/postgres/psqlcore"
|
||||
"git.aiterp.net/rpdata/api/internal/generate"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type postRepository struct {
|
||||
insertWithIDs bool
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func (r *postRepository) Find(ctx context.Context, id string) (*models.Post, error) {
|
||||
post, err := psqlcore.New(r.db).SelectPost(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.post(post), nil
|
||||
}
|
||||
|
||||
func (r *postRepository) List(ctx context.Context, filter models.PostFilter) ([]*models.Post, error) {
|
||||
q := psqlcore.New(r.db)
|
||||
params := psqlcore.SelectPostsParams{LimitSize: 0}
|
||||
if filter.LogID != nil {
|
||||
params.FilterLogShortID = true
|
||||
|
||||
if !strings.HasPrefix(*filter.LogID, "L") {
|
||||
log, err := q.SelectLog(ctx, *filter.LogID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
params.LogShortID = log.ShortID
|
||||
} else {
|
||||
params.LogShortID = *filter.LogID
|
||||
}
|
||||
}
|
||||
if filter.Kinds != nil {
|
||||
params.FilterKinds = true
|
||||
params.Kinds = filter.Kinds
|
||||
}
|
||||
if filter.Search != nil {
|
||||
params.FilterSearch = true
|
||||
params.Search = TSQueryFromSearch(*filter.Search)
|
||||
}
|
||||
if filter.Limit > 0 {
|
||||
params.LimitSize = int32(filter.Limit)
|
||||
}
|
||||
|
||||
posts, err := q.SelectPosts(ctx, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.posts(posts), nil
|
||||
}
|
||||
|
||||
func (r *postRepository) Insert(ctx context.Context, post models.Post) (*models.Post, error) {
|
||||
if !r.insertWithIDs || len(post.ID) < 8 {
|
||||
post.ID = generate.PostID()
|
||||
}
|
||||
|
||||
position, err := psqlcore.New(r.db).InsertPost(ctx, psqlcore.InsertPostParams{
|
||||
ID: post.ID,
|
||||
LogShortID: post.LogID,
|
||||
Time: post.Time.UTC(),
|
||||
Kind: post.Kind,
|
||||
Nick: post.Nick,
|
||||
Text: post.Text,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_ = psqlcore.New(r.db).GenerateLogTSVector(ctx, post.LogID)
|
||||
|
||||
post.Position = int(position)
|
||||
|
||||
return &post, nil
|
||||
}
|
||||
|
||||
func (r *postRepository) InsertMany(ctx context.Context, posts ...*models.Post) ([]*models.Post, error) {
|
||||
if len(posts) == 0 {
|
||||
return []*models.Post{}, nil
|
||||
}
|
||||
|
||||
allowedLogID := ""
|
||||
for _, post := range posts {
|
||||
if allowedLogID == "" {
|
||||
allowedLogID = post.LogID
|
||||
} else if allowedLogID != post.LogID {
|
||||
return nil, errors.New("cannot insert multiple posts with different log IDs")
|
||||
}
|
||||
}
|
||||
|
||||
params := psqlcore.InsertPostsParams{
|
||||
LogShortID: posts[0].LogID,
|
||||
}
|
||||
|
||||
for _, post := range posts {
|
||||
if !r.insertWithIDs || len(post.ID) < 8 {
|
||||
post.ID = generate.PostID()
|
||||
}
|
||||
|
||||
params.Ids = append(params.Ids, post.ID)
|
||||
params.Kinds = append(params.Kinds, post.Kind)
|
||||
params.Nicks = append(params.Nicks, post.Nick)
|
||||
params.Offsets = append(params.Offsets, int32(len(params.Offsets)+1))
|
||||
params.Times = append(params.Times, post.Time.UTC())
|
||||
params.Texts = append(params.Texts, post.Text)
|
||||
}
|
||||
|
||||
offset, err := psqlcore.New(r.db).InsertPosts(ctx, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i, post := range posts {
|
||||
post.Position = int(offset) + i
|
||||
}
|
||||
|
||||
err = psqlcore.New(r.db).GenerateLogTSVector(ctx, posts[0].LogID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return posts, nil
|
||||
}
|
||||
|
||||
func (r *postRepository) Update(ctx context.Context, post models.Post, update models.PostUpdate) (*models.Post, error) {
|
||||
post.ApplyUpdate(update)
|
||||
|
||||
err := psqlcore.New(r.db).UpdatePost(ctx, psqlcore.UpdatePostParams{
|
||||
Time: post.Time,
|
||||
Kind: post.Kind,
|
||||
Nick: post.Nick,
|
||||
Text: post.Text,
|
||||
ID: post.ID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_ = psqlcore.New(r.db).GenerateLogTSVector(ctx, post.LogID)
|
||||
|
||||
return &post, nil
|
||||
}
|
||||
|
||||
func (r *postRepository) Move(ctx context.Context, post models.Post, position int) ([]*models.Post, error) {
|
||||
if position == post.Position {
|
||||
return r.List(ctx, models.PostFilter{LogID: &post.LogID})
|
||||
}
|
||||
|
||||
tx, err := r.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
|
||||
_, err = tx.Exec("LOCK TABLE log_post IN SHARE UPDATE EXCLUSIVE MODE")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var lowest, highest int32
|
||||
|
||||
q := psqlcore.New(tx)
|
||||
|
||||
err = q.MovePost(ctx, psqlcore.MovePostParams{ID: post.ID, Position: -1})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if position > post.Position {
|
||||
lowest = int32(post.Position)
|
||||
highest = int32(position)
|
||||
|
||||
err := q.ShiftPostsBetween(ctx, psqlcore.ShiftPostsBetweenParams{
|
||||
ShiftOffset: -1,
|
||||
LogShortID: post.LogID,
|
||||
FromPosition: lowest + 1,
|
||||
ToPosition: highest,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
lowest = int32(position)
|
||||
highest = int32(post.Position)
|
||||
|
||||
err := q.ShiftPostsBetween(ctx, psqlcore.ShiftPostsBetweenParams{
|
||||
ShiftOffset: 1,
|
||||
LogShortID: post.LogID,
|
||||
FromPosition: lowest,
|
||||
ToPosition: highest - 1,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
err = q.MovePost(ctx, psqlcore.MovePostParams{ID: post.ID, Position: int32(position)})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
posts, err := q.SelectPostsByPositionRange(ctx, psqlcore.SelectPostsByPositionRangeParams{
|
||||
LogShortID: post.LogID,
|
||||
FromPosition: lowest,
|
||||
ToPosition: highest,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
positions, err := q.SelectPositionsByLogShortID(ctx, post.LogID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
prev := int32(0)
|
||||
for _, pos := range positions {
|
||||
if pos != prev+1 {
|
||||
return nil, errors.New("post discontinuity detected")
|
||||
}
|
||||
|
||||
prev = pos
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.posts2(posts), nil
|
||||
}
|
||||
|
||||
func (r *postRepository) Delete(ctx context.Context, post models.Post) error {
|
||||
tx, err := r.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
q := psqlcore.New(tx)
|
||||
|
||||
_, err = tx.Exec("LOCK TABLE log_post IN SHARE UPDATE EXCLUSIVE MODE")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = q.DeletePost(ctx, post.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = q.ShiftPostsAfter(ctx, psqlcore.ShiftPostsAfterParams{
|
||||
ShiftOffset: -1,
|
||||
LogShortID: post.LogID,
|
||||
FromPosition: int32(post.Position + 1),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_ = q.GenerateLogTSVector(ctx, post.LogID)
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (r *postRepository) post(post psqlcore.SelectPostRow) *models.Post {
|
||||
return &models.Post{
|
||||
ID: post.ID,
|
||||
LogID: post.LogShortID,
|
||||
Time: post.Time,
|
||||
Kind: post.Kind,
|
||||
Nick: post.Nick,
|
||||
Text: post.Text,
|
||||
Position: int(post.Position),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *postRepository) posts(posts []psqlcore.SelectPostsRow) []*models.Post {
|
||||
results := make([]*models.Post, 0, len(posts))
|
||||
for _, post := range posts {
|
||||
results = append(results, r.post(psqlcore.SelectPostRow(post)))
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func (r *postRepository) posts2(posts []psqlcore.SelectPostsByPositionRangeRow) []*models.Post {
|
||||
results := make([]*models.Post, 0, len(posts))
|
||||
for _, post := range posts {
|
||||
results = append(results, r.post(psqlcore.SelectPostRow(post)))
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
//go:generate go run github.com/kyleconroy/sqlc/cmd/sqlc generate -f ../../../sqlc.yaml
|
||||
|
||||
// Package psqlcore contains the generated code for the PostgresSQL drivers. It's generated using sqlc.
|
||||
package psqlcore
|
||||
@@ -0,0 +1,22 @@
|
||||
-- name: SelectChangeByID :one
|
||||
SELECT * FROM data_change WHERE id = $1 LIMIT 1;
|
||||
|
||||
-- name: SelectChanges :many
|
||||
SELECT * FROM data_change
|
||||
WHERE (@filter_keys::bool = false OR keys && (@keys::text[]))
|
||||
AND (@filter_earliest_date::bool = false OR date >= @earliest_date::timestamp)
|
||||
AND (@filter_latest_date::bool = false OR date <= @latest_date::timestamp)
|
||||
AND (@filter_author::bool = false OR author = @author::text)
|
||||
ORDER BY date DESC
|
||||
LIMIT NULLIF(@limit_size::INT, 0);
|
||||
|
||||
-- name: InsertChange :exec
|
||||
INSERT INTO data_change (id, model, op, author, listed, date, keys, objects)
|
||||
VALUES (
|
||||
@id::text, @model::text, @op::text, @author::text,
|
||||
@listed::boolean, @date::timestamp, @keys::text[],
|
||||
@objects::jsonb
|
||||
);
|
||||
|
||||
-- name: DeleteChange :exec
|
||||
DELETE FROM data_change WHERE id = $1;
|
||||
@@ -0,0 +1,30 @@
|
||||
-- name: SelectChannelByName :one
|
||||
SELECT * FROM data_channel WHERE name = $1 LIMIT 1;
|
||||
|
||||
-- name: InsertChannel :exec
|
||||
INSERT INTO data_channel (name, logged, hub, event_name, location_name)
|
||||
VALUES (
|
||||
@name::text,
|
||||
@logged::boolean, @hub::boolean,
|
||||
@event_name::text, @location_name::text
|
||||
);
|
||||
|
||||
-- name: SelectChannels :many
|
||||
SELECT * FROM data_channel
|
||||
WHERE (@filter_name::bool = false OR name = ANY(@names::text[]))
|
||||
AND (@filter_logged::bool = false OR logged = @logged)
|
||||
AND (@filter_event_name::bool = false OR event_name = @event_name)
|
||||
AND (@filter_location_name::bool = false OR location_name = @location_name)
|
||||
ORDER BY name
|
||||
LIMIT NULLIF(@limit_size::INT, 0);
|
||||
|
||||
-- name: UpdateChannel :exec
|
||||
UPDATE data_channel
|
||||
SET logged = @logged::boolean,
|
||||
hub = @hub::boolean,
|
||||
event_name = @event_name::text,
|
||||
location_name = @location_name::text
|
||||
WHERE name = @name::text;
|
||||
|
||||
-- name: DeleteChannel :exec
|
||||
DELETE FROM data_channel WHERE name=$1;
|
||||
@@ -0,0 +1,36 @@
|
||||
-- name: SelectChapter :one
|
||||
SELECT * FROM story_chapter WHERE id=$1::TEXT LIMIT 1;
|
||||
|
||||
-- name: SelectChapters :many
|
||||
SELECT * FROM story_chapter
|
||||
WHERE (@story_id::TEXT = '' OR story_id = @story_id::TEXT)
|
||||
ORDER BY created_date
|
||||
LIMIT NULLIF(@limit_size::INT, 0);
|
||||
|
||||
-- name: InsertChapter :exec
|
||||
INSERT INTO story_chapter (id, story_id, title, author, source, created_date, fictional_date, edited_date, comment_mode, comments_locked)
|
||||
VALUES (
|
||||
@id::TEXT, @story_id::TEXT, @title::TEXT, @author::TEXT, @source::TEXT,
|
||||
@created_date::TIMESTAMP, @fictional_date::TIMESTAMP, @edited_date::TIMESTAMP,
|
||||
@comment_mode::TEXT, @comments_locked::BOOLEAN
|
||||
);
|
||||
|
||||
-- name: UpdateChapterStoryID :exec
|
||||
UPDATE story_chapter
|
||||
SET story_id = @story_id::TEXT
|
||||
WHERE id = @id;
|
||||
|
||||
-- name: UpdateChapter :exec
|
||||
UPDATE story_chapter
|
||||
SET title = @title,
|
||||
source = @source,
|
||||
fictional_date = @fictional_date,
|
||||
comment_mode = @comment_mode,
|
||||
comments_locked = @comments_locked
|
||||
WHERE id = @id;
|
||||
|
||||
-- name: DeleteChapter :exec
|
||||
DELETE FROM story_chapter WHERE id=$1;
|
||||
|
||||
-- name: DeleteChaptersByStoryID :exec
|
||||
DELETE FROM story_chapter WHERE story_id=$1;
|
||||
@@ -0,0 +1,47 @@
|
||||
-- name: SelectCharacterByID :one
|
||||
SELECT id, nicks, name, short_name, author, description FROM data_character WHERE id = @id::text;
|
||||
|
||||
-- name: SelectCharacterByNick :one
|
||||
SELECT id, nicks, name, short_name, author, description FROM data_character WHERE nicks <@ ARRAY[@nick::text];
|
||||
|
||||
-- name: SelectCharacterByName :one
|
||||
SELECT id, nicks, name, short_name, author, description FROM data_character WHERE name = @name::text;
|
||||
|
||||
-- name: SelectCharacters :many
|
||||
SELECT id, nicks, name, short_name, author, description FROM data_character
|
||||
WHERE (@filter_id::bool = false OR id = ANY(@ids::text[]))
|
||||
AND (@filter_name::bool = false OR name = ANY(@names::text[]))
|
||||
AND (@filter_nick::bool = false OR nicks && (@nicks::text[]))
|
||||
AND (@filter_author::bool = false OR author = @author::text)
|
||||
AND (@filter_search::bool = false OR "ts_vector" @@ to_tsquery(@search::text))
|
||||
LIMIT NULLIF(@limit_size::INT, 0);
|
||||
|
||||
-- name: InsertCharacter :exec
|
||||
INSERT INTO data_character (id, nicks, name, short_name, author, description, ts_vector) VALUES (
|
||||
@id::text, @nicks::text[], @name::text,
|
||||
@short_name::text, @author::text, @description::text,
|
||||
to_tsvector(
|
||||
'english', @name::text || ' ' || @description::text || ' ' || @author::text || ' '
|
||||
|| immutable_array_to_string(@nicks::text[], ' ')
|
||||
)
|
||||
);
|
||||
|
||||
-- name: UpdateCharacter :exec
|
||||
UPDATE data_character
|
||||
SET name = @name::text,
|
||||
short_name = @short_name::text,
|
||||
description = @description::text
|
||||
WHERE id = @id::text;
|
||||
|
||||
-- name: AddCharacterNick :exec
|
||||
UPDATE data_character
|
||||
SET nicks=array_append(nicks, @nick::text)
|
||||
WHERE id = @id::text;
|
||||
|
||||
-- name: RemoveCharacterNick :exec
|
||||
UPDATE data_character
|
||||
SET nicks=array_remove(nicks, @nick::text)
|
||||
WHERE id = @id::text;
|
||||
|
||||
-- name: DeleteCharacter :exec
|
||||
DELETE FROM data_character WHERE id=$1;
|
||||
@@ -0,0 +1,37 @@
|
||||
-- name: SelectComment :one
|
||||
SELECT * FROM story_comment WHERE id=$1;
|
||||
|
||||
-- name: InsertComment :exec
|
||||
INSERT INTO story_comment (id, chapter_id, character_id, subject, author, character_name, source, created_date, fictional_date, edited_date)
|
||||
VALUES (
|
||||
@id, @chapter_id, @character_id,
|
||||
@subject, @author, @character_name, @source,
|
||||
@created_date, @fictional_date, @edited_date
|
||||
);
|
||||
|
||||
-- name: UpdateComment :exec
|
||||
UPDATE story_comment
|
||||
SET subject = @subject,
|
||||
source = @source,
|
||||
fictional_date = @fictional_date,
|
||||
edited_date = @edited_date,
|
||||
character_name = @character_name,
|
||||
character_id = @character_id
|
||||
WHERE id = @id;
|
||||
|
||||
-- name: SelectComments :many
|
||||
SELECT * FROM story_comment
|
||||
WHERE (@chapter_id = '' OR chapter_id = @chapter_id)
|
||||
LIMIT NULLIF(@limit_size::INT, 0);
|
||||
|
||||
-- name: DeleteComment :exec
|
||||
DELETE FROM story_comment WHERE id = $1;
|
||||
|
||||
-- name: DeleteCommentsByChapterID :exec
|
||||
DELETE FROM story_comment WHERE chapter_id = $1;
|
||||
|
||||
-- name: DeleteCommentsByStoryID :exec
|
||||
DELETE FROM story_comment
|
||||
WHERE chapter_id IN (
|
||||
SELECT id FROM story_chapter WHERE story_id = $1
|
||||
);
|
||||
@@ -0,0 +1,8 @@
|
||||
-- name: EnsureCounter :exec
|
||||
INSERT INTO core_counter (id, value) VALUES (@id::text, 0) ON CONFLICT DO NOTHING;
|
||||
|
||||
-- name: IncrementCounter :one
|
||||
UPDATE core_counter SET value = value + 1 WHERE id = @id::text RETURNING value::int;
|
||||
|
||||
-- name: BumpCounter :exec
|
||||
UPDATE core_counter SET value = @value::int WHERE id = @id::text AND value <= @value::int;
|
||||
@@ -0,0 +1,15 @@
|
||||
-- name: SelectKey :one
|
||||
SELECT * FROM "key" WHERE id=$1 LIMIT 1;
|
||||
|
||||
-- name: SelectKeys :many
|
||||
SELECT * FROM "key";
|
||||
|
||||
-- name: SelectKeysByUser :many
|
||||
SELECT * FROM "key" WHERE "user" = $1;
|
||||
|
||||
-- name: InsertKey :exec
|
||||
INSERT INTO key (id, name, "user", secret)
|
||||
VALUES (@id, @name, @user_id, @secret);
|
||||
|
||||
-- name: DeleteKey :exec
|
||||
DELETE FROM "key" WHERE id = $1;
|
||||
@@ -0,0 +1,44 @@
|
||||
-- name: SelectLog :one
|
||||
SELECT id, short_id, character_ids, date, channel_name, event_name, title, description, open FROM log WHERE id=$1 OR short_id=$1 LIMIT 1;
|
||||
|
||||
-- name: SelectLogs :many
|
||||
SELECT id, short_id, character_ids, date, channel_name, event_name, title, description, open FROM log
|
||||
WHERE (@filter_short_id::BOOL = false OR short_id = ANY(@short_ids::TEXT[]))
|
||||
AND (@filter_character_id::BOOL = false OR character_ids @> (@character_ids::TEXT[]))
|
||||
AND (@filter_channel_name::BOOL = false OR channel_name = ANY(@channel_names::TEXT[]))
|
||||
AND (@filter_event_name::BOOL = false OR event_name = ANY(@event_names::TEXT[]))
|
||||
AND (@filter_open::BOOL = false OR open = @open::BOOL)
|
||||
AND (@filter_earlist_date::BOOL = false OR date >= @earliest_date::TIMESTAMP)
|
||||
AND (@filter_lastest_date::BOOL = false OR date <= @latest_date::TIMESTAMP)
|
||||
AND (@filter_search::BOOL = false OR "ts_vector" @@ to_tsquery(@search::TEXT))
|
||||
ORDER BY date DESC
|
||||
LIMIT NULLIF(@limit_size::INT, 0);
|
||||
|
||||
-- name: InsertLog :exec
|
||||
INSERT INTO log (id, short_id, character_ids, date, channel_name, event_name, title, description, open)
|
||||
VALUES (
|
||||
@id, @short_id, @character_ids, @date, @channel_name, @event_name, @title, @description, @open
|
||||
);
|
||||
|
||||
-- name: UpdateLog :exec
|
||||
UPDATE log
|
||||
SET title = @title,
|
||||
event_name = @event_name,
|
||||
description = @description,
|
||||
open = @open,
|
||||
character_ids = @character_ids
|
||||
WHERE id = @id;
|
||||
|
||||
-- name: GenerateLogTSVector :exec
|
||||
UPDATE log
|
||||
SET "ts_vector" = (
|
||||
SELECT TO_TSVECTOR(
|
||||
immutable_array_to_string(array_agg(text), '\n\n')
|
||||
)
|
||||
FROM log_post
|
||||
WHERE log_short_id = @short_id
|
||||
)
|
||||
WHERE short_id = @short_id;
|
||||
|
||||
-- name: DeleteLog :exec
|
||||
DELETE FROM log WHERE id=$1;
|
||||
@@ -0,0 +1,63 @@
|
||||
-- name: SelectPost :one
|
||||
SELECT id, log_short_id, time, kind, nick, text, position FROM log_post WHERE id=$1 LIMIT 1;
|
||||
|
||||
-- name: SelectLogIDsFromPostSearch :many
|
||||
SELECT DISTINCT(log_short_id) FROM log_post WHERE "ts_vector" @@ to_tsquery(@search::TEXT);
|
||||
|
||||
-- name: SelectPostsByPositionRange :many
|
||||
SELECT id, log_short_id, time, kind, nick, text, position FROM log_post WHERE log_short_id = @log_short_id AND position >= @from_position AND position <= @to_position;
|
||||
|
||||
-- name: SelectPositionsByLogShortID :many
|
||||
SELECT position FROM log_post WHERE log_short_id = $1 ORDER BY position;
|
||||
|
||||
-- name: SelectPosts :many
|
||||
SELECT id, log_short_id, time, kind, nick, text, position FROM log_post
|
||||
WHERE (@filter_ids::BOOL = false OR id = ANY(@ids::TEXT[]))
|
||||
AND (@filter_kinds::BOOL = false OR kind = ANY(@kinds::TEXT[]))
|
||||
AND (@filter_log_short_id::BOOL = false OR log_short_id = @log_short_id)
|
||||
AND (@filter_search::BOOL = false OR "ts_vector" @@ to_tsquery(@search::TEXT))
|
||||
ORDER BY log_short_id, position
|
||||
LIMIT NULLIF(@limit_size::INT, 0);
|
||||
|
||||
-- name: InsertPost :one
|
||||
INSERT INTO log_post (id, log_short_id, time, kind, nick, text, position, ts_vector)
|
||||
SELECT @id, @log_short_id, @time, @kind, @nick, @text, COALESCE(MAX(position), 0)+1, to_tsvector(@nick || ' ' || @text)
|
||||
FROM log_post
|
||||
WHERE log_short_id = @log_short_id
|
||||
RETURNING position;
|
||||
|
||||
-- name: InsertPosts :one
|
||||
INSERT INTO log_post (id, log_short_id, time, kind, nick, text, position, ts_vector)
|
||||
SELECT UNNEST(@ids::TEXT[]), @log_short_id, UNNEST(@times::TIMESTAMP[]), UNNEST(@kinds::TEXT[]),
|
||||
UNNEST(@nicks::TEXT[]), UNNEST(@texts::TEXT[]), COALESCE(MAX(position), 0) + UNNEST(@offsets::INT[]),
|
||||
to_tsvector(UNNEST(@nicks::TEXT[]) || ' ' || UNNEST(@texts::TEXT[]))
|
||||
FROM log_post
|
||||
WHERE log_short_id = @log_short_id
|
||||
RETURNING position;
|
||||
|
||||
-- name: UpdatePost :exec
|
||||
UPDATE log_post
|
||||
SET time = @time,
|
||||
kind = @kind,
|
||||
nick = @nick,
|
||||
text = @text,
|
||||
ts_vector = to_tsvector(@nick || ' ' || @text)
|
||||
WHERE id = @id;
|
||||
|
||||
-- name: MovePost :exec
|
||||
UPDATE log_post SET position = @position WHERE id = @id;
|
||||
|
||||
-- name: ShiftPostsBetween :exec
|
||||
UPDATE log_post SET position = position + @shift_offset::INT WHERE log_short_id = @log_short_id AND position >= @from_position AND position <= @to_position;
|
||||
|
||||
-- name: ShiftPostsAfter :exec
|
||||
UPDATE log_post SET position = position + @shift_offset::INT WHERE log_short_id = @log_short_id AND position >= @from_position;
|
||||
|
||||
-- name: ShiftPostsBefore :exec
|
||||
UPDATE log_post SET position = position + @shift_offset::INT WHERE log_short_id = @log_short_id AND position <= @to_position;
|
||||
|
||||
-- name: DeletePost :exec
|
||||
DELETE FROM log_post WHERE id = @id;
|
||||
|
||||
-- name: DeletePostsByLogShortID :exec
|
||||
DELETE FROM log_post WHERE log_short_id = @log_short_id;
|
||||
@@ -0,0 +1,37 @@
|
||||
-- name: SelectStory :one
|
||||
SELECT * FROM story WHERE id = $1 LIMIT 1;
|
||||
|
||||
-- name: SelectStories :many
|
||||
SELECT * FROM story
|
||||
WHERE (@filter_id::bool = false OR id = ANY(@ids::text[]))
|
||||
AND (@filter_author::bool = false OR author = @author::text)
|
||||
AND (@filter_earlist_fictional_date::bool = false OR fictional_date >= @earliest_fictional_date::timestamp)
|
||||
AND (@filter_lastest_fictional_date::bool = false OR fictional_date <= @latest_fictional_date::timestamp)
|
||||
AND (@filter_category::bool = false OR category = @category::text)
|
||||
AND (@filter_open::bool = false OR open = @open::bool)
|
||||
AND (@filter_listed::bool = false OR listed = @listed::bool)
|
||||
ORDER BY updated_date DESC
|
||||
LIMIT NULLIF(@limit_size::INT, 0);
|
||||
|
||||
-- name: InsertStory :exec
|
||||
INSERT INTO story (id, author, name, category, open, listed, sort_by_fictional_date, created_date, fictional_date, updated_date)
|
||||
VALUES (
|
||||
@id::text, @author::text, @name::text, @category::text,
|
||||
@open::boolean, @listed::boolean, @sort_by_fictional_date::boolean,
|
||||
@created_date::timestamp, @fictional_date::timestamp, @updated_date::timestamp
|
||||
);
|
||||
|
||||
-- name: UpdateStory :exec
|
||||
UPDATE story
|
||||
SET name = @name,
|
||||
category = @category,
|
||||
author = @author,
|
||||
open = @open,
|
||||
listed = @listed,
|
||||
fictional_date = @fictional_date,
|
||||
updated_date = @updated_date,
|
||||
sort_by_fictional_date = @sort_by_fictional_date
|
||||
WHERE id = @id;
|
||||
|
||||
-- name: DeleteStory :exec
|
||||
DELETE FROM story WHERE id=$1;
|
||||
@@ -0,0 +1,62 @@
|
||||
-- name: SetTag :exec
|
||||
INSERT INTO common_tag (tag, target_kind, target_id)
|
||||
VALUES (
|
||||
@tag::TEXT, @target_kind::TEXT, @target_id::TEXT
|
||||
)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- name: SetTags :exec
|
||||
INSERT INTO common_tag (tag, target_kind, target_id)
|
||||
SELECT unnest(@tags::TEXT[]), @target_kind::TEXT as target_kind, @target_id::TEXT as target_id
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- name: ClearTag :exec
|
||||
DELETE FROM common_tag
|
||||
WHERE tag = @tag::TEXT
|
||||
AND target_kind = @target_kind::TEXT
|
||||
AND target_id = @target_id::TEXT;
|
||||
|
||||
-- name: ClearTagsByTargetLike :exec
|
||||
DELETE FROM common_tag
|
||||
WHERE target_kind = @target_kind::TEXT
|
||||
AND target_id = @target_id::TEXT
|
||||
AND tag LIKE @tag_like::TEXT;
|
||||
|
||||
-- name: ClearTagsByTarget :exec
|
||||
DELETE FROM common_tag
|
||||
WHERE target_kind = @target_kind::TEXT
|
||||
AND target_id = @target_id::TEXT;
|
||||
|
||||
-- name: SetCharacterTagsFromIDs :exec
|
||||
INSERT INTO common_tag
|
||||
SELECT 'Character:'||name AS tag, @target_kind::TEXT as target_kind, @target_id::TEXT as target_id
|
||||
FROM data_character WHERE id = ANY(@character_ids::TEXT[])
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- name: SetLocationTagFromChannelName :exec
|
||||
INSERT INTO common_tag
|
||||
SELECT 'Location:'||location_name AS tag, @target_kind::TEXT as target_kind, @target_id::TEXT as target_id
|
||||
FROM data_channel
|
||||
WHERE name = @channel_name AND location_name != ''
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- name: SelectDistinctTags :many
|
||||
SELECT DISTINCT tag FROM common_tag ORDER BY tag;
|
||||
|
||||
-- name: SelectDistinctTagsLike :many
|
||||
SELECT DISTINCT tag FROM common_tag WHERE tag LIKE $1::text ORDER BY tag;
|
||||
|
||||
-- name: SelectTagsByTag :many
|
||||
SELECT * FROM common_tag WHERE tag = @tag_name::text ORDER BY tag;
|
||||
|
||||
-- name: SelectTagsByTags :many
|
||||
SELECT * FROM common_tag WHERE tag = ANY(@tag_names::text[]) ORDER BY tag;
|
||||
|
||||
-- name: SelectTargetsByTags :many
|
||||
SELECT DISTINCT target_id FROM common_tag WHERE tag = ANY(@tag_names::text[]) AND target_kind = @target_kind::text;
|
||||
|
||||
-- name: SelectTagsByTarget :many
|
||||
SELECT * FROM common_tag WHERE target_kind = @target_kind AND target_id = @target_id::text ORDER BY tag;
|
||||
|
||||
-- name: SelectTagsByTargets :many
|
||||
SELECT * FROM common_tag WHERE target_kind = @target_kind AND target_id = ANY(@target_ids::text[]) ORDER BY tag;
|
||||
@@ -0,0 +1,11 @@
|
||||
-- name: SelectUser :one
|
||||
SELECT * FROM "user" WHERE id=$1 LIMIT 1;
|
||||
|
||||
-- name: SelectUsers :many
|
||||
SELECT * FROM "user";
|
||||
|
||||
-- name: InsertUser :exec
|
||||
INSERT INTO "user" (id, nick, permissions)
|
||||
VALUES (
|
||||
@id, @nick, @permissions
|
||||
);
|
||||
@@ -0,0 +1,250 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"git.aiterp.net/rpdata/api/database/postgres/psqlcore"
|
||||
"git.aiterp.net/rpdata/api/internal/generate"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
)
|
||||
|
||||
type storyRepository struct {
|
||||
insertWithIDs bool
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func (r *storyRepository) Find(ctx context.Context, id string) (*models.Story, error) {
|
||||
q := psqlcore.New(r.db)
|
||||
story, err := q.SelectStory(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tags, err := q.SelectTagsByTarget(ctx, psqlcore.SelectTagsByTargetParams{
|
||||
TargetKind: "Story",
|
||||
TargetID: story.ID,
|
||||
})
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.story(story, tags), nil
|
||||
}
|
||||
|
||||
func (r *storyRepository) List(ctx context.Context, filter models.StoryFilter) ([]*models.Story, error) {
|
||||
q := psqlcore.New(r.db)
|
||||
params := psqlcore.SelectStoriesParams{LimitSize: 0}
|
||||
|
||||
if len(filter.Tags) > 0 {
|
||||
targets, err := q.SelectTargetsByTags(ctx, psqlcore.SelectTargetsByTagsParams{
|
||||
TagNames: models.EncodeTagArray(filter.Tags),
|
||||
TargetKind: "Story",
|
||||
})
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
params.FilterID = true
|
||||
params.Ids = targets
|
||||
|
||||
if len(params.Ids) == 0 {
|
||||
return []*models.Story{}, nil
|
||||
}
|
||||
}
|
||||
if filter.Author != nil {
|
||||
params.FilterAuthor = true
|
||||
params.Author = *filter.Author
|
||||
}
|
||||
if filter.Category != nil {
|
||||
params.FilterCategory = true
|
||||
params.Category = string(*filter.Category)
|
||||
}
|
||||
if !filter.EarliestFictionalDate.IsZero() {
|
||||
params.FilterEarlistFictionalDate = true
|
||||
params.EarliestFictionalDate = filter.EarliestFictionalDate.UTC()
|
||||
}
|
||||
if !filter.LatestFictionalDate.IsZero() {
|
||||
params.FilterLastestFictionalDate = true
|
||||
params.LatestFictionalDate = filter.LatestFictionalDate.UTC()
|
||||
}
|
||||
if filter.Open != nil {
|
||||
params.FilterOpen = true
|
||||
params.Open = *filter.Open
|
||||
}
|
||||
if filter.Unlisted != nil {
|
||||
params.FilterListed = true
|
||||
params.Listed = !*filter.Unlisted
|
||||
}
|
||||
if filter.Limit > 0 {
|
||||
params.LimitSize = int32(filter.Limit)
|
||||
}
|
||||
|
||||
stories, err := q.SelectStories(ctx, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
targetIDs := make([]string, len(stories))
|
||||
for i, story := range stories {
|
||||
targetIDs[i] = story.ID
|
||||
}
|
||||
tags, err := q.SelectTagsByTargets(ctx, psqlcore.SelectTagsByTargetsParams{
|
||||
TargetKind: "Story",
|
||||
TargetIds: targetIDs,
|
||||
})
|
||||
|
||||
return r.stories(stories, tags), nil
|
||||
}
|
||||
|
||||
func (r *storyRepository) Insert(ctx context.Context, story models.Story) (*models.Story, error) {
|
||||
tx, err := r.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
q := psqlcore.New(tx)
|
||||
|
||||
if !r.insertWithIDs || len(story.ID) < 8 {
|
||||
story.ID = generate.StoryID()
|
||||
}
|
||||
|
||||
if story.UpdatedDate.Before(story.CreatedDate) {
|
||||
story.UpdatedDate = story.CreatedDate
|
||||
}
|
||||
|
||||
err = q.InsertStory(ctx, psqlcore.InsertStoryParams{
|
||||
ID: story.ID,
|
||||
Author: story.Author,
|
||||
Name: story.Name,
|
||||
Category: string(story.Category),
|
||||
Open: story.Open,
|
||||
Listed: story.Listed,
|
||||
SortByFictionalDate: story.SortByFictionalDate,
|
||||
CreatedDate: story.CreatedDate.UTC(),
|
||||
FictionalDate: story.FictionalDate.UTC(),
|
||||
UpdatedDate: story.UpdatedDate.UTC(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// This is inefficient, but tags are very rarely added before the story is submitted.
|
||||
err = q.SetTags(ctx, psqlcore.SetTagsParams{
|
||||
Tags: models.EncodeTagArray(story.Tags),
|
||||
TargetKind: "Story",
|
||||
TargetID: story.ID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &story, tx.Commit()
|
||||
}
|
||||
|
||||
func (r *storyRepository) Update(ctx context.Context, story models.Story, update models.StoryUpdate) (*models.Story, error) {
|
||||
story.ApplyUpdate(update)
|
||||
|
||||
err := psqlcore.New(r.db).UpdateStory(ctx, psqlcore.UpdateStoryParams{
|
||||
Name: story.Name,
|
||||
Category: string(story.Category),
|
||||
Author: story.Author,
|
||||
Open: story.Open,
|
||||
Listed: story.Listed,
|
||||
FictionalDate: story.FictionalDate.UTC(),
|
||||
UpdatedDate: story.UpdatedDate.UTC(),
|
||||
SortByFictionalDate: story.SortByFictionalDate,
|
||||
ID: story.ID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &story, nil
|
||||
}
|
||||
|
||||
func (r *storyRepository) AddTag(ctx context.Context, story models.Story, tag models.Tag) error {
|
||||
return psqlcore.New(r.db).SetTag(ctx, psqlcore.SetTagParams{
|
||||
TargetKind: "Story",
|
||||
TargetID: story.ID,
|
||||
Tag: tag.String(),
|
||||
})
|
||||
}
|
||||
|
||||
func (r *storyRepository) RemoveTag(ctx context.Context, story models.Story, tag models.Tag) error {
|
||||
return psqlcore.New(r.db).ClearTag(ctx, psqlcore.ClearTagParams{
|
||||
TargetKind: "Story",
|
||||
TargetID: story.ID,
|
||||
Tag: tag.String(),
|
||||
})
|
||||
}
|
||||
|
||||
func (r *storyRepository) Delete(ctx context.Context, story models.Story) error {
|
||||
tx, err := r.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
q := psqlcore.New(tx)
|
||||
|
||||
err = q.ClearTagsByTarget(ctx, psqlcore.ClearTagsByTargetParams{
|
||||
TargetKind: "Story",
|
||||
TargetID: story.ID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = q.DeleteCommentsByStoryID(ctx, story.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = q.DeleteChaptersByStoryID(ctx, story.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = q.DeleteStory(ctx, story.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (r *storyRepository) story(story psqlcore.Story, tags []psqlcore.CommonTag) *models.Story {
|
||||
storyTags := make([]models.Tag, 0, 8)
|
||||
for _, tag := range tags {
|
||||
if tag.TargetKind == "Story" && tag.TargetID == story.ID {
|
||||
newTag := models.Tag{}
|
||||
err := newTag.Decode(tag.Tag)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
storyTags = append(storyTags, newTag)
|
||||
}
|
||||
}
|
||||
|
||||
return &models.Story{
|
||||
ID: story.ID,
|
||||
Author: story.Author,
|
||||
Name: story.Name,
|
||||
Category: models.StoryCategory(story.Category),
|
||||
Open: story.Open,
|
||||
Listed: story.Listed,
|
||||
Tags: storyTags,
|
||||
CreatedDate: story.CreatedDate,
|
||||
FictionalDate: story.FictionalDate,
|
||||
UpdatedDate: story.UpdatedDate,
|
||||
SortByFictionalDate: story.SortByFictionalDate,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *storyRepository) stories(stories []psqlcore.Story, tags []psqlcore.CommonTag) []*models.Story {
|
||||
results := make([]*models.Story, 0, len(stories))
|
||||
for _, story := range stories {
|
||||
results = append(results, r.story(story, tags))
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"git.aiterp.net/rpdata/api/database/postgres/psqlcore"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
)
|
||||
|
||||
type tagRepository struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func (r *tagRepository) Find(ctx context.Context, kind models.TagKind, name string) (*models.Tag, error) {
|
||||
tag, err := psqlcore.New(r.db).SelectTagsByTag(ctx, fmt.Sprintf("%s:%s", kind, name))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tag2 := &models.Tag{}
|
||||
err = tag2.Decode(tag[0].Tag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tag2, nil
|
||||
}
|
||||
|
||||
func (r *tagRepository) List(ctx context.Context, filter models.TagFilter) ([]*models.Tag, error) {
|
||||
var tags []string
|
||||
var err error
|
||||
if filter.Kind != nil {
|
||||
tags, err = psqlcore.New(r.db).SelectDistinctTagsLike(ctx, string(*filter.Kind)+"%")
|
||||
} else {
|
||||
tags, err = psqlcore.New(r.db).SelectDistinctTags(ctx)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return models.DecodeTagArray(tags)
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"git.aiterp.net/rpdata/api/database/postgres/psqlcore"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
)
|
||||
|
||||
type userRepository struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func (r *userRepository) Find(ctx context.Context, id string) (*models.User, error) {
|
||||
user, err := psqlcore.New(r.db).SelectUser(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &models.User{
|
||||
ID: user.ID,
|
||||
Nick: user.Nick,
|
||||
Permissions: user.Permissions,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *userRepository) List(ctx context.Context) ([]*models.User, error) {
|
||||
users, err := psqlcore.New(r.db).SelectUsers(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
results := make([]*models.User, 0, len(users))
|
||||
for _, user := range users {
|
||||
results = append(results, &models.User{
|
||||
ID: user.ID,
|
||||
Nick: user.Nick,
|
||||
Permissions: user.Permissions,
|
||||
})
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (r *userRepository) Insert(ctx context.Context, user models.User) (*models.User, error) {
|
||||
err := psqlcore.New(r.db).InsertUser(ctx, psqlcore.InsertUserParams{
|
||||
ID: user.ID,
|
||||
Nick: user.Nick,
|
||||
Permissions: user.Permissions,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package postgres
|
||||
|
||||
import "strings"
|
||||
|
||||
// TSQueryFromSearch generates a TS query from a typical search string.
|
||||
func TSQueryFromSearch(search string) string {
|
||||
if strings.HasPrefix(search, "tsq:") {
|
||||
return search[4:]
|
||||
}
|
||||
|
||||
tokens := strings.Split(search, " ")
|
||||
inQuotes := false
|
||||
result := ""
|
||||
|
||||
for i, token := range tokens {
|
||||
clearQuotes := false
|
||||
startQuotes := false
|
||||
|
||||
if strings.HasPrefix(token, "\"") && len(token) > 1 {
|
||||
startQuotes = true
|
||||
}
|
||||
if strings.HasSuffix(token, "\"") {
|
||||
if !startQuotes {
|
||||
clearQuotes = true
|
||||
}
|
||||
|
||||
startQuotes = false
|
||||
}
|
||||
|
||||
token := strings.Trim(token, ",.;:()\"")
|
||||
if len(token) > 0 {
|
||||
if startQuotes {
|
||||
if i > 0 {
|
||||
result += " & "
|
||||
}
|
||||
|
||||
result += "("
|
||||
} else if inQuotes {
|
||||
result += "<->"
|
||||
} else if i != 0 {
|
||||
result += " & "
|
||||
}
|
||||
|
||||
result += token
|
||||
}
|
||||
|
||||
if startQuotes {
|
||||
inQuotes = true
|
||||
}
|
||||
if clearQuotes {
|
||||
inQuotes = false
|
||||
result += ")"
|
||||
}
|
||||
}
|
||||
|
||||
if inQuotes {
|
||||
return result
|
||||
}
|
||||
|
||||
return strings.TrimLeft(result, " ")
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTSQueryFromSearch(t *testing.T) {
|
||||
rows := [][2]string{
|
||||
{`asari matron`, `asari & matron`},
|
||||
{`"asari matron"`, `(asari<->matron)`},
|
||||
{`"asari matron" blue`, `(asari<->matron) & blue`},
|
||||
{`"christmas present" "wrapping paper"`, `(christmas<->present) & (wrapping<->paper)`},
|
||||
{`stuff`, `stuff`},
|
||||
{`"stuff"`, `stuff`},
|
||||
{`"her slowly, rubbing the back of his head awkwardly. "`, `(her<->slowly<->rubbing<->the<->back<->of<->his<->head<->awkwardly)`},
|
||||
}
|
||||
|
||||
for i, row := range rows {
|
||||
t.Run(fmt.Sprintf("Row_%d", i), func(t *testing.T) {
|
||||
assert.Equal(t, row[1], TSQueryFromSearch(row[0]))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
module git.aiterp.net/rpdata/api
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/99designs/gqlgen v0.9.0
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/globalsign/mgo v0.0.0-20180403085842-f76e4f9da92e
|
||||
github.com/go-ini/ini v1.35.0 // indirect
|
||||
github.com/h2non/filetype v1.0.8
|
||||
github.com/kyleconroy/sqlc v1.7.0
|
||||
github.com/lib/pq v1.10.0
|
||||
github.com/minio/minio-go v0.0.0-20180409193742-3d2d02921f05
|
||||
github.com/prometheus/client_golang v0.9.3
|
||||
github.com/prometheus/procfs v0.0.0-20190517135640-51af30a78b0e // indirect
|
||||
github.com/stretchr/testify v1.5.1
|
||||
github.com/tidwall/pretty v1.0.0 // indirect
|
||||
github.com/vektah/dataloaden v0.3.0
|
||||
github.com/vektah/gqlparser v1.1.2
|
||||
go.mongodb.org/mongo-driver v1.0.3
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
replace golang.org/x/net => golang.org/x/net v0.0.0-20180416171110-a35a21de978d
|
||||
@@ -0,0 +1,497 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/99designs/gqlgen v0.9.0 h1:g1arBPML74Vqv0L3Q+TqIhGXLspV+2MYtRLkBxuZrlE=
|
||||
github.com/99designs/gqlgen v0.9.0/go.mod h1:HrrG7ic9EgLPsULxsZh/Ti+p0HNWgR3XRuvnD0pb5KY=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/agnivade/levenshtein v1.0.1 h1:3oJU7J3FGFmyhn8KHjmVaZCN5hxTr7GxgRue+sxIXdQ=
|
||||
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/antlr/antlr4 v0.0.0-20200209180723-1177c0b58d07 h1:ylxsz+1ifp/XBbiaFMqhB5YKshU47EzuZWpUIiH8urY=
|
||||
github.com/antlr/antlr4 v0.0.0-20200209180723-1177c0b58d07/go.mod h1:T7PbCXFs94rrTttyxjbyT5+/1V8T2TYDejxUfHJjw1Y=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/cznic/golex v0.0.0-20181122101858-9c343928389c/go.mod h1:+bmmJDNmKlhWNG+gwWCkaBoTy39Fs+bzRxVBzoTQbIc=
|
||||
github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM=
|
||||
github.com/cznic/parser v0.0.0-20160622100904-31edd927e5b1/go.mod h1:2B43mz36vGZNZEwkWi8ayRSSUXLfjL8OkbzwW4NcPMM=
|
||||
github.com/cznic/sortutil v0.0.0-20181122101858-f5f958428db8/go.mod h1:q2w6Bg5jeox1B+QkJ6Wp/+Vn0G/bo3f1uY7Fn3vivIQ=
|
||||
github.com/cznic/strutil v0.0.0-20171016134553-529a34b1c186/go.mod h1:AHHPPPXTw0h6pVabbcbyGRK1DckRn7r/STdZEeIDzZc=
|
||||
github.com/cznic/y v0.0.0-20170802143616-045f81c6662a/go.mod h1:1rk5VM7oSnA4vjp+hrLQ3HWHa+Y4yPCa3/CsJrcNnvs=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
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/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
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-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
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-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
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.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/h2non/filetype v1.0.8 h1:le8gpf+FQA0/DlDABbtisA1KiTS0Xi+YSC/E8yY3Y14=
|
||||
github.com/h2non/filetype v1.0.8/go.mod h1:isekKqOuhMj+s/7r3rIeTErIRy4Rub5uBWHfvMusLMU=
|
||||
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
||||
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
|
||||
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
|
||||
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
|
||||
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
|
||||
github.com/jackc/pgconn v1.4.0/go.mod h1:Y2O3ZDF0q4mMacyWV3AstPJpeHXWGEetiFttmq5lahk=
|
||||
github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
|
||||
github.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
|
||||
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
|
||||
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
|
||||
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
|
||||
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
|
||||
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
|
||||
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
|
||||
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
|
||||
github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
|
||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
|
||||
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
|
||||
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
|
||||
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
|
||||
github.com/jackc/pgtype v1.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0=
|
||||
github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkALtxZMCH411K+tKzNpwzCKU+AnPzBKZ+I+Po=
|
||||
github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ=
|
||||
github.com/jackc/pgtype v1.6.2/go.mod h1:JCULISAZBFGrHaOXIIFiyfzW5VY0GRitRr8NeJsrdig=
|
||||
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
|
||||
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
|
||||
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
|
||||
github.com/jackc/pgx/v4 v4.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXgo+kA=
|
||||
github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o=
|
||||
github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg=
|
||||
github.com/jackc/pgx/v4 v4.10.1/go.mod h1:QlrWebbs3kqEZPHCTGyxecvzG6tvIsYu+A5b1raylkA=
|
||||
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kyleconroy/sqlc v1.7.0 h1:a6/SblJ/NB48NhymCXsHHJlQzEqUOCKjvVJXTeqLn24=
|
||||
github.com/kyleconroy/sqlc v1.7.0/go.mod h1:BZmkzwSB91So4gfoDpwXoMqni9zfenoF/3Rv4t8NEj0=
|
||||
github.com/lfittl/pg_query_go v1.0.2 h1:adHtAAb0LpoXI3cYqdJTbo5W682+V9vg3/8ONAhnhqE=
|
||||
github.com/lfittl/pg_query_go v1.0.2/go.mod h1:Ch/Oiv5pRI5jf9M7Hvf0XwCvFwa4PWRcNoFcLtVU8pc=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.10.0 h1:Zx5DJFEYQXio93kgXnQ09fXNiUKsqv4OUEu2UtGcB1E=
|
||||
github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
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/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
|
||||
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pingcap/check v0.0.0-20190102082844-67f458068fc8 h1:USx2/E1bX46VG32FIw034Au6seQ2fY9NEILmNh/UlQg=
|
||||
github.com/pingcap/check v0.0.0-20190102082844-67f458068fc8/go.mod h1:B1+S9LNcuMyLH/4HMTViQOJevkGiik3wW2AN9zb2fNQ=
|
||||
github.com/pingcap/errors v0.11.0/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
|
||||
github.com/pingcap/errors v0.11.5-0.20201021055732-210aacd3fd99 h1:PVuEvTi/LlviMG7X3av44NRwcdPf0tiqL/YdVOIKCpA=
|
||||
github.com/pingcap/errors v0.11.5-0.20201021055732-210aacd3fd99/go.mod h1:G7x87le1poQzLB/TqvTJI2ILrSgobnq4Ut7luOwvfvI=
|
||||
github.com/pingcap/log v0.0.0-20200511115504-543df19646ad h1:SveG82rmu/GFxYanffxsSF503SiQV+2JLnWEiGiF+Tc=
|
||||
github.com/pingcap/log v0.0.0-20200511115504-543df19646ad/go.mod h1:4rbK1p9ILyIfb6hU7OG2CiWSqMXnp3JMbiaVJ6mvoY8=
|
||||
github.com/pingcap/parser v0.0.0-20201024025010-3b2fb4b41d73 h1:MN/e9n6GUBmmi6sKtriW0Dv1dfIohZRoKr/ZU1B4jrM=
|
||||
github.com/pingcap/parser v0.0.0-20201024025010-3b2fb4b41d73/go.mod h1:74+OEdwM4B/jMpBRl92ch6CSmSYkQtv2TNxIjFdT/GE=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM=
|
||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.0-20190517135640-51af30a78b0e h1:zK8d1aZ+gw/Ne4uMfZTFRxj08PUOp+gGwm4HWUeGI1k=
|
||||
github.com/prometheus/procfs v0.0.0-20190517135640-51af30a78b0e/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
|
||||
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M=
|
||||
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
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/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
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.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U=
|
||||
github.com/vektah/dataloaden v0.3.0 h1:ZfVN2QD6swgvp+tDqdH/OIT/wu3Dhu0cus0k5gIZS84=
|
||||
github.com/vektah/dataloaden v0.3.0/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U=
|
||||
github.com/vektah/gqlparser v1.1.2 h1:ZsyLGn7/7jDNI+y4SEhI4yAxRChlv15pUHMjijT+e68=
|
||||
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.mongodb.org/mongo-driver v1.0.3 h1:GKoji1ld3tw2aC+GX1wbr/J2fX13yNacEYoJ8Nhr0yU=
|
||||
go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
|
||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM=
|
||||
go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
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/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191219041853-979b82bfef62 h1:vDaiisQl0rGVXqk3wT2yc43gSnwlj4haEG5J78IGZP4=
|
||||
golang.org/x/tools v0.0.0-20191219041853-979b82bfef62/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
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/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
|
||||
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
|
||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71 h1:Xe2gvTZUJpsvOWUnvmL/tmhVBZUmHSvLbMjRj6NUUKo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
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=
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user