diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..28a804d
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..8b44d93
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/watcherTasks.xml b/.idea/watcherTasks.xml
new file mode 100644
index 0000000..97ad6d2
--- /dev/null
+++ b/.idea/watcherTasks.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
new file mode 100644
index 0000000..a0bea08
--- /dev/null
+++ b/.idea/workspace.xml
@@ -0,0 +1,586 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ delete(l.cache
+ dataloade
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/database/mongodb/characters.go b/database/mongodb/characters.go
new file mode 100644
index 0000000..bdec5e4
--- /dev/null
+++ b/database/mongodb/characters.go
@@ -0,0 +1,217 @@
+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
+}
+
+func newCharacterRepository(db *mgo.Database) (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{
+ 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) 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).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) {
+ nextId, err := r.cidCounter.Increment(1)
+ if err != nil {
+ return nil, err
+ }
+ character.ID = "C" + strconv.Itoa(nextId)
+
+ 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)
+}
diff --git a/database/mongodb/db.go b/database/mongodb/db.go
index 841a638..8e42b20 100644
--- a/database/mongodb/db.go
+++ b/database/mongodb/db.go
@@ -2,6 +2,7 @@ package mongodb
import (
"fmt"
+ "github.com/globalsign/mgo/bson"
"time"
"git.aiterp.net/rpdata/api/internal/config"
@@ -31,8 +32,15 @@ func Init(cfg config.Database) (bundle *repositories.Bundle, closeFn func() erro
db := session.DB(cfg.Db)
+ characters, err := newCharacterRepository(db)
+ if err != nil {
+ session.Close()
+ return nil, nil, err
+ }
+
bundle = &repositories.Bundle{
- Tags: newTagRepository(db),
+ Characters: characters,
+ Tags: newTagRepository(db),
}
closeFn = func() error {
@@ -42,3 +50,46 @@ func Init(cfg config.Database) (bundle *repositories.Bundle, closeFn func() erro
return
}
+
+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) 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
+}
diff --git a/database/mongodb/tags.go b/database/mongodb/tags.go
index 11a8c08..9b4ade9 100644
--- a/database/mongodb/tags.go
+++ b/database/mongodb/tags.go
@@ -21,7 +21,7 @@ func newTagRepository(db *mgo.Database) repositories.TagRepository {
}
}
-func (r *tagRepository) Find(ctx context.Context, kind, name string) (*models.Tag, error) {
+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 {
@@ -30,7 +30,13 @@ func (r *tagRepository) Find(ctx context.Context, kind, name string) (*models.Ta
return nil, repositories.ErrNotFound
}
- return tags[0], nil
+ 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) {
diff --git a/go.mod b/go.mod
index 7f8e726..06168ce 100644
--- a/go.mod
+++ b/go.mod
@@ -4,41 +4,35 @@ go 1.12
require (
github.com/99designs/gqlgen v0.9.0
- github.com/agnivade/levenshtein v1.0.1
github.com/beorn7/perks v1.0.0 // indirect
- github.com/davecgh/go-spew v1.1.1
github.com/dgrijalva/jwt-go v3.2.0+incompatible
- github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4
github.com/globalsign/mgo v0.0.0-20180403085842-f76e4f9da92e
- github.com/go-ini/ini v1.35.0
+ github.com/go-ini/ini v1.35.0 // indirect
github.com/go-sql-driver/mysql v1.4.0
github.com/golang/protobuf v1.3.1 // indirect
github.com/google/go-cmp v0.3.0 // indirect
- github.com/gorilla/websocket v1.4.0
+ github.com/gorilla/websocket v1.4.0 // indirect
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/lib/pq v1.1.1 // indirect
+ github.com/mattn/go-sqlite3 v1.10.0 // indirect
github.com/minio/minio-go v0.0.0-20180409193742-3d2d02921f05
- github.com/mitchellh/go-homedir v0.0.0-20161203194507-b8bc1bf76747
- github.com/opentracing/opentracing-go v1.0.2
- github.com/pkg/errors v0.8.1
- github.com/pmezard/go-difflib v1.0.0
+ github.com/mitchellh/go-homedir v0.0.0-20161203194507-b8bc1bf76747 // indirect
github.com/prometheus/client_golang v0.9.1
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 // indirect
github.com/prometheus/common v0.4.0 // indirect
github.com/prometheus/procfs v0.0.0-20190517135640-51af30a78b0e // indirect
- github.com/sirupsen/logrus v1.2.0
+ github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a // indirect
github.com/stretchr/testify v1.3.0
github.com/tidwall/pretty v1.0.0 // indirect
- github.com/urfave/cli v1.20.0
+ github.com/vektah/dataloaden v0.3.0
github.com/vektah/gqlparser v1.1.2
go.mongodb.org/mongo-driver v1.0.3
- golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
- golang.org/x/net v0.0.0-20190514140710-3ec191127204
- golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a
- golang.org/x/text v0.3.0
- golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd
- google.golang.org/appengine v1.1.0
+ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 // indirect
+ golang.org/x/net v0.0.0-20190514140710-3ec191127204 // indirect
+ golang.org/x/text v0.3.0 // indirect
+ google.golang.org/appengine v1.1.0 // indirect
+ gopkg.in/ini.v1 v1.42.0 // indirect
gopkg.in/yaml.v2 v2.2.2
)
diff --git a/go.sum b/go.sum
index 8cffd58..54a1259 100644
--- a/go.sum
+++ b/go.sum
@@ -1,14 +1,5 @@
-github.com/99designs/gqlgen v0.5.1 h1:cRsbpZgX83PrXb0/hj5EkNdmaVN4l/Eii81Z8LCexgY=
-github.com/99designs/gqlgen v0.5.1/go.mod h1:KSQDfLlTTGmzlRgLGm6HeKKKo598l5E2svEM6Nz2Jnw=
-github.com/99designs/gqlgen v0.8.0 h1:ZBteuSgeeFwn+mztjDqQZQdVa9pqslmPnDeCovcnc0Y=
-github.com/99designs/gqlgen v0.8.0/go.mod h1:st7qHA6ssU3uRZkmv+wzrzgX4srvIqEIdE5iuRW8GhE=
-github.com/99designs/gqlgen v0.8.3 h1:I6bMglXNKkn4KlvkSMzqZw53e1N2FF9Gud4NmsOxqiA=
-github.com/99designs/gqlgen v0.8.3/go.mod h1:aLyJw9xUgdJxZ8EqNQxo2pGFhXXJ/hq8t7J4yn8TgI4=
github.com/99designs/gqlgen v0.9.0 h1:g1arBPML74Vqv0L3Q+TqIhGXLspV+2MYtRLkBxuZrlE=
github.com/99designs/gqlgen v0.9.0/go.mod h1:HrrG7ic9EgLPsULxsZh/Ti+p0HNWgR3XRuvnD0pb5KY=
-github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
-github.com/agnivade/levenshtein v1.0.0 h1:q+77q31bLT5jhN3BHKA1276nUEdbz7XjDa0o5dRKcZ0=
-github.com/agnivade/levenshtein v1.0.0/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
github.com/agnivade/levenshtein v1.0.1 h1:3oJU7J3FGFmyhn8KHjmVaZCN5hxTr7GxgRue+sxIXdQ=
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
@@ -18,14 +9,11 @@ github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo
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/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
-github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
-github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/globalsign/mgo v0.0.0-20180403085842-f76e4f9da92e h1:hmrbHva/wKD6X4M7pgHVbg/KfVV1wwgr1NEkQaVD3rU=
github.com/globalsign/mgo v0.0.0-20180403085842-f76e4f9da92e/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/go-chi/chi v3.3.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
@@ -33,18 +21,20 @@ github.com/go-ini/ini v1.35.0 h1:D/my3+xOfqZMkJpciRcyqU7XMBUgiZa9qXjZIa8uv2k=
github.com/go-ini/ini v1.35.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
-github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
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/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
@@ -56,6 +46,8 @@ github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCO
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/jmoiron/sqlx v0.0.0-20180614180643-0dae4fefe7c0 h1:5B0uxl2lzNRVkJVg+uGHxWtRt4C0Wjc6kJKo5XYx8xE=
github.com/jmoiron/sqlx v0.0.0-20180614180643-0dae4fefe7c0/go.mod h1:IiEW3SEiiErVyFdH8NTuWjSifiEQKUoyK3LNqr2kCHU=
+github.com/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/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
@@ -64,6 +56,10 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
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/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
+github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
+github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
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/minio/minio-go v0.0.0-20180409193742-3d2d02921f05 h1:YEyFCqmHcmbcTLqZ7yEAg7VIUat6PuDzGbIctZy888k=
@@ -72,7 +68,6 @@ github.com/mitchellh/go-homedir v0.0.0-20161203194507-b8bc1bf76747 h1:eQox4Rh4ew
github.com/mitchellh/go-homedir v0.0.0-20161203194507-b8bc1bf76747/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
-github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2 h1:3jA2P6O1F9UOrWVpwrIo17pu01KWvNWg4X946/Y5Zwg=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
@@ -84,28 +79,24 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1 h1:K47Rk0v/fkEfwfQet2KWhscE0cJzjgCCDBG2KHZoVno=
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 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY=
-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/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
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/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
-github.com/sirupsen/logrus v1.0.5/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
-github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
+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 v0.0.0-20190330032615-68dc04aab96a h1:pa8hGb/2YqsZKovtsgrwcDH1RZhVbTKCjLp47XpqCDs=
+github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
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/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
@@ -116,52 +107,42 @@ github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
-github.com/vektah/dataloaden v0.2.0/go.mod h1:vxM6NuRlgiR0M6wbVTJeKp9vQIs81ZMfCYO+4yq/jbE=
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U=
-github.com/vektah/gqlparser v0.0.0-20180831041411-14e83ae06ec1 h1:FYOXUtr3sYR9shto7Q/aQ1B0Onyk77aws9wGOORiz+I=
-github.com/vektah/gqlparser v0.0.0-20180831041411-14e83ae06ec1/go.mod h1:K4QdSSpS2XiHHwzb18kWh3iBljB8rLC8okGXsnQy3Nc=
-github.com/vektah/gqlparser v1.1.0 h1:3668p2gUlO+PiS81x957Rpr3/FPRWG6cxgCXAvTS1hw=
-github.com/vektah/gqlparser v1.1.0/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
+github.com/vektah/dataloaden v0.3.0 h1:ZfVN2QD6swgvp+tDqdH/OIT/wu3Dhu0cus0k5gIZS84=
+github.com/vektah/dataloaden v0.3.0/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U=
github.com/vektah/gqlparser v1.1.2 h1:ZsyLGn7/7jDNI+y4SEhI4yAxRChlv15pUHMjijT+e68=
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
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=
-golang.org/x/crypto v0.0.0-20180411161317-d6449816ce06 h1:EOqG0JqGlLr+punVB69jvWCv/ErZKGlC7PMdyHfv+Bc=
-golang.org/x/crypto v0.0.0-20180411161317-d6449816ce06/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1Nwz0AtPflrblfvUudpo+I=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/net v0.0.0-20180404174746-b3c676e531a6/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180416171110-a35a21de978d h1:O2P57H5Cc+d+DJos+iweraI9rmzMYwV+45vkZdDY0Oo=
golang.org/x/net v0.0.0-20180416171110-a35a21de978d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181114220301-adae6a3d119a h1:gOpx8G595UYyvj8UK4+OFyY4rx037g3fmfhe5SasG3U=
-golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190514140710-3ec191127204 h1:4yG6GqBtw9C+UrLp6s2wtSniayy/Vd/3F7ffLE427XI=
-golang.org/x/net v0.0.0-20190514140710-3ec191127204/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
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-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20180416112224-2f57af4873d0/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-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5 h1:mzjBh+S5frKOsOBobWIMAbXavqjmgO17k/2puhcFR94=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/tools v0.0.0-20180911133044-677d2ff680c1 h1:dzEuQYa6+a3gROnSlgly5ERUm4SZKJt+dh+4iSbO+bI=
-golang.org/x/tools v0.0.0-20180911133044-677d2ff680c1/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6 h1:iZgcI2DDp6zW5v9Z/5+f0NuqoxNdmzg4hivjk2WLXpY=
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd h1:oMEQDWVXVNpceQoVd1JN3CQ7LYJJzs5qWqZIUcxXHHw=
golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/ini.v1 v1.42.0 h1:7N3gPTt50s8GuLortA00n8AqRTk75qOP98+mTPpgzRk=
+gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
diff --git a/graph2/complexity.go b/graph2/complexity.go
index 436879b..b3f4e1c 100644
--- a/graph2/complexity.go
+++ b/graph2/complexity.go
@@ -2,9 +2,9 @@ package graph2
import (
"git.aiterp.net/rpdata/api/graph2/graphcore"
+ "git.aiterp.net/rpdata/api/models"
"git.aiterp.net/rpdata/api/models/changes"
"git.aiterp.net/rpdata/api/models/channels"
- "git.aiterp.net/rpdata/api/models/characters"
"git.aiterp.net/rpdata/api/models/files"
"git.aiterp.net/rpdata/api/models/logs"
"git.aiterp.net/rpdata/api/models/posts"
@@ -22,7 +22,7 @@ func complexity() (cr graphcore.ComplexityRoot) {
cr.Query.Character = func(childComplexity int, id *string, nick *string) int {
return childComplexity + findComplexity
}
- cr.Query.Characters = func(childComplexity int, filter *characters.Filter) int {
+ cr.Query.Characters = func(childComplexity int, filter *models.CharacterFilter) int {
return childComplexity + listComplexity
}
cr.Query.Channel = func(childComplexity int, name string) int {
diff --git a/graph2/gqlgen.yml b/graph2/gqlgen.yml
index 526145a..a28d8f5 100644
--- a/graph2/gqlgen.yml
+++ b/graph2/gqlgen.yml
@@ -20,7 +20,7 @@ models:
Character:
model: git.aiterp.net/rpdata/api/models.Character
CharactersFilter:
- model: git.aiterp.net/rpdata/api/models/characters.Filter
+ model: git.aiterp.net/rpdata/api/models.CharacterFilter
Channel:
model: git.aiterp.net/rpdata/api/models.Channel
ChannelsFilter:
diff --git a/graph2/graph.go b/graph2/graph.go
index 3ebefc9..04eb156 100644
--- a/graph2/graph.go
+++ b/graph2/graph.go
@@ -27,15 +27,15 @@ func (r *rootResolver) Query() graphcore.QueryResolver {
}
func (r *rootResolver) Mutation() graphcore.MutationResolver {
- return resolvers.MutationResolver
+ return resolvers.MutationResolver(r.s)
}
func (r *rootResolver) Subscription() graphcore.SubscriptionResolver {
- return resolvers.SubscriptionResolver
+ return resolvers.SubscriptionResolver(r.s)
}
func (r *rootResolver) Log() graphcore.LogResolver {
- return &types.LogResolver
+ return types.LogResolver(r.s)
}
func (r *rootResolver) Comment() graphcore.CommentResolver {
diff --git a/graph2/resolvers/character.go b/graph2/resolvers/character.go
index 4219770..8a23481 100644
--- a/graph2/resolvers/character.go
+++ b/graph2/resolvers/character.go
@@ -3,69 +3,41 @@ package resolvers
import (
"context"
"errors"
- "strings"
-
"git.aiterp.net/rpdata/api/graph2/graphcore"
- "git.aiterp.net/rpdata/api/internal/auth"
"git.aiterp.net/rpdata/api/models"
- "git.aiterp.net/rpdata/api/models/changekeys"
- "git.aiterp.net/rpdata/api/models/changes"
- "git.aiterp.net/rpdata/api/models/characters"
- "git.aiterp.net/rpdata/api/models/logs"
)
// Queries
func (r *queryResolver) Character(ctx context.Context, id *string, nick *string) (*models.Character, error) {
if id != nil {
- character, err := characters.FindID(*id)
- if err != nil {
- return nil, err
- }
-
- return &character, nil
+ return r.s.Characters.Find(ctx, *id)
} else if nick != nil {
- character, err := characters.FindNick(*nick)
- if err != nil {
- return nil, err
- }
-
- return &character, nil
+ return r.s.Characters.FindNick(ctx, *nick)
} else {
return nil, errors.New("You must specify either an ID or a nick")
}
}
-func (r *queryResolver) Characters(ctx context.Context, filter *characters.Filter) ([]*models.Character, error) {
- characters, err := characters.List(filter)
- if err != nil {
- return nil, err
+func (r *queryResolver) Characters(ctx context.Context, filter *models.CharacterFilter) ([]*models.Character, error) {
+ if filter == nil {
+ filter = &models.CharacterFilter{}
}
- characters2 := make([]*models.Character, len(characters))
- for i := range characters {
- characters2[i] = &characters[i]
- }
-
- return characters2, nil
+ return r.s.Characters.List(ctx, *filter)
}
// Mutations
func (r *mutationResolver) AddCharacter(ctx context.Context, input graphcore.CharacterAddInput) (*models.Character, error) {
- token := auth.TokenFromContext(ctx)
- if !token.Permitted("member", "character.add") {
- return nil, errors.New("You are not permitted to add characters")
- }
- if len(input.Name) < 2 || len(input.Nick) < 2 {
- return nil, errors.New("You need to provide a name and a nick (min length: 2)")
+ author := ""
+ if input.Author != nil {
+ author = *input.Author
}
shortName := ""
if input.ShortName != nil {
shortName = *input.ShortName
- } else {
- shortName = strings.SplitN(input.Name, " ", 2)[0]
}
description := ""
@@ -73,122 +45,21 @@ func (r *mutationResolver) AddCharacter(ctx context.Context, input graphcore.Cha
description = *input.Description
}
- author := token.UserID
- if input.Author != nil && *input.Author != author {
- if !token.Permitted("character.add") {
- return nil, errors.New("You are only permitted to add your own characters")
- }
-
- author = *input.Author
- }
-
- logs.ScheduleFullUpdate()
-
- character, err := characters.Add(input.Nick, input.Name, shortName, author, description)
- if err != nil {
- return nil, errors.New("Adding character failed: " + err.Error())
- }
-
- go changes.Submit("Character", "add", token.UserID, true, changekeys.Listed(character), character)
-
- return &character, nil
+ return r.s.Characters.Create(ctx, input.Nick, input.Name, shortName, author, description)
}
func (r *mutationResolver) AddCharacterNick(ctx context.Context, input graphcore.CharacterNickInput) (*models.Character, error) {
- character, err := characters.FindID(input.ID)
- if err != nil {
- return nil, errors.New("Character not found")
- }
-
- if len(input.Nick) < 2 {
- return nil, errors.New("You need to provide a valid nick (min length: 2)")
- }
-
- token := auth.TokenFromContext(ctx)
- if !token.PermittedUser(character.Author, "member", "character.edit") {
- return nil, errors.New("You are not permitted to edit this character")
- }
-
- logs.ScheduleFullUpdate()
-
- character, err = characters.AddNick(character, input.Nick)
- if err != nil {
- return nil, errors.New("Failed to add nick: " + err.Error())
- }
-
- go logs.ScheduleFullUpdate()
- go changes.Submit("Character", "edit", token.UserID, true, changekeys.Listed(character), character)
-
- return &character, nil
+ return r.s.Characters.AddNick(ctx, input.ID, input.Nick)
}
func (r *mutationResolver) RemoveCharacterNick(ctx context.Context, input graphcore.CharacterNickInput) (*models.Character, error) {
- character, err := characters.FindID(input.ID)
- if err != nil {
- return nil, errors.New("Character not found")
- }
-
- token := auth.TokenFromContext(ctx)
- if !token.PermittedUser(character.Author, "member", "character.edit") {
- return nil, errors.New("You are not permitted to edit this character")
- }
-
- character, err = characters.RemoveNick(character, input.Nick)
- if err != nil {
- return nil, errors.New("Failed to remove nick: " + err.Error())
- }
-
- go logs.ScheduleFullUpdate()
- go changes.Submit("Character", "edit", token.UserID, true, changekeys.Listed(character), character)
-
- return &character, nil
+ return r.s.Characters.RemoveNick(ctx, input.ID, input.Nick)
}
func (r *mutationResolver) EditCharacter(ctx context.Context, input graphcore.CharacterEditInput) (*models.Character, error) {
- character, err := characters.FindID(input.ID)
- if err != nil {
- return nil, errors.New("Character not found")
- }
-
- if input.Name != nil && len(*input.Name) < 2 {
- return nil, errors.New("You need to provide a valid name (min length: 2)")
- }
- if input.ShortName != nil && len(*input.ShortName) < 2 {
- return nil, errors.New("You need to provide a valid short name (min length: 2)")
- }
-
- token := auth.TokenFromContext(ctx)
- if !token.PermittedUser(character.Author, "member", "character.edit") {
- return nil, errors.New("You are not permitted to edit this character")
- }
-
- character, err = characters.Edit(character, input.Name, input.ShortName, input.Description)
- if err != nil {
- return nil, errors.New("Failed to edit character: " + err.Error())
- }
-
- go changes.Submit("Character", "edit", token.UserID, true, changekeys.Listed(character), character)
-
- return &character, nil
+ return r.s.Characters.Update(ctx, input.ID, input.Name, input.ShortName, input.Description)
}
func (r *mutationResolver) RemoveCharacter(ctx context.Context, input graphcore.CharacterRemoveInput) (*models.Character, error) {
- character, err := characters.FindID(input.ID)
- if err != nil {
- return nil, errors.New("Character not found")
- }
-
- token := auth.TokenFromContext(ctx)
- if !token.PermittedUser(character.Author, "member", "character.remove") {
- return nil, errors.New("You are not permitted to remove this character")
- }
-
- character, err = characters.Remove(character)
- if err != nil {
- return nil, errors.New("Failed to remove character: " + err.Error())
- }
-
- go changes.Submit("Character", "remove", token.UserID, true, changekeys.Listed(character), character)
-
- return &character, nil
+ return r.s.Characters.Delete(ctx, input.ID)
}
diff --git a/graph2/resolvers/resolvers.go b/graph2/resolvers/resolvers.go
index abff036..1013355 100644
--- a/graph2/resolvers/resolvers.go
+++ b/graph2/resolvers/resolvers.go
@@ -6,16 +6,20 @@ import (
)
type queryResolver struct{ s *services.Bundle }
-type mutationResolver struct{}
-type subscriptionResolver struct{}
+type mutationResolver struct{ s *services.Bundle }
+type subscriptionResolver struct{ s *services.Bundle }
// QueryResolver has all the queries
func QueryResolver(s *services.Bundle) graphcore.QueryResolver {
return &queryResolver{s: s}
}
-// MutationResolver brings the mutagens.
-var MutationResolver *mutationResolver
+// MutationResolver brings the radioactive goop
+func MutationResolver(s *services.Bundle) graphcore.MutationResolver {
+ return &mutationResolver{s: s}
+}
// SubscriptionResolver has the real-time magic.
-var SubscriptionResolver *subscriptionResolver
+func SubscriptionResolver(s *services.Bundle) graphcore.SubscriptionResolver {
+ return &subscriptionResolver{s: s}
+}
diff --git a/graph2/types/log.go b/graph2/types/log.go
index 9c041f3..5b6802f 100644
--- a/graph2/types/log.go
+++ b/graph2/types/log.go
@@ -3,13 +3,16 @@ package types
import (
"context"
"errors"
+ "git.aiterp.net/rpdata/api/services"
"git.aiterp.net/rpdata/api/internal/loader"
"git.aiterp.net/rpdata/api/models"
"git.aiterp.net/rpdata/api/models/posts"
)
-type logResolver struct{}
+type logResolver struct {
+ characters *services.CharacterService
+}
func (r *logResolver) Channel(ctx context.Context, log *models.Log) (*models.Channel, error) {
loader := loader.FromContext(ctx)
@@ -21,22 +24,13 @@ func (r *logResolver) Channel(ctx context.Context, log *models.Log) (*models.Cha
}
func (r *logResolver) Characters(ctx context.Context, log *models.Log) ([]*models.Character, error) {
- loader := loader.FromContext(ctx)
- if loader == nil {
- return nil, errors.New("no loader")
+ if len(log.CharacterIDs) == 0 {
+ return []*models.Character{}, nil
}
- characters, err := loader.Characters("id", log.CharacterIDs...)
- if err != nil {
- return nil, err
- }
-
- characters2 := make([]*models.Character, len(characters))
- for i := range characters {
- characters2[i] = &characters[i]
- }
-
- return characters2, nil
+ return r.characters.List(ctx, models.CharacterFilter{
+ IDs: log.CharacterIDs,
+ })
}
func (r *logResolver) Posts(ctx context.Context, log *models.Log, kinds []string) ([]*models.Post, error) {
@@ -54,4 +48,6 @@ func (r *logResolver) Posts(ctx context.Context, log *models.Log, kinds []string
}
// LogResolver is a resolver
-var LogResolver logResolver
+func LogResolver(s *services.Bundle) *logResolver {
+ return &logResolver{characters: s.Characters}
+}
diff --git a/internal/auth/permitted.go b/internal/auth/permitted.go
index 833bf22..8e2270a 100644
--- a/internal/auth/permitted.go
+++ b/internal/auth/permitted.go
@@ -2,6 +2,7 @@ package auth
import (
"context"
+ "log"
"reflect"
"git.aiterp.net/rpdata/api/models"
@@ -43,6 +44,8 @@ func CheckPermission(ctx context.Context, op string, obj interface{}) error {
authorized = token.PermittedUser(v.Author, "member", "story."+op)
case *models.User:
authorized = token.Permitted("user." + op)
+ default:
+ log.Panicf("Invalid model %T: %#+v", v, v)
}
if !authorized {
diff --git a/models/changekeys/one.go b/models/changekeys/one.go
index deb674b..eec2cc6 100644
--- a/models/changekeys/one.go
+++ b/models/changekeys/one.go
@@ -26,6 +26,10 @@ func One(object interface{}) models.ChangeKey {
id := ""
v := reflect.ValueOf(object)
+ for v.Kind() == reflect.Ptr {
+ v = v.Elem()
+ }
+
if f := v.FieldByName("ID"); f.Kind() == reflect.String {
id = f.String()
} else if f = v.FieldByName("Name"); f.Kind() == reflect.String {
diff --git a/models/changes/submit.go b/models/changes/submit.go
index b58a272..fb85170 100644
--- a/models/changes/submit.go
+++ b/models/changes/submit.go
@@ -55,34 +55,50 @@ func Submit(model, op, author string, listed bool, keys []models.ChangeKey, obje
switch object := object.(type) {
case models.Log:
change.Logs = append(change.Logs, object)
+ case *models.Log:
+ change.Logs = append(change.Logs, *object)
case []models.Log:
change.Logs = append(change.Logs, object...)
case models.Character:
change.Characters = append(change.Characters, object)
+ case *models.Character:
+ change.Characters = append(change.Characters, *object)
case []models.Character:
change.Characters = append(change.Characters, object...)
case models.Channel:
change.Channels = append(change.Channels, object)
+ case *models.Channel:
+ change.Channels = append(change.Channels, *object)
case []models.Channel:
change.Channels = append(change.Channels, object...)
case models.Post:
change.Posts = append(change.Posts, object)
+ case *models.Post:
+ change.Posts = append(change.Posts, *object)
case []models.Post:
change.Posts = append(change.Posts, object...)
case models.Story:
change.Stories = append(change.Stories, object)
+ case *models.Story:
+ change.Stories = append(change.Stories, *object)
case []models.Story:
change.Stories = append(change.Stories, object...)
case models.Tag:
change.Tags = append(change.Tags, object)
+ case *models.Tag:
+ change.Tags = append(change.Tags, *object)
case []models.Tag:
change.Tags = append(change.Tags, object...)
case models.Chapter:
change.Chapters = append(change.Chapters, object)
+ case *models.Chapter:
+ change.Chapters = append(change.Chapters, *object)
case []models.Chapter:
change.Chapters = append(change.Chapters, object...)
case models.Comment:
change.Comments = append(change.Comments, object)
+ case *models.Comment:
+ change.Comments = append(change.Comments, *object)
case []models.Comment:
change.Comments = append(change.Comments, object...)
default:
diff --git a/models/character.go b/models/character.go
index a8c7d0b..e9de49a 100644
--- a/models/character.go
+++ b/models/character.go
@@ -34,4 +34,21 @@ func (character *Character) HasNick(nick string) bool {
// ChangeObject in GQL.
func (*Character) IsChangeObject() {
panic("this method is a dummy, and so is its caller")
-}
\ No newline at end of file
+}
+
+// CharacterFilter is a filter for character listing.
+type CharacterFilter struct {
+ IDs []string
+ Nicks []string
+ Names []string
+ Author *string
+ Search *string
+ Limit int
+}
+
+// CharacterUpdate is an update for characters.
+type CharacterUpdate struct {
+ Name *string
+ ShortName *string
+ Description *string
+}
diff --git a/models/tag.go b/models/tag.go
index acb651c..e8295c6 100644
--- a/models/tag.go
+++ b/models/tag.go
@@ -63,7 +63,7 @@ func (e TagKind) MarshalGQL(w io.Writer) {
fmt.Fprint(w, "\""+string(e), "\"")
}
-// TagFilter is a filter of tags.
+// TagFilter is a filter for tag listing.
type TagFilter struct {
Kind *TagKind `bson:"kind,omitempty"`
}
diff --git a/models/tags/db.go b/models/tags/db.go
deleted file mode 100644
index f29ea9c..0000000
--- a/models/tags/db.go
+++ /dev/null
@@ -1,14 +0,0 @@
-package tags
-
-import (
- "git.aiterp.net/rpdata/api/internal/store"
- "github.com/globalsign/mgo"
-)
-
-var storyCollection *mgo.Collection
-
-func init() {
- store.HandleInit(func(db *mgo.Database) {
- storyCollection = db.C("story.stories")
- })
-}
diff --git a/models/tags/list.go b/models/tags/list.go
deleted file mode 100644
index b709101..0000000
--- a/models/tags/list.go
+++ /dev/null
@@ -1,26 +0,0 @@
-package tags
-
-import (
- "sort"
- "strings"
-
- "git.aiterp.net/rpdata/api/models"
- "github.com/globalsign/mgo/bson"
-)
-
-// List lists all tags
-func List() ([]models.Tag, error) {
- tags := make([]models.Tag, 0, 64)
- err := storyCollection.Find(bson.M{"listed": true, "tags": bson.M{"$ne": nil}}).Distinct("tags", &tags)
-
- sort.Slice(tags, func(i, j int) bool {
- 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, err
-}
diff --git a/repositories/character.go b/repositories/character.go
new file mode 100644
index 0000000..8ce069f
--- /dev/null
+++ b/repositories/character.go
@@ -0,0 +1,19 @@
+package repositories
+
+import (
+ "context"
+
+ "git.aiterp.net/rpdata/api/models"
+)
+
+// CharacterRepository is an interface for a database using logs.
+type CharacterRepository interface {
+ Find(ctx context.Context, id string) (*models.Character, error)
+ FindNick(ctx context.Context, nick string) (*models.Character, error)
+ List(ctx context.Context, filter models.CharacterFilter) ([]*models.Character, error)
+ Insert(ctx context.Context, character models.Character) (*models.Character, error)
+ Update(ctx context.Context, character models.Character, update models.CharacterUpdate) (*models.Character, error)
+ AddNick(ctx context.Context, character models.Character, nick string) (*models.Character, error)
+ RemoveNick(ctx context.Context, character models.Character, nick string) (*models.Character, error)
+ Delete(ctx context.Context, character models.Character) error
+}
diff --git a/repositories/repository.go b/repositories/repository.go
index 4f06cca..3661dfe 100644
--- a/repositories/repository.go
+++ b/repositories/repository.go
@@ -4,7 +4,8 @@ import "errors"
// A Bundle is a set of repositories.
type Bundle struct {
- Tags TagRepository
+ Characters CharacterRepository
+ Tags TagRepository
}
// ErrNotFound should be returned instead of any database-specific not found error.
diff --git a/repositories/tag.go b/repositories/tag.go
index 24a1e89..8cdd618 100644
--- a/repositories/tag.go
+++ b/repositories/tag.go
@@ -8,6 +8,6 @@ import (
// TagRepository is an interface for a database using logs.
type TagRepository interface {
- Find(ctx context.Context, kind, name string) (*models.Tag, error)
+ Find(ctx context.Context, kind models.TagKind, name string) (*models.Tag, error)
List(ctx context.Context, filter models.TagFilter) ([]*models.Tag, error)
}
diff --git a/services/characters.go b/services/characters.go
new file mode 100644
index 0000000..067d527
--- /dev/null
+++ b/services/characters.go
@@ -0,0 +1,207 @@
+package services
+
+import (
+ "context"
+ "errors"
+ "git.aiterp.net/rpdata/api/internal/auth"
+ "git.aiterp.net/rpdata/api/models"
+ "git.aiterp.net/rpdata/api/models/changekeys"
+ "git.aiterp.net/rpdata/api/models/changes"
+ "git.aiterp.net/rpdata/api/repositories"
+ "git.aiterp.net/rpdata/api/services/loaders"
+ "sort"
+ "strings"
+)
+
+type CharacterService struct {
+ characters repositories.CharacterRepository
+ loader *loaders.CharacterLoader
+}
+
+// Find uses the loader to find the character by the ID.
+func (s *CharacterService) Find(ctx context.Context, id string) (*models.Character, error) {
+ return s.loader.Load(id)
+}
+
+// Find uses the loader to find the character by the ID.
+func (s *CharacterService) FindNick(ctx context.Context, nick string) (*models.Character, error) {
+ return s.characters.FindNick(ctx, nick)
+}
+
+// List lists the characters. If the only filter active is `IDs`, the loader is used to batch together requests.
+func (s *CharacterService) List(ctx context.Context, filter models.CharacterFilter) ([]*models.Character, error) {
+ if len(filter.IDs) > 0 && len(filter.Names) == 0 && len(filter.Nicks) == 0 && filter.Author == nil && filter.Search == nil {
+ characters, errs := s.loader.LoadAll(filter.IDs)
+ if len(characters) == 0 && len(errs) > 0 {
+ if errs[0] == repositories.ErrNotFound {
+ return []*models.Character{}, nil
+ } else {
+ return nil, errs[0]
+ }
+ }
+
+ if err := ctx.Err(); err != nil {
+ return nil, err
+ }
+
+ var badIndices []int
+ for i, character := range characters {
+ if character == nil {
+ badIndices = append(badIndices, i-len(badIndices))
+ }
+ }
+ for _, index := range badIndices {
+ characters = append(characters[:index], characters[index+1:]...)
+ }
+
+ sort.Slice(characters, func(i, j int) bool {
+ return strings.Compare(characters[i].ID, characters[j].ID) < 0
+ })
+
+ return characters, nil
+ }
+
+ return s.characters.List(ctx, filter)
+}
+
+func (s *CharacterService) Create(ctx context.Context, nick, name, shortName, author, description string) (*models.Character, error) {
+ token := auth.TokenFromContext(ctx)
+ if token == nil {
+ return nil, auth.ErrUnauthenticated
+ }
+
+ if name == "" {
+ return nil, errors.New("Name cannot be empty")
+ }
+
+ if author == "" {
+ author = token.UserID
+ }
+
+ if shortName == "" {
+ split := strings.SplitN(name, " ", 2)
+ shortName = split[0]
+ }
+
+ character := &models.Character{
+ Name: name,
+ ShortName: shortName,
+ Author: author,
+ Nicks: []string{nick},
+ Description: description,
+ }
+
+ err := auth.CheckPermission(ctx, "add", character)
+ if err != nil {
+ return nil, err
+ }
+
+ character, err = s.characters.Insert(ctx, *character)
+ if err != nil {
+ return nil, err
+ }
+
+ //TODO: New change submit system
+ go changes.Submit("Character", "add", token.UserID, true, changekeys.Listed(character), character)
+
+ return character, nil
+}
+
+func (s *CharacterService) Update(ctx context.Context, id string, name, shortName, description *string) (*models.Character, error) {
+ character, err := s.characters.Find(ctx, id)
+ if err != nil {
+ return nil, err
+ }
+
+ err = auth.CheckPermission(ctx, "edit", character)
+ if err != nil {
+ return nil, err
+ }
+
+ character, err = s.characters.Update(ctx, *character, models.CharacterUpdate{
+ Name: name,
+ ShortName: shortName,
+ Description: description,
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ s.loader.Clear(character.ID)
+ s.loader.Prime(character.ID, character)
+
+ //TODO: New change submit system
+ token := auth.TokenFromContext(ctx)
+ go changes.Submit("Character", "edit", token.UserID, true, changekeys.Listed(character), character)
+
+ return character, nil
+}
+
+func (s *CharacterService) AddNick(ctx context.Context, id string, nick string) (*models.Character, error) {
+ character, err := s.characters.Find(ctx, id)
+ if err != nil {
+ return nil, err
+ }
+
+ err = auth.CheckPermission(ctx, "edit", character)
+ if err != nil {
+ return nil, err
+ }
+
+ character, err = s.characters.AddNick(ctx, *character, nick)
+ if err != nil {
+ return nil, err
+ }
+
+ //TODO: New change submit system
+ token := auth.TokenFromContext(ctx)
+ go changes.Submit("Character", "edit", token.UserID, true, changekeys.Listed(character), character)
+
+ return character, nil
+}
+
+func (s *CharacterService) RemoveNick(ctx context.Context, id string, nick string) (*models.Character, error) {
+ character, err := s.characters.Find(ctx, id)
+ if err != nil {
+ return nil, err
+ }
+
+ err = auth.CheckPermission(ctx, "edit", character)
+ if err != nil {
+ return nil, err
+ }
+
+ character, err = s.characters.RemoveNick(ctx, *character, nick)
+ if err != nil {
+ return nil, err
+ }
+
+ //TODO: New change submit system
+ token := auth.TokenFromContext(ctx)
+ go changes.Submit("Character", "edit", token.UserID, true, changekeys.Listed(character), character)
+
+ return character, nil
+}
+
+func (s *CharacterService) Delete(ctx context.Context, id string) (*models.Character, error) {
+ character, err := s.characters.Find(ctx, id)
+ if err != nil {
+ return nil, err
+ }
+
+ err = auth.CheckPermission(ctx, "edit", character)
+ if err != nil {
+ return nil, err
+ }
+
+ err = s.characters.Delete(ctx, *character)
+ if err != nil {
+ return nil, err
+ }
+
+ //TODO: New change submit system
+ token := auth.TokenFromContext(ctx)
+ go changes.Submit("Character", "remove", token.UserID, true, changekeys.Listed(character), character)
+
+ return character, nil
+}
diff --git a/services/loaders/characterloader_gen.go b/services/loaders/characterloader_gen.go
new file mode 100644
index 0000000..6a70726
--- /dev/null
+++ b/services/loaders/characterloader_gen.go
@@ -0,0 +1,224 @@
+// Code generated by github.com/vektah/dataloaden, DO NOT EDIT.
+
+package loaders
+
+import (
+ "sync"
+ "time"
+
+ "git.aiterp.net/rpdata/api/models"
+)
+
+// CharacterLoaderConfig captures the config to create a new CharacterLoader
+type CharacterLoaderConfig struct {
+ // Fetch is a method that provides the data for the loader
+ Fetch func(keys []string) ([]*models.Character, []error)
+
+ // Wait is how long wait before sending a batch
+ Wait time.Duration
+
+ // MaxBatch will limit the maximum number of keys to send in one batch, 0 = not limit
+ MaxBatch int
+}
+
+// NewCharacterLoader creates a new CharacterLoader given a fetch, wait, and maxBatch
+func NewCharacterLoader(config CharacterLoaderConfig) *CharacterLoader {
+ return &CharacterLoader{
+ fetch: config.Fetch,
+ wait: config.Wait,
+ maxBatch: config.MaxBatch,
+ }
+}
+
+// CharacterLoader batches and caches requests
+type CharacterLoader struct {
+ // this method provides the data for the loader
+ fetch func(keys []string) ([]*models.Character, []error)
+
+ // how long to done before sending a batch
+ wait time.Duration
+
+ // this will limit the maximum number of keys to send in one batch, 0 = no limit
+ maxBatch int
+
+ // INTERNAL
+
+ // lazily created cache
+ cache map[string]*models.Character
+
+ // the current batch. keys will continue to be collected until timeout is hit,
+ // then everything will be sent to the fetch method and out to the listeners
+ batch *characterLoaderBatch
+
+ // mutex to prevent races
+ mu sync.Mutex
+}
+
+type characterLoaderBatch struct {
+ keys []string
+ data []*models.Character
+ error []error
+ closing bool
+ done chan struct{}
+}
+
+// Load a Character by key, batching and caching will be applied automatically
+func (l *CharacterLoader) Load(key string) (*models.Character, error) {
+ return l.LoadThunk(key)()
+}
+
+// LoadThunk returns a function that when called will block waiting for a Character.
+// This method should be used if you want one goroutine to make requests to many
+// different data loaders without blocking until the thunk is called.
+func (l *CharacterLoader) LoadThunk(key string) func() (*models.Character, error) {
+ l.mu.Lock()
+ if it, ok := l.cache[key]; ok {
+ l.mu.Unlock()
+ return func() (*models.Character, error) {
+ return it, nil
+ }
+ }
+ if l.batch == nil {
+ l.batch = &characterLoaderBatch{done: make(chan struct{})}
+ }
+ batch := l.batch
+ pos := batch.keyIndex(l, key)
+ l.mu.Unlock()
+
+ return func() (*models.Character, error) {
+ <-batch.done
+
+ var data *models.Character
+ if pos < len(batch.data) {
+ data = batch.data[pos]
+ }
+
+ var err error
+ // its convenient to be able to return a single error for everything
+ if len(batch.error) == 1 {
+ err = batch.error[0]
+ } else if batch.error != nil {
+ err = batch.error[pos]
+ }
+
+ if err == nil {
+ l.mu.Lock()
+ l.unsafeSet(key, data)
+ l.mu.Unlock()
+ }
+
+ return data, err
+ }
+}
+
+// LoadAll fetches many keys at once. It will be broken into appropriate sized
+// sub batches depending on how the loader is configured
+func (l *CharacterLoader) LoadAll(keys []string) ([]*models.Character, []error) {
+ results := make([]func() (*models.Character, error), len(keys))
+
+ for i, key := range keys {
+ results[i] = l.LoadThunk(key)
+ }
+
+ characters := make([]*models.Character, len(keys))
+ errors := make([]error, len(keys))
+ for i, thunk := range results {
+ characters[i], errors[i] = thunk()
+ }
+ return characters, errors
+}
+
+// LoadAllThunk returns a function that when called will block waiting for a Characters.
+// This method should be used if you want one goroutine to make requests to many
+// different data loaders without blocking until the thunk is called.
+func (l *CharacterLoader) LoadAllThunk(keys []string) func() ([]*models.Character, []error) {
+ results := make([]func() (*models.Character, error), len(keys))
+ for i, key := range keys {
+ results[i] = l.LoadThunk(key)
+ }
+ return func() ([]*models.Character, []error) {
+ characters := make([]*models.Character, len(keys))
+ errors := make([]error, len(keys))
+ for i, thunk := range results {
+ characters[i], errors[i] = thunk()
+ }
+ return characters, errors
+ }
+}
+
+// Prime the cache with the provided key and value. If the key already exists, no change is made
+// and false is returned.
+// (To forcefully prime the cache, clear the key first with loader.clear(key).prime(key, value).)
+func (l *CharacterLoader) Prime(key string, value *models.Character) bool {
+ l.mu.Lock()
+ var found bool
+ if _, found = l.cache[key]; !found {
+ // make a copy when writing to the cache, its easy to pass a pointer in from a loop var
+ // and end up with the whole cache pointing to the same value.
+ cpy := *value
+ l.unsafeSet(key, &cpy)
+ }
+ l.mu.Unlock()
+ return !found
+}
+
+// Clear the value at key from the cache, if it exists
+func (l *CharacterLoader) Clear(key string) {
+ l.mu.Lock()
+ delete(l.cache, key)
+ l.mu.Unlock()
+}
+
+func (l *CharacterLoader) unsafeSet(key string, value *models.Character) {
+ if l.cache == nil {
+ l.cache = map[string]*models.Character{}
+ }
+ l.cache[key] = value
+}
+
+// keyIndex will return the location of the key in the batch, if its not found
+// it will add the key to the batch
+func (b *characterLoaderBatch) keyIndex(l *CharacterLoader, key string) int {
+ for i, existingKey := range b.keys {
+ if key == existingKey {
+ return i
+ }
+ }
+
+ pos := len(b.keys)
+ b.keys = append(b.keys, key)
+ if pos == 0 {
+ go b.startTimer(l)
+ }
+
+ if l.maxBatch != 0 && pos >= l.maxBatch-1 {
+ if !b.closing {
+ b.closing = true
+ l.batch = nil
+ go b.end(l)
+ }
+ }
+
+ return pos
+}
+
+func (b *characterLoaderBatch) startTimer(l *CharacterLoader) {
+ time.Sleep(l.wait)
+ l.mu.Lock()
+
+ // we must have hit a batch limit and are already finalizing this batch
+ if b.closing {
+ l.mu.Unlock()
+ return
+ }
+
+ l.batch = nil
+ l.mu.Unlock()
+
+ b.end(l)
+}
+
+func (b *characterLoaderBatch) end(l *CharacterLoader) {
+ b.data, b.error = l.fetch(b.keys)
+ close(b.done)
+}
diff --git a/services/loaders/loaders.go b/services/loaders/loaders.go
new file mode 100644
index 0000000..3dde766
--- /dev/null
+++ b/services/loaders/loaders.go
@@ -0,0 +1,53 @@
+package loaders
+
+import (
+ "context"
+ "git.aiterp.net/rpdata/api/models"
+ "git.aiterp.net/rpdata/api/repositories"
+ "time"
+)
+
+//go:generate go run github.com/vektah/dataloaden CharacterLoader string *git.aiterp.net/rpdata/api/models.Character
+
+// CharacterLoaderFromRepository creates a new CharacterLoader
+func CharacterLoaderFromRepository(repo repositories.CharacterRepository) *CharacterLoader {
+ return &CharacterLoader{
+ wait: time.Millisecond * 1,
+ maxBatch: 100,
+ fetch: func(keys []string) ([]*models.Character, []error) {
+ timeout, cancel := context.WithTimeout(context.Background(), time.Second*15)
+ defer cancel()
+
+ characters, err := repo.List(timeout, models.CharacterFilter{
+ IDs: keys,
+ })
+ if err != nil {
+ errs := make([]error, len(keys))
+ for i := range errs {
+ errs[i] = err
+ }
+
+ return nil, errs
+ }
+
+ charMap := make(map[string]*models.Character, len(keys))
+
+ for _, character := range characters {
+ charMap[character.ID] = character
+ }
+
+ results := make([]*models.Character, len(keys))
+ errs := make([]error, len(keys))
+
+ for i, key := range keys {
+ if character, ok := charMap[key]; ok {
+ results[i] = character
+ } else {
+ errs[i] = repositories.ErrNotFound
+ }
+ }
+
+ return results, errs
+ },
+ }
+}
diff --git a/services/services.go b/services/services.go
index e7df004..45c7f3f 100644
--- a/services/services.go
+++ b/services/services.go
@@ -1,10 +1,14 @@
package services
-import "git.aiterp.net/rpdata/api/repositories"
+import (
+ "git.aiterp.net/rpdata/api/repositories"
+ "git.aiterp.net/rpdata/api/services/loaders"
+)
-// A Bundle contains all services, like a bean bag in the more caffeinated language family.
+// A Bundle contains all services.
type Bundle struct {
- Tags *TagService
+ Tags *TagService
+ Characters *CharacterService
}
// NewBundle creates a new bundle.
@@ -12,6 +16,10 @@ func NewBundle(repos *repositories.Bundle) *Bundle {
bundle := &Bundle{}
bundle.Tags = &TagService{tags: repos.Tags}
+ bundle.Characters = &CharacterService{
+ characters: repos.Characters,
+ loader: loaders.CharacterLoaderFromRepository(repos.Characters),
+ }
return bundle
}
diff --git a/services/tags.go b/services/tags.go
index b6bb737..ab62605 100644
--- a/services/tags.go
+++ b/services/tags.go
@@ -13,7 +13,7 @@ type TagService struct {
}
// FindTag finds one tag.
-func (s *TagService) FindTag(ctx context.Context, source, id string) (*models.Tag, error) {
+func (s *TagService) FindTag(ctx context.Context, source models.TagKind, id string) (*models.Tag, error) {
return s.tags.Find(ctx, source, id)
}
diff --git a/tools.go b/tools.go
new file mode 100644
index 0000000..4bad26f
--- /dev/null
+++ b/tools.go
@@ -0,0 +1,7 @@
+//+build tools
+
+package main
+
+import (
+ _ "github.com/vektah/dataloaden"
+)