Generated
+5
@@ -0,0 +1,5 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||
</state>
|
||||
</component>
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="GoNilness" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
</profile>
|
||||
</component>
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
"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"
|
||||
"git.aiterp.net/rpdata/api/services"
|
||||
"github.com/99designs/gqlgen/handler"
|
||||
@@ -52,13 +51,13 @@ func main() {
|
||||
log.Println("Characters updated")
|
||||
}()
|
||||
|
||||
go logListedChanges()
|
||||
go logListedChanges(services.Changes)
|
||||
|
||||
log.Fatal(http.ListenAndServe(":8081", nil))
|
||||
}
|
||||
|
||||
func logListedChanges() {
|
||||
sub := changes.Subscribe(context.Background(), nil, true)
|
||||
func logListedChanges(changes *services.ChangeService) {
|
||||
sub := changes.Subscribe(context.Background(), models.ChangeFilter{PassAll: true})
|
||||
|
||||
for change := range sub {
|
||||
log.Printf("Change: Author=%#+v Model=%#+v Op=%#+v", change.Author, change.Model, change.Op)
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
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"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type changeRepository struct {
|
||||
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 len(filter.Keys) > 0 {
|
||||
query["keys"] = bson.M{"$in": filter.Keys}
|
||||
}
|
||||
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).All(&changes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return changes, nil
|
||||
}
|
||||
|
||||
func (r *changeRepository) Insert(ctx context.Context, change models.Change) (*models.Change, error) {
|
||||
next, err := r.idCounter.Increment(1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
change.ID = "Change_" + strconv.Itoa(next)
|
||||
|
||||
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) (repositories.ChangeRepository, error) {
|
||||
collection := db.C("common.changes")
|
||||
|
||||
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{
|
||||
changes: collection,
|
||||
idCounter: newCounter(db, "auto_increment", "Change"),
|
||||
}, nil
|
||||
}
|
||||
@@ -38,8 +38,15 @@ func Init(cfg config.Database) (bundle *repositories.Bundle, closeFn func() erro
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
changes, err := newChangeRepository(db)
|
||||
if err != nil {
|
||||
session.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
bundle = &repositories.Bundle{
|
||||
Characters: characters,
|
||||
Changes: changes,
|
||||
Tags: newTagRepository(db),
|
||||
}
|
||||
|
||||
@@ -73,6 +80,22 @@ func (c *counter) WithName(name string) *counter {
|
||||
}
|
||||
}
|
||||
|
||||
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) Increment(amount int) (int, error) {
|
||||
type counterDoc struct {
|
||||
ID string `bson:"_id"`
|
||||
|
||||
@@ -3,7 +3,6 @@ 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/files"
|
||||
"git.aiterp.net/rpdata/api/models/logs"
|
||||
@@ -71,7 +70,7 @@ func complexity() (cr graphcore.ComplexityRoot) {
|
||||
cr.Query.Files = func(childComplexity int, filter *files.Filter) int {
|
||||
return childComplexity + listComplexity
|
||||
}
|
||||
cr.Query.Changes = func(childComplexity int, filter *changes.Filter) int {
|
||||
cr.Query.Changes = func(childComplexity int, filter *models.ChangeFilter) int {
|
||||
return childComplexity + listComplexity
|
||||
}
|
||||
cr.Query.Token = func(childComplexity int) int {
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@ models:
|
||||
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
|
||||
model: git.aiterp.net/rpdata/api/models.ChangeFilter
|
||||
Token:
|
||||
model: git.aiterp.net/rpdata/api/models.Token
|
||||
User:
|
||||
|
||||
@@ -2,39 +2,25 @@ package resolvers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"git.aiterp.net/rpdata/api/models/changes"
|
||||
)
|
||||
|
||||
/// Queries
|
||||
|
||||
func (r *queryResolver) Changes(ctx context.Context, filter *changes.Filter) ([]*models.Change, error) {
|
||||
changes, err := changes.List(filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func (r *queryResolver) Changes(ctx context.Context, filter *models.ChangeFilter) ([]*models.Change, error) {
|
||||
if filter == nil {
|
||||
filter = &models.ChangeFilter{}
|
||||
}
|
||||
|
||||
changes2 := make([]*models.Change, len(changes))
|
||||
for i := range changes {
|
||||
changes2[i] = &changes[i]
|
||||
}
|
||||
|
||||
return changes2, nil
|
||||
return r.s.Changes.List(ctx, *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")
|
||||
func (r *subscriptionResolver) Changes(ctx context.Context, filter *models.ChangeFilter) (<-chan *models.Change, error) {
|
||||
if filter == nil {
|
||||
filter = &models.ChangeFilter{}
|
||||
}
|
||||
|
||||
keys2 := make([]models.ChangeKey, len(keys))
|
||||
for i := range keys {
|
||||
keys2[i] = *keys[i]
|
||||
}
|
||||
|
||||
return changes.Subscribe(ctx, keys2, false), nil
|
||||
return r.s.Changes.Subscribe(ctx, *filter), nil
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ type Subscription {
|
||||
"""
|
||||
Changes subscribes to the changes matching the following keys.
|
||||
"""
|
||||
changes(keys: [ChangeKeyInput!]!): Change!
|
||||
changes(filter: ChangesFilter): Change!
|
||||
}
|
||||
|
||||
# A Time represents a RFC3339 encoded date with up to millisecond precision.
|
||||
|
||||
@@ -73,9 +73,12 @@ input ChangesFilter {
|
||||
"The keys to query for."
|
||||
keys: [ChangeKeyInput!]
|
||||
|
||||
"Only show changes by this author"
|
||||
author: String
|
||||
|
||||
"Only show changes more recent than this date."
|
||||
earliestDate: Time
|
||||
|
||||
"Limit the amount of results."
|
||||
"Limit the amount of results. This even goes for subscriptions!"
|
||||
limit: Int
|
||||
}
|
||||
+18
-14
@@ -114,20 +114,24 @@ func CheckToken(tokenString string) (token models.Token, err error) {
|
||||
return models.Token{}, ErrDeletedUser
|
||||
}
|
||||
|
||||
acceptedPermissions := make([]string, 0, 8)
|
||||
for _, permission := range permissions {
|
||||
found := false
|
||||
acceptedPermissions := make([]string, 0, len(user.Permissions))
|
||||
if len(permissions) > 0 {
|
||||
for _, permission := range permissions {
|
||||
found := false
|
||||
|
||||
for _, userPermission := range user.Permissions {
|
||||
if permission == userPermission {
|
||||
found = true
|
||||
break
|
||||
for _, userPermission := range user.Permissions {
|
||||
if permission == userPermission {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
acceptedPermissions = append(acceptedPermissions, permission)
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
acceptedPermissions = append(acceptedPermissions, permission)
|
||||
}
|
||||
} else {
|
||||
acceptedPermissions = append(acceptedPermissions, user.Permissions...)
|
||||
}
|
||||
|
||||
return models.Token{UserID: user.ID, Permissions: acceptedPermissions}, nil
|
||||
@@ -153,10 +157,10 @@ func parseClaims(jwtClaims jwt.Claims) (userid string, permissions []string, err
|
||||
permissions = append(permissions, permission)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(permissions) == 0 {
|
||||
return "", nil, ErrInvalidClaims
|
||||
if len(permissions) == 0 {
|
||||
return "", nil, ErrInvalidClaims
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package notifier
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// A Notifier is a synchronization primitive for waking upp all listeners at once.
|
||||
type Notifier struct {
|
||||
mutex sync.Mutex
|
||||
ch chan struct{}
|
||||
}
|
||||
|
||||
// Broadcast wakes all listeners if there are any.
|
||||
func (notifier *Notifier) Broadcast() {
|
||||
notifier.mutex.Lock()
|
||||
if notifier.ch != nil {
|
||||
close(notifier.ch)
|
||||
notifier.ch = nil
|
||||
}
|
||||
notifier.mutex.Unlock()
|
||||
}
|
||||
|
||||
// C gets the channel that'll close on the next notification.
|
||||
func (notifier *Notifier) C() <-chan struct{} {
|
||||
notifier.mutex.Lock()
|
||||
if notifier.ch == nil {
|
||||
notifier.ch = make(chan struct{})
|
||||
}
|
||||
ch := notifier.ch
|
||||
notifier.mutex.Unlock()
|
||||
|
||||
return ch
|
||||
}
|
||||
|
||||
// Wait waits for the next `Broadcast` call, or the context's termination.
|
||||
func (notifier *Notifier) Wait(ctx context.Context) error {
|
||||
select {
|
||||
case <-notifier.C():
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
+122
-6
@@ -1,6 +1,9 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Change represents a change in the rpdata history through the API.
|
||||
type Change struct {
|
||||
@@ -22,15 +25,55 @@ type Change struct {
|
||||
Comments []Comment `bson:"comments"`
|
||||
}
|
||||
|
||||
// ChangeKey is a key for a change that can be used when subscribing to them.
|
||||
type ChangeKey struct {
|
||||
Model ChangeModel `bson:"model"`
|
||||
ID string `bson:"id"`
|
||||
// AddObject adds the model into the appropriate array.
|
||||
func (change *Change) AddObject(object interface{}) bool {
|
||||
if v := reflect.ValueOf(object); v.Kind() == reflect.Ptr {
|
||||
return change.AddObject(v.Elem().Interface())
|
||||
}
|
||||
|
||||
switch object := object.(type) {
|
||||
case Log:
|
||||
change.Logs = append(change.Logs, object)
|
||||
case []Log:
|
||||
change.Logs = append(change.Logs, object...)
|
||||
case Character:
|
||||
change.Characters = append(change.Characters, object)
|
||||
case []Character:
|
||||
change.Characters = append(change.Characters, object...)
|
||||
case Channel:
|
||||
change.Channels = append(change.Channels, object)
|
||||
case []Channel:
|
||||
change.Channels = append(change.Channels, object...)
|
||||
case Post:
|
||||
change.Posts = append(change.Posts, object)
|
||||
case []Post:
|
||||
change.Posts = append(change.Posts, object...)
|
||||
case Story:
|
||||
change.Stories = append(change.Stories, object)
|
||||
case []Story:
|
||||
change.Stories = append(change.Stories, object...)
|
||||
case Tag:
|
||||
change.Tags = append(change.Tags, object)
|
||||
case []Tag:
|
||||
change.Tags = append(change.Tags, object...)
|
||||
case Chapter:
|
||||
change.Chapters = append(change.Chapters, object)
|
||||
case []Chapter:
|
||||
change.Chapters = append(change.Chapters, object...)
|
||||
case Comment:
|
||||
change.Comments = append(change.Comments, object)
|
||||
case []Comment:
|
||||
change.Comments = append(change.Comments, object...)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// 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)+len(change.Comments))
|
||||
data := make([]interface{}, 0, 8)
|
||||
|
||||
for _, log := range change.Logs {
|
||||
data = append(data, &log)
|
||||
@@ -59,3 +102,76 @@ func (change *Change) Objects() []interface{} {
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func (change *Change) PassesFilter(filter ChangeFilter) bool {
|
||||
if filter.PassAll {
|
||||
return true
|
||||
}
|
||||
|
||||
if filter.Author != nil && change.Author != *filter.Author {
|
||||
return false
|
||||
}
|
||||
|
||||
// For unlisted changes, pass it only if the filter refers to the specific index.
|
||||
if !change.Listed {
|
||||
hasSpecificKey := false
|
||||
|
||||
KeyFindLoop:
|
||||
for _, key := range filter.Keys {
|
||||
if key.ID == "*" {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, changeKey := range change.Keys {
|
||||
if changeKey.Model == key.Model && changeKey.ID == key.ID {
|
||||
hasSpecificKey = true
|
||||
break KeyFindLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !hasSpecificKey {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if filter.EarliestDate != nil && filter.EarliestDate.Before(change.Date) {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(filter.Keys) > 0 {
|
||||
foundKey := false
|
||||
|
||||
KeyFindLoop2:
|
||||
for _, key := range filter.Keys {
|
||||
for _, changeKey := range change.Keys {
|
||||
if changeKey == key {
|
||||
foundKey = true
|
||||
break KeyFindLoop2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !foundKey {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// ChangeKey is a key for a change that can be used when subscribing to them.
|
||||
type ChangeKey struct {
|
||||
Model ChangeModel `bson:"model"`
|
||||
ID string `bson:"id"`
|
||||
}
|
||||
|
||||
// ChangeFilter is a filter for listing changes.
|
||||
type ChangeFilter struct {
|
||||
Keys []ChangeKey
|
||||
EarliestDate *time.Time
|
||||
Author *string
|
||||
Limit *int
|
||||
|
||||
PassAll bool // DO NOT EXPOSE
|
||||
}
|
||||
|
||||
+1
-17
@@ -1,14 +1,11 @@
|
||||
package changes
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/internal/store"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"github.com/globalsign/mgo"
|
||||
"github.com/globalsign/mgo/bson"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var collection *mgo.Collection
|
||||
@@ -24,18 +21,5 @@ func list(query bson.M, limit int) ([]models.Change, error) {
|
||||
func init() {
|
||||
store.HandleInit(func(db *mgo.Database) {
|
||||
collection = db.C("common.changes")
|
||||
|
||||
collection.EnsureIndexKey("date")
|
||||
collection.EnsureIndexKey("author")
|
||||
collection.EnsureIndexKey("keys")
|
||||
|
||||
err := collection.EnsureIndex(mgo.Index{
|
||||
Name: "expiry",
|
||||
Key: []string{"date"},
|
||||
ExpireAfter: time.Hour * 2400, // 100 days
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
)
|
||||
|
||||
type ChangeRepository interface {
|
||||
Find(ctx context.Context, id string) (*models.Change, error)
|
||||
List(ctx context.Context, filter models.ChangeFilter) ([]*models.Change, error)
|
||||
Insert(ctx context.Context, change models.Change) (*models.Change, error)
|
||||
Remove(ctx context.Context, change models.Change) error
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import "errors"
|
||||
// A Bundle is a set of repositories.
|
||||
type Bundle struct {
|
||||
Characters CharacterRepository
|
||||
Changes ChangeRepository
|
||||
Tags TagRepository
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/internal/notifier"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"git.aiterp.net/rpdata/api/repositories"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ChangeService struct {
|
||||
changes repositories.ChangeRepository
|
||||
|
||||
mutex sync.RWMutex
|
||||
buffer []models.Change
|
||||
offset uint64
|
||||
notifier notifier.Notifier
|
||||
submitQueue chan *models.Change
|
||||
loopStarted bool
|
||||
}
|
||||
|
||||
func (s *ChangeService) Find(ctx context.Context, id string) (*models.Change, error) {
|
||||
return s.changes.Find(ctx, id)
|
||||
}
|
||||
|
||||
func (s *ChangeService) List(ctx context.Context, filter models.ChangeFilter) ([]*models.Change, error) {
|
||||
return s.changes.List(ctx, filter)
|
||||
}
|
||||
|
||||
func (s *ChangeService) Submit(ctx context.Context, model models.ChangeModel, op string, listed bool, keys []models.ChangeKey, objects ...interface{}) {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if token == nil {
|
||||
panic("no token!")
|
||||
}
|
||||
|
||||
change := &models.Change{
|
||||
Model: model,
|
||||
Op: op,
|
||||
Author: token.UserID,
|
||||
Listed: listed,
|
||||
Keys: keys,
|
||||
}
|
||||
|
||||
for _, obj := range objects {
|
||||
if !change.AddObject(obj) {
|
||||
log.Printf("Cannot add object of type %T to change", obj)
|
||||
}
|
||||
}
|
||||
|
||||
s.mutex.Lock()
|
||||
if !s.loopStarted {
|
||||
s.loopStarted = true
|
||||
s.submitQueue = make(chan *models.Change, 64)
|
||||
go s.loop()
|
||||
}
|
||||
s.mutex.Unlock()
|
||||
|
||||
s.submitQueue <- change
|
||||
}
|
||||
|
||||
func (s *ChangeService) Subscribe(ctx context.Context, filter models.ChangeFilter) <-chan *models.Change {
|
||||
channel := make(chan *models.Change)
|
||||
|
||||
go func() {
|
||||
defer close(channel)
|
||||
|
||||
s.mutex.RLock()
|
||||
pos := s.offset + uint64(len(s.buffer))
|
||||
slice := make([]models.Change, 32)
|
||||
s.mutex.RUnlock()
|
||||
|
||||
count := 0
|
||||
|
||||
for {
|
||||
s.mutex.RLock()
|
||||
nextPos := s.offset + uint64(len(s.buffer))
|
||||
length := nextPos - pos
|
||||
if length > 0 {
|
||||
index := pos - s.offset
|
||||
|
||||
pos = nextPos
|
||||
copy(slice, s.buffer[index:])
|
||||
}
|
||||
ch := s.notifier.C()
|
||||
s.mutex.RUnlock()
|
||||
|
||||
for _, change := range slice[:length] {
|
||||
if change.PassesFilter(filter) {
|
||||
channel <- &change
|
||||
|
||||
count++
|
||||
if filter.Limit != nil && count == *filter.Limit {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ch:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return channel
|
||||
}
|
||||
|
||||
func (s *ChangeService) loop() {
|
||||
for change := range s.submitQueue {
|
||||
timeout, cancel := context.WithTimeout(context.Background(), time.Second*15)
|
||||
|
||||
change, err := s.changes.Insert(timeout, *change)
|
||||
if err != nil {
|
||||
log.Println("Failed to submit change:")
|
||||
}
|
||||
|
||||
s.mutex.Lock()
|
||||
s.buffer = append(s.buffer, *change)
|
||||
if len(s.buffer) > 16 {
|
||||
copy(s.buffer, s.buffer[8:])
|
||||
s.buffer = s.buffer[:len(s.buffer)-8]
|
||||
s.offset += 8
|
||||
}
|
||||
s.mutex.Unlock()
|
||||
s.notifier.Broadcast()
|
||||
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
+8
-17
@@ -6,7 +6,6 @@ import (
|
||||
"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"
|
||||
@@ -14,8 +13,9 @@ import (
|
||||
)
|
||||
|
||||
type CharacterService struct {
|
||||
characters repositories.CharacterRepository
|
||||
loader *loaders.CharacterLoader
|
||||
characters repositories.CharacterRepository
|
||||
loader *loaders.CharacterLoader
|
||||
changeService *ChangeService
|
||||
}
|
||||
|
||||
// Find uses the loader to find the character by the ID.
|
||||
@@ -101,8 +101,7 @@ func (s *CharacterService) Create(ctx context.Context, nick, name, shortName, au
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//TODO: New change submit system
|
||||
go changes.Submit("Character", "add", token.UserID, true, changekeys.Listed(character), character)
|
||||
s.changeService.Submit(ctx, "Character", "add", true, changekeys.Listed(character), character)
|
||||
|
||||
return character, nil
|
||||
}
|
||||
@@ -130,9 +129,7 @@ func (s *CharacterService) Update(ctx context.Context, id string, name, shortNam
|
||||
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)
|
||||
s.changeService.Submit(ctx, "Character", "edit", true, changekeys.Listed(character), character)
|
||||
|
||||
return character, nil
|
||||
}
|
||||
@@ -153,9 +150,7 @@ func (s *CharacterService) AddNick(ctx context.Context, id string, nick string)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//TODO: New change submit system
|
||||
token := auth.TokenFromContext(ctx)
|
||||
go changes.Submit("Character", "edit", token.UserID, true, changekeys.Listed(character), character)
|
||||
s.changeService.Submit(ctx, "Character", "edit", true, changekeys.Listed(character), character)
|
||||
|
||||
return character, nil
|
||||
}
|
||||
@@ -176,9 +171,7 @@ func (s *CharacterService) RemoveNick(ctx context.Context, id string, nick strin
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//TODO: New change submit system
|
||||
token := auth.TokenFromContext(ctx)
|
||||
go changes.Submit("Character", "edit", token.UserID, true, changekeys.Listed(character), character)
|
||||
s.changeService.Submit(ctx, "Character", "edit", true, changekeys.Listed(character), character)
|
||||
|
||||
return character, nil
|
||||
}
|
||||
@@ -199,9 +192,7 @@ func (s *CharacterService) Delete(ctx context.Context, id string) (*models.Chara
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//TODO: New change submit system
|
||||
token := auth.TokenFromContext(ctx)
|
||||
go changes.Submit("Character", "remove", token.UserID, true, changekeys.Listed(character), character)
|
||||
s.changeService.Submit(ctx, "Character", "remove", true, changekeys.Listed(character), character)
|
||||
|
||||
return character, nil
|
||||
}
|
||||
|
||||
@@ -9,16 +9,21 @@ import (
|
||||
type Bundle struct {
|
||||
Tags *TagService
|
||||
Characters *CharacterService
|
||||
Changes *ChangeService
|
||||
}
|
||||
|
||||
// NewBundle creates a new bundle.
|
||||
func NewBundle(repos *repositories.Bundle) *Bundle {
|
||||
bundle := &Bundle{}
|
||||
|
||||
bundle.Changes = &ChangeService{
|
||||
changes: repos.Changes,
|
||||
}
|
||||
bundle.Tags = &TagService{tags: repos.Tags}
|
||||
bundle.Characters = &CharacterService{
|
||||
characters: repos.Characters,
|
||||
loader: loaders.CharacterLoaderFromRepository(repos.Characters),
|
||||
characters: repos.Characters,
|
||||
loader: loaders.CharacterLoaderFromRepository(repos.Characters),
|
||||
changeService: bundle.Changes,
|
||||
}
|
||||
|
||||
return bundle
|
||||
|
||||
Reference in New Issue
Block a user