Browse Source

graph2: Added change type and query.

1.1
Gisle Aune 6 years ago
parent
commit
ffec008820
  1. 10
      graph2/gqlgen.yml
  2. 4
      graph2/graph.go
  3. 14
      graph2/queries/changes.go
  4. 4
      graph2/schema/root.gql
  5. 81
      graph2/schema/types/Change.gql
  6. 24
      graph2/types/change.go
  7. 59
      models/change-model.go
  8. 8
      models/change.go
  9. 2
      models/changekeys/all.go
  10. 2
      models/changekeys/listed.go
  11. 2
      models/changekeys/one.go
  12. 9
      models/changes/db.go
  13. 42
      models/changes/list.go
  14. 24
      models/changes/submit.go

10
graph2/gqlgen.yml

@ -49,6 +49,16 @@ models:
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/files.Filter model: git.aiterp.net/rpdata/api/models/files.Filter
Change:
model: git.aiterp.net/rpdata/api/models.Change
ChangeModel:
model: git.aiterp.net/rpdata/api/models.ChangeModel
ChangeKey:
model: git.aiterp.net/rpdata/api/models.ChangeKey
ChangeKeyInput: # It's the same as ChangeKey
model: git.aiterp.net/rpdata/api/models.ChangeKey
ChangesFilter:
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:

4
graph2/graph.go

@ -42,6 +42,10 @@ func (r *rootResolver) File() FileResolver {
return &types.FileResolver return &types.FileResolver
} }
func (r *rootResolver) Change() ChangeResolver {
return &types.ChangeResolver
}
func (r *rootResolver) Token() TokenResolver { func (r *rootResolver) Token() TokenResolver {
return &types.TokenResolver return &types.TokenResolver
} }

14
graph2/queries/changes.go

@ -0,0 +1,14 @@
package queries
import (
"context"
"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)
}

4
graph2/schema/root.gql

@ -54,6 +54,10 @@ type Query {
files(filter: FilesFilter): [File!]! files(filter: FilesFilter): [File!]!
# Find changes
changes(filter: ChangesFilter): [Change!]!
# Get information about the token, useful for debugging. # Get information about the token, useful for debugging.
token: Token! token: Token!
} }

81
graph2/schema/types/Change.gql

@ -0,0 +1,81 @@
"""
A change represents a a change in the history.
"""
type Change {
"A unique ID of the change."
id: String!
"The model of the changed object."
model: ChangeModel!
"The operation performed."
op: String!
"The author that performed the change."
author: String!
"Whether the change will be listed without a matching key."
listed: Boolean!
"The keys indexing this change."
keys: [ChangeKey!]!
"The date when the change happened."
date: Date
"The changed objects."
objects: [ChangeObject!]!
}
union ChangeObject = Character | Channel | Log | Post | Story | Tag | Chapter
"""
A change key is a key associated with a change, used for filtering
and subscription. A log post edit has keys for both the log and post,
making it suitable to use it to patch an active log file.
"""
type ChangeKey {
"The model the key is for."
model: ChangeModel!
"The model's id, or * if it's a key indicating a model list should be updated."
id: String!
}
"""
See ChangeKey
"""
input ChangeKeyInput {
"The model the key is for."
model: ChangeModel!
"The model's id, or * if it's a key indicating a model list should be updated."
id: String!
}
"""
A model related to the change.
"""
enum ChangeModel {
Character
Channel
Log
Post
Story
Tag
Chapter
}
"""
Optional filter for the changes query.
"""
input ChangesFilter {
"The keys to query for."
keys: [ChangeKeyInput!]
"Only show changes more recent than this date."
earliestDate: Date
"Limit the amount of results."
limit: Int
}

24
graph2/types/change.go

@ -0,0 +1,24 @@
package types
import (
"context"
"git.aiterp.net/rpdata/api/graph2/input"
"git.aiterp.net/rpdata/api/models"
)
type changeResolver struct{}
func (r *changeResolver) Objects(ctx context.Context, obj *models.Change) ([]input.ChangeObject, error) {
objects := obj.Objects()
results := make([]input.ChangeObject, len(objects))
for i := range objects {
results[i] = objects[i]
}
return results, nil
}
// ChangeResolver is a resolver
var ChangeResolver changeResolver

59
models/change-model.go

@ -0,0 +1,59 @@
package models
import (
"fmt"
"io"
"strconv"
)
// ChangeModel describes a model related to the change.
type ChangeModel string
const (
// ChangeModelCharacter is a value of ChangeModel
ChangeModelCharacter ChangeModel = "Character"
// ChangeModelChannel is a value of ChangeModel
ChangeModelChannel ChangeModel = "Channel"
// ChangeModelLog is a value of ChangeModel
ChangeModelLog ChangeModel = "Log"
// ChangeModelPost is a value of ChangeModel
ChangeModelPost ChangeModel = "Post"
// ChangeModelStory is a value of ChangeModel
ChangeModelStory ChangeModel = "Story"
// ChangeModelTag is a value of ChangeModel
ChangeModelTag ChangeModel = "Tag"
// ChangeModelChapter is a value of ChangeModel
ChangeModelChapter ChangeModel = "Chapter"
)
// IsValid returns true if the underlying string is one of the correct values.
func (e ChangeModel) IsValid() bool {
switch e {
case ChangeModelCharacter, ChangeModelChannel, ChangeModelLog, ChangeModelPost, ChangeModelStory, ChangeModelTag, ChangeModelChapter:
return true
}
return false
}
func (e ChangeModel) String() string {
return string(e)
}
// UnmarshalGQL unmarshals the underlying graphql value.
func (e *ChangeModel) UnmarshalGQL(v interface{}) error {
str, ok := v.(string)
if !ok {
return fmt.Errorf("enums must be strings")
}
*e = ChangeModel(str)
if !e.IsValid() {
return fmt.Errorf("%s is not a valid ChangeModel", str)
}
return nil
}
// MarshalGQL marshals the underlying graphql value.
func (e ChangeModel) MarshalGQL(w io.Writer) {
fmt.Fprint(w, strconv.Quote(e.String()))
}

