@@ -49,6 +49,16 @@ models:
|
||||
model: git.aiterp.net/rpdata/api/models.File
|
||||
FilesFilter:
|
||||
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:
|
||||
model: git.aiterp.net/rpdata/api/models.Token
|
||||
User:
|
||||
|
||||
@@ -42,6 +42,10 @@ func (r *rootResolver) File() FileResolver {
|
||||
return &types.FileResolver
|
||||
}
|
||||
|
||||
func (r *rootResolver) Change() ChangeResolver {
|
||||
return &types.ChangeResolver
|
||||
}
|
||||
|
||||
func (r *rootResolver) Token() TokenResolver {
|
||||
return &types.TokenResolver
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -54,6 +54,10 @@ type Query {
|
||||
files(filter: FilesFilter): [File!]!
|
||||
|
||||
|
||||
# Find changes
|
||||
changes(filter: ChangesFilter): [Change!]!
|
||||
|
||||
|
||||
# Get information about the token, useful for debugging.
|
||||
token: Token!
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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()))
|
||||
}
|
||||
+5
-5
@@ -5,7 +5,7 @@ import "time"
|
||||
// Change represents a change in the rpdata history through the API.
|
||||
type Change struct {
|
||||
ID string `bson:"_id"`
|
||||
Model string `bson:"model"`
|
||||
Model ChangeModel `bson:"model"`
|
||||
Op string `bson:"op"`
|
||||
Author string `bson:"author"`
|
||||
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.
|
||||
type ChangeKey struct {
|
||||
Model string `bson:"model"`
|
||||
ID string `bson:"id"`
|
||||
Model ChangeModel `bson:"model"`
|
||||
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))
|
||||
|
||||
for _, log := range change.Logs {
|
||||
|
||||
@@ -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.
|
||||
func All(model string) models.ChangeKey {
|
||||
return models.ChangeKey{Model: model, ID: "*"}
|
||||
return models.ChangeKey{Model: models.ChangeModel(model), ID: "*"}
|
||||
}
|
||||
|
||||
@@ -9,5 +9,5 @@ func Listed(objects ...interface{}) []models.ChangeKey {
|
||||
return nil
|
||||
}
|
||||
|
||||
return append(keys, All(keys[0].Model))
|
||||
return append(keys, All(keys[0].Model.String()))
|
||||
}
|
||||
|
||||
@@ -34,5 +34,5 @@ func One(object interface{}) models.ChangeKey {
|
||||
panic("Unsupported model")
|
||||
}
|
||||
|
||||
return models.ChangeKey{Model: model, ID: id}
|
||||
return models.ChangeKey{Model: models.ChangeModel(model), ID: id}
|
||||
}
|
||||
|
||||
@@ -6,12 +6,21 @@ import (
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/internal/store"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"github.com/globalsign/mgo"
|
||||
"github.com/globalsign/mgo/bson"
|
||||
)
|
||||
|
||||
var collection *mgo.Collection
|
||||
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() {
|
||||
store.HandleInit(func(db *mgo.Database) {
|
||||
collection = db.C("common.changes")
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -18,9 +18,31 @@ func Submit(model, op, author string, listed bool, keys []models.ChangeKey, obje
|
||||
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{
|
||||
ID: "Change_" + strconv.Itoa(id),
|
||||
Model: model,
|
||||
Model: models.ChangeModel(model),
|
||||
Date: time.Now(),
|
||||
Op: op,
|
||||
Author: author,
|
||||
|
||||
Reference in New Issue
Block a user