Compare commits

..
2 Commits
Author SHA1 Message Date
gisle 162ccf6bfb The great module madness continus, now with pointers.
the build was successful
2019-05-17 11:51:23 +02:00
gisle 7e3dc49635 Basic stuff, doesn't compile 2019-05-16 15:57:08 +02:00
281 changed files with 6432 additions and 12606 deletions
+15 -47
View File
@@ -1,53 +1,21 @@
name: rpdata-api workspace:
base: /go
path: src/git.aiterp.net/rpdata/api
kind: pipeline pipeline:
type: docker docker-release:
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 image: plugins/docker
settings: repo: vmaple/rpdata
auto_tag: true auto_tag: true
username: secrets: [ docker_username, docker_password ]
from_secret: docker_username
password:
from_secret: docker_password
repo: r.vmaple.dev/rpdata/rpdata-api
registry: r.vmaple.dev
when: when:
event: event: tag
- tag ref: refs/tags/*
docker-release-latest:
- name: docker-release-latest
image: plugins/docker image: plugins/docker
settings: repo: vmaple/rpdata
auto_tag: false tags: latest
username: secrets: [ docker_username, docker_password ]
from_secret: docker_username
password:
from_secret: docker_password
repo: r.vmaple.dev/rpdata/rpdata-api
registry: r.vmaple.dev
when: when:
event: event: tag
- tag ref: refs/tags/*
-9
View File
@@ -8,15 +8,6 @@ rpdata-graphiql
rpdata-* rpdata-*
!rpdata-*/ !rpdata-*/
.vscode .vscode
.idea
/main
/main.exe
/graph2/graphcore/*_gen.go
/database/postgres/psqlcore/*.go
!/database/postgres/psqlcore/package.go
generated.gql generated.gql
generated.go generated.go
/dump.zip
/rpdata-dump-v1/
+8 -5
View File
@@ -1,6 +1,6 @@
## 1. Build Server ## 1. Build Server
# Use Go 1.14 # Use Go 1.12
FROM golang:1.16 AS build-server FROM golang:1.12 AS build-server
# Load repository into docker # Load repository into docker
WORKDIR /project/ WORKDIR /project/
@@ -12,12 +12,15 @@ RUN go generate ./...
# Build server and tools (CGO disabled to make them 100% static) # Build server and tools (CGO disabled to make them 100% static)
ENV CGO_ENABLED 0 ENV CGO_ENABLED 0
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-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-lb2charimport ./cmd/rpdata-lb2charimport
RUN go build -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-restore ./cmd/rpdata-restore RUN go build -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-lb2logimport ./cmd/rpdata-lb2logimport
# TODO: RUN go build -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-wikifileimport ./cmd/rpdata-wikifileimport
RUN go build -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-ensurechannels ./cmd/rpdata-ensurechannels
RUN go build -installsuffix cgo -ldflags="-s -w" -o /binaries/rpdata-as2storyimport ./cmd/rpdata-as2storyimport
## 2. Distribute ## 2. Distribute
# Use alpine linux # Use alpine linux
FROM alpine:3.13 FROM alpine:3.7
# Install certificates to enable HTTPS GET requests, and tzdata for log imports. # Install certificates to enable HTTPS GET requests, and tzdata for log imports.
RUN apk add --no-cache ca-certificates tzdata RUN apk add --no-cache ca-certificates tzdata
+139
View File
@@ -0,0 +1,139 @@
package main
import (
"flag"
"fmt"
"log"
"strings"
"time"
"git.aiterp.net/rpdata/api/internal/store"
"git.aiterp.net/rpdata/api/models"
"git.aiterp.net/rpdata/api/models/chapters"
"git.aiterp.net/rpdata/api/models/stories"
_ "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([]models.Tag, 0, 8)
for _, tagResult := range tagResults {
if tagResult.PageID == result.ID {
tags = append(tags, models.Tag{Kind: models.TagKind(tagResult.Type), Name: tagResult.Name})
}
}
category := models.StoryCategory(result.Category)
if !category.IsValid() {
log.Println(result.Name, "does not have a valid category:", result.Category)
continue
}
story, err := stories.Add(result.Name, result.Author[5:], category, true, 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 := chapters.Add(story, title, result.Author[5:], result.Source, publishDate, &fictionalDate, models.ChapterCommentModeDisabled)
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"`
}
-270
View File
@@ -1,270 +0,0 @@
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)
}
+41
View File
@@ -0,0 +1,41 @@
package main
import (
"fmt"
"os"
"git.aiterp.net/rpdata/api/internal/store"
"git.aiterp.net/rpdata/api/models/channels"
"git.aiterp.net/rpdata/api/models/logs"
)
func main() {
err := store.Init()
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
logs, err := logs.List(nil)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
added := make(map[string]bool, 1024)
for _, log := range logs {
if added[log.ChannelName] {
continue
}
_, err := channels.Ensure(log.ChannelName, false)
if err != nil {
fmt.Fprintln(os.Stderr, log.ID, err)
continue
}
added[log.ChannelName] = true
fmt.Println(log.ChannelName, "ensured")
}
}
+3
View File
@@ -0,0 +1,3 @@
characters.json
characters.cson
debug
+55
View File
@@ -0,0 +1,55 @@
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
}
+62
View File
@@ -0,0 +1,62 @@
package main
import (
"flag"
"log"
"os"
"git.aiterp.net/rpdata/api/internal/store"
"git.aiterp.net/rpdata/api/models/characters"
)
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 := characters.Add(info.Nicks[0], info.Name, info.ShortName, info.Author, "")
if err != nil {
log.Println(info.Nicks[0], "failed to insert:", err)
charsFailed++
char, err = characters.FindNick(info.Nicks[0])
if err != nil {
continue
}
} else {
log.Println(info.Nicks[0], "added")
charsAdded++
}
for _, alt := range info.Nicks[1:] {
char, err = characters.AddNick(char, 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)
}
+2
View File
@@ -0,0 +1,2 @@
logs
debug
+85
View File
@@ -0,0 +1,85 @@
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
}
+110
View File
@@ -0,0 +1,110 @@
package main
import (
"fmt"
"log"
"os"
"path"
"strings"
"time"
"git.aiterp.net/rpdata/api/models/posts"
"git.aiterp.net/rpdata/api/internal/store"
"git.aiterp.net/rpdata/api/models/logs"
)
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 := logs.Add(date, channel, title, event, "", false)
if err != nil {
log.Println(err)
continue
}
_, err = posts.Add(l, 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 = posts.Add(l, 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 = posts.Add(l, postTime, strings.ToLower(line.Verb), prefixReplacer.Replace(line.Args[0]), line.Text)
if err != nil {
log.Println(err)
}
}
l, err = logs.UpdateCharacters(l, nil)
if err != nil {
log.Println(err)
}
fmt.Println(l.ID, "completed")
}
}
-318
View File
@@ -1,318 +0,0 @@
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!")
}
}
}
+34 -45
View File
@@ -3,64 +3,46 @@ package main
import ( import (
"context" "context"
"fmt" "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" "log"
"net/http" "net/http"
"runtime/debug" "runtime/debug"
"strings"
"git.aiterp.net/rpdata/api/graph2"
"git.aiterp.net/rpdata/api/internal/auth"
"git.aiterp.net/rpdata/api/internal/loader"
"git.aiterp.net/rpdata/api/internal/store"
"git.aiterp.net/rpdata/api/models"
"git.aiterp.net/rpdata/api/models/changes"
"git.aiterp.net/rpdata/api/models/logs"
"github.com/99designs/gqlgen/handler"
) )
func main() { func main() {
cfg := config.Global() err := store.Init()
db, err := database.Init(cfg.Database)
if err != nil { if err != nil {
log.Fatalln("Failed to init db:", err) log.Fatalln("Failed to init store:", 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("/", handler.Playground("RPData API", "/graphql"))
http.Handle("/graphql", queryHandler(serviceBundle)) http.Handle("/graphql", queryHandler())
http.Handle("/metrics", promhttp.Handler())
go func() { go func() {
err := serviceBundle.Logs.RefreshAllLogCharacters(context.Background()) err := logs.RunFullUpdate()
if err != nil { if err != nil {
log.Println("Refresh Characters:", err) log.Println(err)
} }
log.Println("Characters updated")
}() }()
go logListedChanges(serviceBundle.Changes) go logListedChanges()
log.Fatal(http.ListenAndServe(":8081", nil)) log.Fatal(http.ListenAndServe(":8081", nil))
} }
func logListedChanges(changes *services.ChangeService) { func logListedChanges() {
sub := changes.Subscribe(context.Background(), models.ChangeFilter{}) sub := changes.Subscribe(context.Background(), nil, true)
for change := range sub { for change := range sub {
log.Printf("Change: Author=%#+v Model=%#+v Op=%#+v", change.Author, change.Model, change.Op) log.Printf("Change: Author=%#+v Model=%#+v Op=%#+v", change.Author, change.Model, change.Op)
@@ -89,11 +71,9 @@ func logListedChanges(changes *services.ChangeService) {
log.Println("Change subscription closed.") log.Println("Change subscription closed.")
} }
func queryHandler(services *services.Bundle) http.HandlerFunc { func queryHandler() http.HandlerFunc {
handler := handler.GraphQL( handler := handler.GraphQL(
graph2.New(services), graph2.New(),
handler.UploadMaxSize(10485760),
handler.UploadMaxMemory(1048576),
handler.RecoverFunc(func(ctx context.Context, err interface{}) error { handler.RecoverFunc(func(ctx context.Context, err interface{}) error {
log.Println(err) log.Println(err)
log.Println(string(debug.Stack())) log.Println(string(debug.Stack()))
@@ -101,11 +81,20 @@ func queryHandler(services *services.Bundle) http.HandlerFunc {
return fmt.Errorf("The server failed to serve your request due to an internal error, it has been logged") return fmt.Errorf("The server failed to serve your request due to an internal error, it has been logged")
}), }),
handler.ComplexityLimit(300), handler.ComplexityLimit(300),
handler.RequestMiddleware(instrumentation.RequestMiddleware()),
handler.ResolverMiddleware(instrumentation.ResolverMiddleware()),
) )
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
r = services.Auth.RequestWithToken(r) l := loader.New()
r = r.WithContext(l.ToContext(r.Context()))
// >_>
if strings.HasPrefix(r.Header.Get("Authorization"), "Bearer of the curse") {
w.Header().Set("X-Emerald-Herald", "Seek souls. Larger, more powerful souls.")
w.Header().Add("X-Emerald-Herald", "Seek the king, that is the only way.")
w.Header().Add("X-Emerald-Herald", "Lest this land swallow you whole... As it has so many others.")
}
r = auth.RequestWithToken(r)
handler.ServeHTTP(w, r) handler.ServeHTTP(w, r)
} }
+77
View File
@@ -0,0 +1,77 @@
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/models/files"
)
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 := files.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 := files.Insert(info.Name, "wiki", info.Mime, info.User, ts, info.Size, info.URL)
if err != nil {
log.Println("Skipping", info.Name, "error:", err)
continue
}
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"`
}
-43
View File
@@ -1,43 +0,0 @@
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
}
}
-135
View File
@@ -1,135 +0,0 @@
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
}
-107
View File
@@ -1,107 +0,0 @@
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)
}
-147
View File
@@ -1,147 +0,0 @@
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
}
-262
View File
@@ -1,262 +0,0 @@
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)
}
-144
View File
@@ -1,144 +0,0 @@
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")
}
}
-276
View File
@@ -1,276 +0,0 @@
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
}
-105
View File
@@ -1,105 +0,0 @@
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
}
-75
View File
@@ -1,75 +0,0 @@
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)
}
-215
View File
@@ -1,215 +0,0 @@
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
}
-362
View File
@@ -1,362 +0,0 @@
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))
}
-224
View File
@@ -1,224 +0,0 @@
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
}
-78
View File
@@ -1,78 +0,0 @@
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
}
-52
View File
@@ -1,52 +0,0 @@
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
}
-180
View File
@@ -1,180 +0,0 @@
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
}
-104
View File
@@ -1,104 +0,0 @@
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
}
-144
View File
@@ -1,144 +0,0 @@
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
}
-234
View File
@@ -1,234 +0,0 @@
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
}
-114
View File
@@ -1,114 +0,0 @@
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
}
-107
View File
@@ -1,107 +0,0 @@
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()
}
-82
View File
@@ -1,82 +0,0 @@
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)
}
-249
View File
@@ -1,249 +0,0 @@
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()
}
@@ -1,17 +0,0 @@
-- +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
@@ -1,9 +0,0 @@
-- +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
@@ -1,9 +0,0 @@
-- +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
@@ -1,9 +0,0 @@
-- +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
@@ -1,12 +0,0 @@
-- +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
@@ -1,9 +0,0 @@
-- +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
@@ -1,15 +0,0 @@
-- +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
@@ -1,9 +0,0 @@
-- +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
@@ -1,15 +0,0 @@
-- +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
@@ -1,18 +0,0 @@
-- +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
@@ -1,9 +0,0 @@
-- +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
@@ -1,9 +0,0 @@
-- +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
@@ -1,9 +0,0 @@
-- +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
@@ -1,15 +0,0 @@
-- +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
@@ -1,9 +0,0 @@
-- +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
@@ -1,20 +0,0 @@
-- +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
@@ -1,9 +0,0 @@
-- +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
@@ -1,9 +0,0 @@
-- +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
@@ -1,9 +0,0 @@
-- +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
@@ -1,9 +0,0 @@
-- +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
@@ -1,20 +0,0 @@
-- +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
@@ -1,9 +0,0 @@
-- +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
@@ -1,20 +0,0 @@
-- +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
@@ -1,9 +0,0 @@
-- +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
@@ -1,19 +0,0 @@
-- +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
@@ -1,20 +0,0 @@
-- +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
@@ -1,9 +0,0 @@
-- +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
@@ -1,9 +0,0 @@
-- +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
@@ -1,9 +0,0 @@
-- +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
@@ -1,9 +0,0 @@
-- +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
@@ -1,9 +0,0 @@
-- +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
@@ -1,9 +0,0 @@
-- +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
@@ -1,9 +0,0 @@
-- +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
@@ -1,14 +0,0 @@
-- +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
@@ -1,13 +0,0 @@
-- +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
@@ -1,9 +0,0 @@
-- +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
@@ -1,9 +0,0 @@
-- +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
-303
View File
@@ -1,303 +0,0 @@
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
}
-4
View File
@@ -1,4 +0,0 @@
//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
-22
View File
@@ -1,22 +0,0 @@
-- 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;
-30
View File
@@ -1,30 +0,0 @@
-- 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;
-36
View File
@@ -1,36 +0,0 @@
-- 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;
-47
View File
@@ -1,47 +0,0 @@
-- 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;
-37
View File
@@ -1,37 +0,0 @@
-- 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
);
-8
View File
@@ -1,8 +0,0 @@
-- 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;
-15
View File
@@ -1,15 +0,0 @@
-- 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;
-44
View File
@@ -1,44 +0,0 @@
-- 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;
-63
View File
@@ -1,63 +0,0 @@
-- 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;
-37
View File
@@ -1,37 +0,0 @@
-- 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;
-62
View File
@@ -1,62 +0,0 @@
-- 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;
-11
View File
@@ -1,11 +0,0 @@
-- 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
);
-250
View File
@@ -1,250 +0,0 @@
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
}
-43
View File
@@ -1,43 +0,0 @@
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)
}
-56
View File
@@ -1,56 +0,0 @@
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
}
-61
View File
@@ -1,61 +0,0 @@
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, " ")
}
-25
View File
@@ -1,25 +0,0 @@
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]))
})
}
}
+25 -16
View File
@@ -3,23 +3,32 @@ module git.aiterp.net/rpdata/api
go 1.12 go 1.12
require ( require (
github.com/99designs/gqlgen v0.9.0 github.com/99designs/gqlgen v0.8.0
github.com/agnivade/levenshtein v1.0.1
github.com/davecgh/go-spew v1.1.1
github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4
github.com/globalsign/mgo v0.0.0-20180403085842-f76e4f9da92e github.com/globalsign/mgo v0.0.0-20180403085842-f76e4f9da92e
github.com/go-ini/ini v1.35.0 // indirect github.com/go-ini/ini v1.35.0
github.com/h2non/filetype v1.0.8 github.com/go-sql-driver/mysql v1.4.0
github.com/kyleconroy/sqlc v1.7.0 github.com/gorilla/websocket v1.4.0
github.com/lib/pq v1.10.0 github.com/graph-gophers/dataloader v0.0.0-20180104184831-78139374585c
github.com/hashicorp/golang-lru v0.5.0
github.com/jmoiron/sqlx v0.0.0-20180614180643-0dae4fefe7c0
github.com/minio/minio-go v0.0.0-20180409193742-3d2d02921f05 github.com/minio/minio-go v0.0.0-20180409193742-3d2d02921f05
github.com/prometheus/client_golang v0.9.3 github.com/mitchellh/go-homedir v0.0.0-20161203194507-b8bc1bf76747
github.com/prometheus/procfs v0.0.0-20190517135640-51af30a78b0e // indirect github.com/opentracing/opentracing-go v1.0.2
github.com/stretchr/testify v1.5.1 github.com/pkg/errors v0.8.1
github.com/tidwall/pretty v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0
github.com/vektah/dataloaden v0.3.0 github.com/sirupsen/logrus v1.0.5
github.com/vektah/gqlparser v1.1.2 github.com/stretchr/testify v1.3.0
go.mongodb.org/mongo-driver v1.0.3 github.com/urfave/cli v1.20.0
golang.org/x/sync v0.0.0-20190423024810-112230192c58 github.com/vektah/gqlparser v1.1.0
gopkg.in/yaml.v2 v2.4.0 golang.org/x/crypto v0.0.0-20180411161317-d6449816ce06
golang.org/x/net v0.0.0-20180416171110-a35a21de978d
golang.org/x/sys v0.0.0-20180416112224-2f57af4873d0
golang.org/x/text v0.3.0
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6
google.golang.org/appengine v1.1.0
gopkg.in/yaml.v2 v2.2.2
) )
replace golang.org/x/net => golang.org/x/net v0.0.0-20180416171110-a35a21de978d
+39 -446
View File
@@ -1,497 +1,90 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/99designs/gqlgen v0.5.1 h1:cRsbpZgX83PrXb0/hj5EkNdmaVN4l/Eii81Z8LCexgY=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/99designs/gqlgen v0.5.1/go.mod h1:KSQDfLlTTGmzlRgLGm6HeKKKo598l5E2svEM6Nz2Jnw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= github.com/99designs/gqlgen v0.8.0 h1:ZBteuSgeeFwn+mztjDqQZQdVa9pqslmPnDeCovcnc0Y=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= github.com/99designs/gqlgen v0.8.0/go.mod h1:st7qHA6ssU3uRZkmv+wzrzgX4srvIqEIdE5iuRW8GhE=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= github.com/99designs/gqlgen v0.8.3 h1:I6bMglXNKkn4KlvkSMzqZw53e1N2FF9Gud4NmsOxqiA=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= github.com/99designs/gqlgen v0.8.3/go.mod h1:aLyJw9xUgdJxZ8EqNQxo2pGFhXXJ/hq8t7J4yn8TgI4=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= github.com/agnivade/levenshtein v1.0.0 h1:q+77q31bLT5jhN3BHKA1276nUEdbz7XjDa0o5dRKcZ0=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= github.com/agnivade/levenshtein v1.0.0/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
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 h1:3oJU7J3FGFmyhn8KHjmVaZCN5hxTr7GxgRue+sxIXdQ=
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= 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/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.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/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 h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 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/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
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 h1:hmrbHva/wKD6X4M7pgHVbg/KfVV1wwgr1NEkQaVD3rU=
github.com/globalsign/mgo v0.0.0-20180403085842-f76e4f9da92e/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= 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-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 h1:D/my3+xOfqZMkJpciRcyqU7XMBUgiZa9qXjZIa8uv2k=
github.com/go-ini/ini v1.35.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= 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-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
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.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/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/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.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/graph-gophers/dataloader v0.0.0-20180104184831-78139374585c h1:94S+uoVVMpQAEOrqGjCDyUdML4dJDkh6aC4MYmXECg4=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/graph-gophers/dataloader v0.0.0-20180104184831-78139374585c/go.mod h1:jk4jk0c5ZISbKaMe8WsVopGB5/15GvGHMdMdPtwlRp4=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
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.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/jmoiron/sqlx v0.0.0-20180614180643-0dae4fefe7c0 h1:5B0uxl2lzNRVkJVg+uGHxWtRt4C0Wjc6kJKo5XYx8xE=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/jmoiron/sqlx v0.0.0-20180614180643-0dae4fefe7c0/go.mod h1:IiEW3SEiiErVyFdH8NTuWjSifiEQKUoyK3LNqr2kCHU=
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.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.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/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 h1:YEyFCqmHcmbcTLqZ7yEAg7VIUat6PuDzGbIctZy888k=
github.com/minio/minio-go v0.0.0-20180409193742-3d2d02921f05/go.mod h1:7guKYtitv8dktvNUGrhzmNlA5wrAABTQXCoesZdFQO8= 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 v0.0.0-20161203194507-b8bc1bf76747 h1:eQox4Rh4ewJF+mqYPxCkmBAirRnPaHEB26UkNuPyjlk=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v0.0.0-20161203194507-b8bc1bf76747/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 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/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2 h1:3jA2P6O1F9UOrWVpwrIo17pu01KWvNWg4X946/Y5Zwg=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/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/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
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.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/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/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/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/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/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/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.0.5/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
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.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.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/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.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 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= 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.2.0/go.mod h1:vxM6NuRlgiR0M6wbVTJeKp9vQIs81ZMfCYO+4yq/jbE=
github.com/vektah/dataloaden v0.3.0 h1:ZfVN2QD6swgvp+tDqdH/OIT/wu3Dhu0cus0k5gIZS84= github.com/vektah/gqlparser v0.0.0-20180831041411-14e83ae06ec1 h1:FYOXUtr3sYR9shto7Q/aQ1B0Onyk77aws9wGOORiz+I=
github.com/vektah/dataloaden v0.3.0/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U= github.com/vektah/gqlparser v0.0.0-20180831041411-14e83ae06ec1/go.mod h1:K4QdSSpS2XiHHwzb18kWh3iBljB8rLC8okGXsnQy3Nc=
github.com/vektah/gqlparser v1.1.0 h1:3668p2gUlO+PiS81x957Rpr3/FPRWG6cxgCXAvTS1hw=
github.com/vektah/gqlparser v1.1.0/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
github.com/vektah/gqlparser v1.1.2 h1:ZsyLGn7/7jDNI+y4SEhI4yAxRChlv15pUHMjijT+e68= github.com/vektah/gqlparser v1.1.2 h1:ZsyLGn7/7jDNI+y4SEhI4yAxRChlv15pUHMjijT+e68=
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= golang.org/x/crypto v0.0.0-20180411161317-d6449816ce06 h1:EOqG0JqGlLr+punVB69jvWCv/ErZKGlC7PMdyHfv+Bc=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= golang.org/x/crypto v0.0.0-20180411161317-d6449816ce06/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= golang.org/x/net v0.0.0-20180404174746-b3c676e531a6/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
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 h1:O2P57H5Cc+d+DJos+iweraI9rmzMYwV+45vkZdDY0Oo=
golang.org/x/net v0.0.0-20180416171110-a35a21de978d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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/sys v0.0.0-20180416112224-2f57af4873d0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
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.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/tools v0.0.0-20180911133044-677d2ff680c1 h1:dzEuQYa6+a3gROnSlgly5ERUm4SZKJt+dh+4iSbO+bI=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180911133044-677d2ff680c1/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6 h1:iZgcI2DDp6zW5v9Z/5+f0NuqoxNdmzg4hivjk2WLXpY=
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-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.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 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-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
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.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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 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= sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67/go.mod h1:L5q+DGLGOQFpo1snNEkLOJT2d1YTW66rWNzatr3He1k=
+4
View File
@@ -0,0 +1,4 @@
#!/bin/sh
echo "# Generated by stiching together the files under schema/ – DO NOT EDIT" >generated.gql
cat schema/root.gql schema/**/*.gql >>generated.gql
+47 -52
View File
@@ -1,12 +1,17 @@
package graph2 package graph2
import ( import (
"git.aiterp.net/rpdata/api/graph2/graphcore" input "git.aiterp.net/rpdata/api/graph2/input"
"git.aiterp.net/rpdata/api/models" changes "git.aiterp.net/rpdata/api/models/changes"
channels "git.aiterp.net/rpdata/api/models/channels"
characters "git.aiterp.net/rpdata/api/models/characters"
files "git.aiterp.net/rpdata/api/models/files"
logs "git.aiterp.net/rpdata/api/models/logs"
posts "git.aiterp.net/rpdata/api/models/posts"
stories "git.aiterp.net/rpdata/api/models/stories"
) )
func complexity() (cr graphcore.ComplexityRoot) { func complexity() (cr ComplexityRoot) {
tinySubListComplexity := 2
subListComplexity := 25 subListComplexity := 25
bigSublistComplexity := 75 bigSublistComplexity := 75
hugeSublistComplexity := 100 hugeSublistComplexity := 100
@@ -17,29 +22,29 @@ func complexity() (cr graphcore.ComplexityRoot) {
cr.Query.Character = func(childComplexity int, id *string, nick *string) int { cr.Query.Character = func(childComplexity int, id *string, nick *string) int {
return childComplexity + findComplexity return childComplexity + findComplexity
} }
cr.Query.Characters = func(childComplexity int, filter *models.CharacterFilter) int { cr.Query.Characters = func(childComplexity int, filter *characters.Filter) int {
return childComplexity + listComplexity return childComplexity + listComplexity
} }
cr.Query.Channel = func(childComplexity int, name string) int { cr.Query.Channel = func(childComplexity int, name string) int {
return childComplexity + findComplexity return childComplexity + findComplexity
} }
cr.Query.Channels = func(childComplexity int, filter *models.ChannelFilter) int { cr.Query.Channels = func(childComplexity int, filter *channels.Filter) int {
return childComplexity + listComplexity return childComplexity + listComplexity
} }
cr.Query.Post = func(childComplexity int, id string) int { cr.Query.Post = func(childComplexity int, id string) int {
return childComplexity + findComplexity return childComplexity + findComplexity
} }
cr.Query.Posts = func(childComplexity int, filter *models.PostFilter) int { cr.Query.Posts = func(childComplexity int, filter *posts.Filter) int {
return childComplexity + listComplexity return childComplexity + listComplexity
} }
cr.Query.UnknownNicks = func(childComplexity int, filter *graphcore.UnknownNicksFilter) int { cr.Query.UnknownNicks = func(childComplexity int, filter *input.UnknownNicksFilter) int {
return childComplexity + listComplexity return childComplexity + listComplexity
} }
cr.Query.Log = func(childComplexity int, id string) int { cr.Query.Log = func(childComplexity int, id string) int {
return childComplexity + findComplexity return childComplexity + findComplexity
} }
cr.Query.Logs = func(childComplexity int, filter *models.LogFilter) int { cr.Query.Logs = func(childComplexity int, filter *logs.Filter) int {
if filter != nil && ((filter.Open != nil && *filter.Open == true) || (filter.Limit <= 10)) { if filter != nil && filter.Open != nil && *filter.Open == true {
return childComplexity + findComplexity return childComplexity + findComplexity
} }
@@ -51,111 +56,107 @@ func complexity() (cr graphcore.ComplexityRoot) {
cr.Query.Comment = func(childComplexity int, id string) int { cr.Query.Comment = func(childComplexity int, id string) int {
return childComplexity + findComplexity return childComplexity + findComplexity
} }
cr.Query.Tags = func(childComplexity int, filter *graphcore.TagFilter) int { cr.Query.Tags = func(childComplexity int) int {
return childComplexity + listComplexity return childComplexity + listComplexity
} }
cr.Query.Story = func(childComplexity int, id string) int { cr.Query.Story = func(childComplexity int, id string) int {
return childComplexity + findComplexity return childComplexity + findComplexity
} }
cr.Query.Stories = func(childComplexity int, filter *models.StoryFilter) int { cr.Query.Stories = func(childComplexity int, filter *stories.Filter) int {
if filter != nil && filter.Limit <= 10 {
return childComplexity + findComplexity
}
return childComplexity + listComplexity return childComplexity + listComplexity
} }
cr.Query.File = func(childComplexity int, id string) int { cr.Query.File = func(childComplexity int, id string) int {
return childComplexity + findComplexity return childComplexity + findComplexity
} }
cr.Query.Files = func(childComplexity int, filter *models.FileFilter) int { cr.Query.Files = func(childComplexity int, filter *files.Filter) int {
return childComplexity + listComplexity return childComplexity + listComplexity
} }
cr.Query.Changes = func(childComplexity int, filter *models.ChangeFilter) int { cr.Query.Changes = func(childComplexity int, filter *changes.Filter) int {
return childComplexity + listComplexity return childComplexity + listComplexity
} }
cr.Query.Token = func(childComplexity int) int { cr.Query.Token = func(childComplexity int) int {
return childComplexity + findComplexity return childComplexity + findComplexity
} }
cr.Mutation.AddStory = func(childComplexity int, input graphcore.StoryAddInput) int { cr.Mutation.AddStory = func(childComplexity int, input input.StoryAddInput) int {
return childComplexity + mutationComplexity return childComplexity + mutationComplexity
} }
cr.Mutation.AddStoryTag = func(childComplexity int, input graphcore.StoryTagAddInput) int { cr.Mutation.AddStoryTag = func(childComplexity int, input input.StoryTagAddInput) int {
return childComplexity + mutationComplexity return childComplexity + mutationComplexity
} }
cr.Mutation.RemoveStoryTag = func(childComplexity int, input graphcore.StoryTagRemoveInput) int { cr.Mutation.RemoveStoryTag = func(childComplexity int, input input.StoryTagRemoveInput) int {
return childComplexity + mutationComplexity return childComplexity + mutationComplexity
} }
cr.Mutation.EditStory = func(childComplexity int, input graphcore.StoryEditInput) int { cr.Mutation.EditStory = func(childComplexity int, input input.StoryEditInput) int {
return childComplexity + mutationComplexity return childComplexity + mutationComplexity
} }
cr.Mutation.RemoveStory = func(childComplexity int, input graphcore.StoryRemoveInput) int { cr.Mutation.RemoveStory = func(childComplexity int, input input.StoryRemoveInput) int {
return childComplexity + mutationComplexity return childComplexity + mutationComplexity
} }
cr.Mutation.AddChapter = func(childComplexity int, input graphcore.ChapterAddInput) int { cr.Mutation.AddChapter = func(childComplexity int, input input.ChapterAddInput) int {
return childComplexity + mutationComplexity return childComplexity + mutationComplexity
} }
cr.Mutation.EditChapter = func(childComplexity int, input graphcore.ChapterEditInput) int { cr.Mutation.EditChapter = func(childComplexity int, input input.ChapterEditInput) int {
return childComplexity + mutationComplexity return childComplexity + mutationComplexity
} }
cr.Mutation.MoveChapter = func(childComplexity int, input graphcore.ChapterMoveInput) int { cr.Mutation.MoveChapter = func(childComplexity int, input input.ChapterMoveInput) int {
return childComplexity + mutationComplexity return childComplexity + mutationComplexity
} }
cr.Mutation.RemoveChapter = func(childComplexity int, input graphcore.ChapterRemoveInput) int { cr.Mutation.RemoveChapter = func(childComplexity int, input input.ChapterRemoveInput) int {
return childComplexity + mutationComplexity return childComplexity + mutationComplexity
} }
cr.Mutation.AddComment = func(childComplexity int, input graphcore.CommentAddInput) int { cr.Mutation.AddComment = func(childComplexity int, input input.CommentAddInput) int {
return childComplexity + mutationComplexity return childComplexity + mutationComplexity
} }
cr.Mutation.EditComment = func(childComplexity int, input graphcore.CommentEditInput) int { cr.Mutation.EditComment = func(childComplexity int, input input.CommentEditInput) int {
return childComplexity + mutationComplexity return childComplexity + mutationComplexity
} }
cr.Mutation.RemoveComment = func(childComplexity int, input graphcore.CommentRemoveInput) int { cr.Mutation.RemoveComment = func(childComplexity int, input input.CommentRemoveInput) int {
return childComplexity + mutationComplexity return childComplexity + mutationComplexity
} }
cr.Mutation.AddLog = func(childComplexity int, input graphcore.LogAddInput) int { cr.Mutation.AddLog = func(childComplexity int, input input.LogAddInput) int {
return childComplexity + mutationComplexity return childComplexity + mutationComplexity
} }
cr.Mutation.ImportLog = func(childComplexity int, input graphcore.LogImportInput) int { cr.Mutation.ImportLog = func(childComplexity int, input input.LogImportInput) int {
return childComplexity + mutationComplexity return childComplexity + mutationComplexity
} }
cr.Mutation.EditLog = func(childComplexity int, input graphcore.LogEditInput) int { cr.Mutation.EditLog = func(childComplexity int, input input.LogEditInput) int {
return childComplexity + mutationComplexity return childComplexity + mutationComplexity
} }
cr.Mutation.RemoveLog = func(childComplexity int, input graphcore.LogRemoveInput) int { cr.Mutation.RemoveLog = func(childComplexity int, input input.LogRemoveInput) int {
return childComplexity + mutationComplexity return childComplexity + mutationComplexity
} }
cr.Mutation.AddPost = func(childComplexity int, input graphcore.PostAddInput) int { cr.Mutation.AddPost = func(childComplexity int, input input.PostAddInput) int {
return childComplexity + mutationComplexity return childComplexity + mutationComplexity
} }
cr.Mutation.EditPost = func(childComplexity int, input graphcore.PostEditInput) int { cr.Mutation.EditPost = func(childComplexity int, input input.PostEditInput) int {
return childComplexity + mutationComplexity return childComplexity + mutationComplexity
} }
cr.Mutation.MovePost = func(childComplexity int, input graphcore.PostMoveInput) int { cr.Mutation.MovePost = func(childComplexity int, input input.PostMoveInput) int {
return childComplexity + mutationComplexity return childComplexity + mutationComplexity
} }
cr.Mutation.RemovePost = func(childComplexity int, input graphcore.PostRemoveInput) int { cr.Mutation.RemovePost = func(childComplexity int, input input.PostRemoveInput) int {
return childComplexity + mutationComplexity return childComplexity + mutationComplexity
} }
cr.Mutation.AddCharacter = func(childComplexity int, input graphcore.CharacterAddInput) int { cr.Mutation.AddCharacter = func(childComplexity int, input input.CharacterAddInput) int {
return childComplexity + mutationComplexity return childComplexity + mutationComplexity
} }
cr.Mutation.AddCharacterNick = func(childComplexity int, input graphcore.CharacterNickInput) int { cr.Mutation.AddCharacterNick = func(childComplexity int, input input.CharacterNickInput) int {
return childComplexity + mutationComplexity return childComplexity + mutationComplexity
} }
cr.Mutation.RemoveCharacterNick = func(childComplexity int, input graphcore.CharacterNickInput) int { cr.Mutation.RemoveCharacterNick = func(childComplexity int, input input.CharacterNickInput) int {
return childComplexity + mutationComplexity return childComplexity + mutationComplexity
} }
cr.Mutation.EditCharacter = func(childComplexity int, input graphcore.CharacterEditInput) int { cr.Mutation.EditCharacter = func(childComplexity int, input input.CharacterEditInput) int {
return childComplexity + mutationComplexity return childComplexity + mutationComplexity
} }
cr.Mutation.RemoveCharacter = func(childComplexity int, input graphcore.CharacterRemoveInput) int { cr.Mutation.RemoveCharacter = func(childComplexity int, input input.CharacterRemoveInput) int {
return childComplexity + mutationComplexity return childComplexity + mutationComplexity
} }
cr.Mutation.AddChannel = func(childComplexity int, input graphcore.ChannelAddInput) int { cr.Mutation.AddChannel = func(childComplexity int, input input.ChannelAddInput) int {
return childComplexity + mutationComplexity return childComplexity + mutationComplexity
} }
cr.Mutation.EditChannel = func(childComplexity int, input graphcore.ChannelEditInput) int { cr.Mutation.EditChannel = func(childComplexity int, input input.ChannelEditInput) int {
return childComplexity + mutationComplexity return childComplexity + mutationComplexity
} }
@@ -165,18 +166,12 @@ func complexity() (cr graphcore.ComplexityRoot) {
cr.Log.Posts = func(childComplexity int, kinds []string) int { cr.Log.Posts = func(childComplexity int, kinds []string) int {
return childComplexity + hugeSublistComplexity return childComplexity + hugeSublistComplexity
} }
cr.Log.NextLogs = func(childComplexity int) int {
return childComplexity + (subListComplexity / 2)
}
cr.Log.PrevLogs = func(childComplexity int) int {
return childComplexity + (subListComplexity / 2)
}
cr.Story.Chapters = func(childComplexity int) int { cr.Story.Chapters = func(childComplexity int) int {
return childComplexity + bigSublistComplexity return childComplexity + bigSublistComplexity
} }
cr.Story.Tags = func(childComplexity int) int { cr.Story.Tags = func(childComplexity int) int {
return childComplexity + tinySubListComplexity return childComplexity + subListComplexity
} }
cr.Chapter.Comments = func(childComplexity int, limit *int) int { cr.Chapter.Comments = func(childComplexity int, limit *int) int {
+23 -16
View File
@@ -1,14 +1,12 @@
schema: schema: generated.gql
- schema/root.gql
- schema/types/*.gql
exec: exec:
filename: graphcore/exec_gen.go filename: generated.go
package: graphcore package: graph2
model: model:
filename: graphcore/input_gen.go filename: input/generated.go
package: graphcore package: input
models: models:
Tag: Tag:
@@ -20,41 +18,48 @@ models:
Character: Character:
model: git.aiterp.net/rpdata/api/models.Character model: git.aiterp.net/rpdata/api/models.Character
CharactersFilter: CharactersFilter:
model: git.aiterp.net/rpdata/api/models.CharacterFilter model: git.aiterp.net/rpdata/api/models/characters.Filter
Channel: Channel:
model: git.aiterp.net/rpdata/api/models.Channel model: git.aiterp.net/rpdata/api/models.Channel
ChannelsFilter: ChannelsFilter:
model: git.aiterp.net/rpdata/api/models.ChannelFilter model: git.aiterp.net/rpdata/api/models/channels.Filter
UnknownNick: UnknownNick:
model: git.aiterp.net/rpdata/api/models.UnknownNick model: git.aiterp.net/rpdata/api/models.UnknownNick
Post: Post:
model: git.aiterp.net/rpdata/api/models.Post model: git.aiterp.net/rpdata/api/models.Post
PostsFilter: PostsFilter:
model: git.aiterp.net/rpdata/api/models.PostFilter model: git.aiterp.net/rpdata/api/models/posts.Filter
Log: Log:
model: git.aiterp.net/rpdata/api/models.Log model: git.aiterp.net/rpdata/api/models.Log
LogsFilter: LogsFilter:
model: git.aiterp.net/rpdata/api/models.LogFilter model: git.aiterp.net/rpdata/api/models/logs.Filter
LogImporter: LogImporter:
model: git.aiterp.net/rpdata/api/models.LogImporter model: git.aiterp.net/rpdata/api/models.LogImporter
LogSuggestion:
model: git.aiterp.net/rpdata/api/models.LogSuggestion
Comment: Comment:
model: git.aiterp.net/rpdata/api/models.Comment model: git.aiterp.net/rpdata/api/models.Comment
fields:
fictionalDate:
resolver: true
ChapterCommentMode: ChapterCommentMode:
model: git.aiterp.net/rpdata/api/models.ChapterCommentMode model: git.aiterp.net/rpdata/api/models.ChapterCommentMode
Chapter: Chapter:
model: git.aiterp.net/rpdata/api/models.Chapter model: git.aiterp.net/rpdata/api/models.Chapter
fields:
fictionalDate:
resolver: true
Story: Story:
model: git.aiterp.net/rpdata/api/models.Story model: git.aiterp.net/rpdata/api/models.Story
fields:
fictionalDate:
resolver: true
StoryCategory: StoryCategory:
model: git.aiterp.net/rpdata/api/models.StoryCategory model: git.aiterp.net/rpdata/api/models.StoryCategory
StoriesFilter: StoriesFilter:
model: git.aiterp.net/rpdata/api/models.StoryFilter model: git.aiterp.net/rpdata/api/models/stories.Filter
File: File:
model: git.aiterp.net/rpdata/api/models.File model: git.aiterp.net/rpdata/api/models.File
FilesFilter: FilesFilter:
model: git.aiterp.net/rpdata/api/models.FileFilter model: git.aiterp.net/rpdata/api/models/files.Filter
Change: Change:
model: git.aiterp.net/rpdata/api/models.Change model: git.aiterp.net/rpdata/api/models.Change
ChangeModel: ChangeModel:
@@ -64,8 +69,10 @@ models:
ChangeKeyInput: # It's the same as ChangeKey ChangeKeyInput: # It's the same as ChangeKey
model: git.aiterp.net/rpdata/api/models.ChangeKey model: git.aiterp.net/rpdata/api/models.ChangeKey
ChangesFilter: ChangesFilter:
model: git.aiterp.net/rpdata/api/models.ChangeFilter model: git.aiterp.net/rpdata/api/models/changes.Filter
Token: Token:
model: git.aiterp.net/rpdata/api/models.Token model: git.aiterp.net/rpdata/api/models.Token
User: User:
model: git.aiterp.net/rpdata/api/models.User model: git.aiterp.net/rpdata/api/models.User
Date:
model: git.aiterp.net/rpdata/api/models/scalars.Date
+24 -27
View File
@@ -1,59 +1,56 @@
package graph2 package graph2
import ( import (
"git.aiterp.net/rpdata/api/graph2/graphcore" "git.aiterp.net/rpdata/api/graph2/queries"
"git.aiterp.net/rpdata/api/graph2/resolvers"
"git.aiterp.net/rpdata/api/graph2/types" "git.aiterp.net/rpdata/api/graph2/types"
"git.aiterp.net/rpdata/api/services"
graphql "github.com/99designs/gqlgen/graphql" graphql "github.com/99designs/gqlgen/graphql"
) )
//go:generate ./combine.sh
//go:generate go run github.com/99designs/gqlgen -v //go:generate go run github.com/99designs/gqlgen -v
// New creates a new GraphQL schema. // New creates a new GraphQL schema.
func New(s *services.Bundle) graphql.ExecutableSchema { func New() graphql.ExecutableSchema {
return graphcore.NewExecutableSchema(graphcore.Config{ return NewExecutableSchema(Config{
Resolvers: &rootResolver{s}, Resolvers: &rootResolver{},
Complexity: complexity(), Complexity: complexity(),
}) })
} }
type rootResolver struct { type rootResolver struct{}
s *services.Bundle
func (r *rootResolver) Query() QueryResolver {
return &queries.Resolver
} }
func (r *rootResolver) Query() graphcore.QueryResolver { func (r *rootResolver) Mutation() MutationResolver {
return resolvers.QueryResolver(r.s) return queries.MutationResolver
} }
func (r *rootResolver) Mutation() graphcore.MutationResolver { func (r *rootResolver) Subscription() SubscriptionResolver {
return resolvers.MutationResolver(r.s) return queries.SubscriptionResolver
} }
func (r *rootResolver) Subscription() graphcore.SubscriptionResolver { func (r *rootResolver) Log() LogResolver {
return resolvers.SubscriptionResolver(r.s) return &types.LogResolver
} }
func (r *rootResolver) Log() graphcore.LogResolver { func (r *rootResolver) Comment() CommentResolver {
return types.LogResolver(r.s) return &types.CommentResolver
} }
func (r *rootResolver) Comment() graphcore.CommentResolver { func (r *rootResolver) Chapter() ChapterResolver {
return types.CommentResolver(r.s) return &types.ChapterResolver
} }
func (r *rootResolver) Chapter() graphcore.ChapterResolver { func (r *rootResolver) Story() StoryResolver {
return types.ChapterResolver(r.s) return &types.StoryResolver
} }
func (r *rootResolver) Story() graphcore.StoryResolver { func (r *rootResolver) Change() ChangeResolver {
return types.StoryResolver(r.s)
}
func (r *rootResolver) Change() graphcore.ChangeResolver {
return &types.ChangeResolver return &types.ChangeResolver
} }
func (r *rootResolver) Token() graphcore.TokenResolver { func (r *rootResolver) Token() TokenResolver {
return types.TokenResolver(r.s) return &types.TokenResolver
} }
+25
View File
@@ -0,0 +1,25 @@
package queries
import (
"context"
"errors"
"git.aiterp.net/rpdata/api/models"
"git.aiterp.net/rpdata/api/models/changes"
)
/// Queries
func (r *resolver) Changes(ctx context.Context, filter *changes.Filter) ([]models.Change, error) {
return changes.List(filter)
}
/// Subscriptions
func (r *subscriptionResolver) Changes(ctx context.Context, keys []models.ChangeKey) (<-chan *models.Change, error) {
if len(keys) == 0 {
return nil, errors.New("At least one key is required for a subscription")
}
return changes.Subscribe(ctx, keys, false), nil
}

Some files were not shown because too many files have changed in this diff Show More