8
models/change.go

@ -5,7 +5,7 @@ import "time"
// Change represents a change in the rpdata history through the API. // Change represents a change in the rpdata history through the API.
type Change struct { type Change struct {
ID string `bson:"_id"` ID string `bson:"_id"`
Model string `bson:"model"`
Model ChangeModel `bson:"model"`
Op string `bson:"op"` Op string `bson:"op"`
Author string `bson:"author"` Author string `bson:"author"`
Listed bool `bson:"listed"` Listed bool `bson:"listed"`
@ -23,12 +23,12 @@ type Change struct {
// ChangeKey is a key for a change that can be used when subscribing to them. // ChangeKey is a key for a change that can be used when subscribing to them.
type ChangeKey struct { type ChangeKey struct {
Model string `bson:"model"`
Model ChangeModel `bson:"model"`
ID string `bson:"id"` ID string `bson:"id"`
} }
// Data makes a combined, mixed array of all the models stored in this change.
func (change *Change) Data() []interface{} {
// Objects makes a combined, mixed array of all the models stored in this change.
func (change *Change) Objects() []interface{} {
data := make([]interface{}, 0, len(change.Logs)+len(change.Characters)+len(change.Channels)+len(change.Posts)+len(change.Stories)+len(change.Tags)+len(change.Chapters)) data := make([]interface{}, 0, len(change.Logs)+len(change.Characters)+len(change.Channels)+len(change.Posts)+len(change.Stories)+len(change.Tags)+len(change.Chapters))
for _, log := range change.Logs { for _, log := range change.Logs {

2
models/changekeys/all.go

@ -4,5 +4,5 @@ import "git.aiterp.net/rpdata/api/models"
// All makes a changelog that's (model, "*"). It's a helper to standardize it. // All makes a changelog that's (model, "*"). It's a helper to standardize it.
func All(model string) models.ChangeKey { func All(model string) models.ChangeKey {
return models.ChangeKey{Model: model, ID: "*"}
return models.ChangeKey{Model: models.ChangeModel(model), ID: "*"}
} }

2
models/changekeys/listed.go

@ -9,5 +9,5 @@ func Listed(objects ...interface{}) []models.ChangeKey {
return nil return nil
} }
return append(keys, All(keys[0].Model))
return append(keys, All(keys[0].Model.String()))
} }

2
models/changekeys/one.go

@ -34,5 +34,5 @@ func One(object interface{}) models.ChangeKey {
panic("Unsupported model") panic("Unsupported model")
} }
return models.ChangeKey{Model: model, ID: id}
return models.ChangeKey{Model: models.ChangeModel(model), ID: id}
} }

9
models/changes/db.go

@ -6,12 +6,21 @@ import (
"time" "time"
"git.aiterp.net/rpdata/api/internal/store" "git.aiterp.net/rpdata/api/internal/store"
"git.aiterp.net/rpdata/api/models"
"github.com/globalsign/mgo" "github.com/globalsign/mgo"
"github.com/globalsign/mgo/bson"
) )
var collection *mgo.Collection var collection *mgo.Collection
var submitMutex sync.Mutex var submitMutex sync.Mutex
func list(query bson.M, limit int) ([]models.Change, error) {
changes := make([]models.Change, 0, 64)
err := collection.Find(query).Limit(limit).Sort("date").All(&changes)
return changes, err
}
func init() { func init() {
store.HandleInit(func(db *mgo.Database) { store.HandleInit(func(db *mgo.Database) {
collection = db.C("common.changes") collection = db.C("common.changes")

42
models/changes/list.go

@ -0,0 +1,42 @@
package changes
import (
"time"
"git.aiterp.net/rpdata/api/models"
"github.com/globalsign/mgo/bson"
)
// Filter is a filter for changes.List.
type Filter struct {
Keys []models.ChangeKey
EarliestDate *time.Time
Limit *int
}
// List lists changes.
func List(filter *Filter) ([]models.Change, error) {
query := bson.M{}
limit := 0
if filter != nil {
if filter.Limit != nil {
limit = *filter.Limit
}
if filter.Keys != nil {
query["keys"] = bson.M{"$in": filter.Keys}
} else {
query["listed"] = true
}
if filter.EarliestDate != nil {
query["date"] = bson.M{"$gt": *filter.EarliestDate}
}
} else {
query["listed"] = true
limit = 256
}
return list(query, limit)
}

24
models/changes/submit.go

@ -18,9 +18,31 @@ func Submit(model, op, author string, listed bool, keys []models.ChangeKey, obje
return models.Change{}, err return models.Change{}, err
} }
if !models.ChangeModel(model).IsValid() {
panic("Invalid model")
}
// Silently discard * keys on unlisted changes.
if !listed {
keysCopy := make([]models.ChangeKey, len(keys))
copy(keysCopy, keys)
keys = keysCopy
deletes := make([]int, 0, 1)
for i, key := range keys {
if key.ID == "*" {
deletes = append(deletes, i-len(deletes))
}
}
for _, index := range deletes {
keys = append(keys[:index], keys[index+1:]...)
}
}
change := models.Change{ change := models.Change{
ID: "Change_" + strconv.Itoa(id), ID: "Change_" + strconv.Itoa(id),
Model: model,
Model: models.ChangeModel(model),
Date: time.Now(), Date: time.Now(),
Op: op, Op: op,
Author: author, Author: author,

Loading…
Cancel
Save