Gisle Aune
5 years ago
42 changed files with 0 additions and 1619 deletions
-
4cmd/rpdata-server/main.go
-
25models/changes/db.go
-
42models/changes/list.go
-
125models/changes/submit.go
-
90models/changes/subscribe.go
-
40models/chapters/add.go
-
61models/chapters/db.go
-
46models/chapters/edit.go
-
11models/chapters/find.go
-
11models/chapters/list.go
-
28models/chapters/move.go
-
24models/chapters/remove.go
-
27models/characters/add-nick.go
-
48models/characters/add.go
-
55models/characters/db.go
-
36models/characters/edit.go
-
16models/characters/find.go
-
56models/characters/list.go
-
32models/characters/remove-nick.go
-
13models/characters/remove.go
-
35models/comments/add.go
-
66models/comments/db.go
-
44models/comments/edit.go
-
11models/comments/find.go
-
11models/comments/list.go
-
17models/comments/remove.go
-
36models/posts/add-many.go
-
51models/posts/add.go
-
56models/posts/db.go
-
44models/posts/edit.go
-
14models/posts/find.go
-
57models/posts/list.go
-
69models/posts/move.go
-
33models/posts/remove.go
-
29models/stories/add-tag.go
-
30models/stories/add.go
-
59models/stories/db.go
-
45models/stories/edit.go
-
11models/stories/find.go
-
67models/stories/list.go
-
34models/stories/remove-tag.go
-
10models/stories/remove.go
@ -1,25 +0,0 @@ |
|||||
package changes |
|
||||
|
|
||||
import ( |
|
||||
"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 |
|
||||
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") |
|
||||
}) |
|
||||
} |
|
@ -1,42 +0,0 @@ |
|||||
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{"$gte": *filter.EarliestDate} |
|
||||
} |
|
||||
} else { |
|
||||
query["listed"] = true |
|
||||
limit = 256 |
|
||||
} |
|
||||
|
|
||||
return list(query, limit) |
|
||||
} |
|
@ -1,125 +0,0 @@ |
|||||
package changes |
|
||||
|
|
||||
import ( |
|
||||
"log" |
|
||||
"strconv" |
|
||||
"time" |
|
||||
|
|
||||
"git.aiterp.net/rpdata/api/internal/counter" |
|
||||
"git.aiterp.net/rpdata/api/models" |
|
||||
) |
|
||||
|
|
||||
// Submit a change to the database. The objects may be any supported model, or arrays.
|
|
||||
func Submit(model, op, author string, listed bool, keys []models.ChangeKey, objects ...interface{}) (models.Change, error) { |
|
||||
submitMutex.Lock() |
|
||||
defer submitMutex.Unlock() |
|
||||
|
|
||||
id, err := counter.Next("auto_increment", "Change") |
|
||||
if err != nil { |
|
||||
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: models.ChangeModel(model), |
|
||||
Date: time.Now(), |
|
||||
Op: op, |
|
||||
Author: author, |
|
||||
Keys: keys, |
|
||||
Listed: listed, |
|
||||
} |
|
||||
|
|
||||
for _, object := range objects { |
|
||||
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: |
|
||||
for _, obj := range object { |
|
||||
change.Logs = append(change.Logs, &obj) |
|
||||
} |
|
||||
case models.Character: |
|
||||
change.Characters = append(change.Characters, &object) |
|
||||
case *models.Character: |
|
||||
change.Characters = append(change.Characters, object) |
|
||||
case []models.Character: |
|
||||
for _, obj := range object { |
|
||||
change.Characters = append(change.Characters, &obj) |
|
||||
} |
|
||||
case models.Channel: |
|
||||
change.Channels = append(change.Channels, &object) |
|
||||
case *models.Channel: |
|
||||
change.Channels = append(change.Channels, object) |
|
||||
case []models.Channel: |
|
||||
for _, obj := range object { |
|
||||
change.Channels = append(change.Channels, &obj) |
|
||||
} |
|
||||
case models.Post: |
|
||||
change.Posts = append(change.Posts, &object) |
|
||||
case *models.Post: |
|
||||
change.Posts = append(change.Posts, object) |
|
||||
case []models.Post: |
|
||||
for _, obj := range object { |
|
||||
change.Posts = append(change.Posts, &obj) |
|
||||
} |
|
||||
case models.Story: |
|
||||
change.Stories = append(change.Stories, &object) |
|
||||
case *models.Story: |
|
||||
change.Stories = append(change.Stories, object) |
|
||||
case []models.Story: |
|
||||
for _, obj := range object { |
|
||||
change.Stories = append(change.Stories, &obj) |
|
||||
} |
|
||||
case models.Chapter: |
|
||||
change.Chapters = append(change.Chapters, &object) |
|
||||
case *models.Chapter: |
|
||||
change.Chapters = append(change.Chapters, object) |
|
||||
case []models.Chapter: |
|
||||
for _, obj := range object { |
|
||||
change.Chapters = append(change.Chapters, &obj) |
|
||||
} |
|
||||
case models.Comment: |
|
||||
change.Comments = append(change.Comments, &object) |
|
||||
case *models.Comment: |
|
||||
change.Comments = append(change.Comments, object) |
|
||||
case []models.Comment: |
|
||||
for _, obj := range object { |
|
||||
change.Comments = append(change.Comments, &obj) |
|
||||
} |
|
||||
default: |
|
||||
log.Printf("Warning: unrecognized object in change: %#+v", object) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
pushToSubscribers(change) |
|
||||
|
|
||||
err = collection.Insert(&change) |
|
||||
if err != nil { |
|
||||
return models.Change{}, err |
|
||||
} |
|
||||
|
|
||||
return change, nil |
|
||||
} |
|
@ -1,90 +0,0 @@ |
|||||
package changes |
|
||||
|
|
||||
import ( |
|
||||
"context" |
|
||||
"sync" |
|
||||
|
|
||||
"git.aiterp.net/rpdata/api/models" |
|
||||
) |
|
||||
|
|
||||
var subMutex sync.Mutex |
|
||||
var subList []*subscription |
|
||||
|
|
||||
type subscription struct { |
|
||||
Keys map[string]bool |
|
||||
Channel chan<- *models.Change |
|
||||
WildCard bool |
|
||||
} |
|
||||
|
|
||||
// Subscribe subscribes to all changes.
|
|
||||
func Subscribe(ctx context.Context, keys []models.ChangeKey, wildcard bool) <-chan *models.Change { |
|
||||
channel := make(chan *models.Change, 64) |
|
||||
sub := &subscription{ |
|
||||
Keys: make(map[string]bool, len(keys)), |
|
||||
Channel: channel, |
|
||||
WildCard: wildcard, |
|
||||
} |
|
||||
|
|
||||
for _, key := range keys { |
|
||||
sub.Keys[mapKey(key)] = true |
|
||||
} |
|
||||
|
|
||||
subMutex.Lock() |
|
||||
subList = append(subList, sub) |
|
||||
subMutex.Unlock() |
|
||||
|
|
||||
go func() { |
|
||||
<-ctx.Done() |
|
||||
|
|
||||
subMutex.Lock() |
|
||||
for i := range subList { |
|
||||
if subList[i] == sub { |
|
||||
subList = append(subList[:i], subList[i+1:]...) |
|
||||
break |
|
||||
} |
|
||||
} |
|
||||
subMutex.Unlock() |
|
||||
}() |
|
||||
|
|
||||
return channel |
|
||||
} |
|
||||
|
|
||||
func pushToSubscribers(change models.Change) { |
|
||||
keys := make([]string, len(change.Keys)) |
|
||||
for i := range change.Keys { |
|
||||
keys[i] = mapKey(change.Keys[i]) |
|
||||
} |
|
||||
|
|
||||
subMutex.Lock() |
|
||||
SubLoop: |
|
||||
for _, sub := range subList { |
|
||||
changeCopy := change |
|
||||
|
|
||||
if sub.WildCard && change.Listed { |
|
||||
select { |
|
||||
case sub.Channel <- &changeCopy: |
|
||||
default: |
|
||||
} |
|
||||
} else { |
|
||||
for _, key := range keys { |
|
||||
if sub.Keys[key] { |
|
||||
select { |
|
||||
case sub.Channel <- &changeCopy: |
|
||||
default: |
|
||||
} |
|
||||
|
|
||||
continue SubLoop |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
subMutex.Unlock() |
|
||||
} |
|
||||
|
|
||||
func mapKey(ck models.ChangeKey) string { |
|
||||
return ck.Model.String() + "." + ck.ID |
|
||||
} |
|
||||
|
|
||||
func init() { |
|
||||
subList = make([]*subscription, 0, 16) |
|
||||
} |
|
@ -1,40 +0,0 @@ |
|||||
package chapters |
|
||||
|
|
||||
import ( |
|
||||
"time" |
|
||||
|
|
||||
"git.aiterp.net/rpdata/api/models" |
|
||||
"github.com/globalsign/mgo/bson" |
|
||||
) |
|
||||
|
|
||||
// Add adds a new chapter.
|
|
||||
func Add(story models.Story, title, author, source string, createdDate time.Time, finctionalDate *time.Time, commentMode models.ChapterCommentMode) (models.Chapter, error) { |
|
||||
chapter := models.Chapter{ |
|
||||
ID: makeChapterID(), |
|
||||
StoryID: story.ID, |
|
||||
Title: title, |
|
||||
Author: author, |
|
||||
Source: source, |
|
||||
CreatedDate: createdDate, |
|
||||
EditedDate: createdDate, |
|
||||
CommentMode: commentMode, |
|
||||
CommentsLocked: false, |
|
||||
} |
|
||||
|
|
||||
if finctionalDate != nil { |
|
||||
chapter.FictionalDate = *finctionalDate |
|
||||
} |
|
||||
|
|
||||
err := collection.Insert(chapter) |
|
||||
if err != nil { |
|
||||
return models.Chapter{}, err |
|
||||
} |
|
||||
|
|
||||
if createdDate.After(story.UpdatedDate) { |
|
||||
if err := storyCollection.UpdateId(story.ID, bson.M{"$set": bson.M{"updatedDate": createdDate}}); err == nil { |
|
||||
story.UpdatedDate = createdDate |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return chapter, nil |
|
||||
} |
|
@ -1,61 +0,0 @@ |
|||||
package chapters |
|
||||
|
|
||||
import ( |
|
||||
"crypto/rand" |
|
||||
"encoding/binary" |
|
||||
"strconv" |
|
||||
|
|
||||
"git.aiterp.net/rpdata/api/internal/store" |
|
||||
"git.aiterp.net/rpdata/api/models" |
|
||||
"github.com/globalsign/mgo" |
|
||||
) |
|
||||
|
|
||||
var collection *mgo.Collection |
|
||||
var storyCollection *mgo.Collection |
|
||||
|
|
||||
func find(query interface{}) (models.Chapter, error) { |
|
||||
chapter := models.Chapter{} |
|
||||
err := collection.Find(query).One(&chapter) |
|
||||
|
|
||||
return chapter, err |
|
||||
} |
|
||||
|
|
||||
func list(query interface{}) ([]models.Chapter, error) { |
|
||||
chapters := make([]models.Chapter, 0, 8) |
|
||||
err := collection.Find(query).Sort("createdDate").All(&chapters) |
|
||||
if err != nil { |
|
||||
return nil, err |
|
||||
} |
|
||||
|
|
||||
return chapters, nil |
|
||||
} |
|
||||
|
|
||||
func makeChapterID() string { |
|
||||
result := "SC" |
|
||||
offset := 0 |
|
||||
data := make([]byte, 32) |
|
||||
|
|
||||
rand.Read(data) |
|
||||
for len(result) < 24 { |
|
||||
result += strconv.FormatUint(binary.LittleEndian.Uint64(data[offset:]), 36) |
|
||||
offset += 8 |
|
||||
|
|
||||
if offset >= 32 { |
|
||||
rand.Read(data) |
|
||||
offset = 0 |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return result[:24] |
|
||||
} |
|
||||
|
|
||||
func init() { |
|
||||
store.HandleInit(func(db *mgo.Database) { |
|
||||
collection = db.C("story.chapters") |
|
||||
storyCollection = db.C("story.stories") |
|
||||
|
|
||||
collection.EnsureIndexKey("storyId") |
|
||||
collection.EnsureIndexKey("author") |
|
||||
collection.EnsureIndexKey("createdDate") |
|
||||
}) |
|
||||
} |
|
@ -1,46 +0,0 @@ |
|||||
package chapters |
|
||||
|
|
||||
import ( |
|
||||
"time" |
|
||||
|
|
||||
"git.aiterp.net/rpdata/api/models" |
|
||||
"github.com/globalsign/mgo/bson" |
|
||||
) |
|
||||
|
|
||||
// Edit edits a chapter, and updates EditedDate. While many Edit functions cheat if there's nothing to
|
|
||||
// change, this functill will due to EditedDate.
|
|
||||
func Edit(chapter models.Chapter, title, source *string, fictionalDate *time.Time, commentMode *models.ChapterCommentMode, commentsLocked *bool) (models.Chapter, error) { |
|
||||
now := time.Now() |
|
||||
changes := bson.M{"editedDate": now} |
|
||||
|
|
||||
edited := chapter |
|
||||
edited.EditedDate = now |
|
||||
|
|
||||
if title != nil && *title != chapter.Title { |
|
||||
changes["title"] = *title |
|
||||
edited.Title = *title |
|
||||
} |
|
||||
if source != nil && *source != chapter.Source { |
|
||||
changes["source"] = *source |
|
||||
edited.Source = *source |
|
||||
} |
|
||||
if fictionalDate != nil && !fictionalDate.Equal(chapter.FictionalDate) { |
|
||||
changes["fictionalDate"] = *fictionalDate |
|
||||
edited.FictionalDate = *fictionalDate |
|
||||
} |
|
||||
if commentMode != nil && *commentMode != chapter.CommentMode { |
|
||||
changes["commentMode"] = *commentMode |
|
||||
edited.CommentMode = *commentMode |
|
||||
} |
|
||||
if commentsLocked != nil && *commentsLocked != chapter.CommentsLocked { |
|
||||
changes["commentsLocked"] = *commentsLocked |
|
||||
edited.CommentsLocked = *commentsLocked |
|
||||
} |
|
||||
|
|
||||
err := collection.UpdateId(chapter.ID, bson.M{"$set": changes}) |
|
||||
if err != nil { |
|
||||
return chapter, err |
|
||||
} |
|
||||
|
|
||||
return edited, nil |
|
||||
} |
|
@ -1,11 +0,0 @@ |
|||||
package chapters |
|
||||
|
|
||||
import ( |
|
||||
"git.aiterp.net/rpdata/api/models" |
|
||||
"github.com/globalsign/mgo/bson" |
|
||||
) |
|
||||
|
|
||||
// FindID finds a chapter by ID
|
|
||||
func FindID(id string) (models.Chapter, error) { |
|
||||
return find(bson.M{"_id": id}) |
|
||||
} |
|
@ -1,11 +0,0 @@ |
|||||
package chapters |
|
||||
|
|
||||
import ( |
|
||||
"git.aiterp.net/rpdata/api/models" |
|
||||
"github.com/globalsign/mgo/bson" |
|
||||
) |
|
||||
|
|
||||
// ListStoryID lists all chapters for the story ID
|
|
||||
func ListStoryID(storyID string) ([]models.Chapter, error) { |
|
||||
return list(bson.M{"storyId": storyID}) |
|
||||
} |
|
@ -1,28 +0,0 @@ |
|||||
package chapters |
|
||||
|
|
||||
import ( |
|
||||
"time" |
|
||||
|
|
||||
"git.aiterp.net/rpdata/api/models" |
|
||||
"github.com/globalsign/mgo/bson" |
|
||||
) |
|
||||
|
|
||||
// Move updates the chapter, moving it to the given story.
|
|
||||
func Move(chapter models.Chapter, story models.Story) (models.Chapter, error) { |
|
||||
now := time.Now() |
|
||||
|
|
||||
err := collection.UpdateId(chapter.ID, bson.M{"$set": bson.M{"editedDate": now, "storyId": story.ID}}) |
|
||||
if err != nil { |
|
||||
return models.Chapter{}, err |
|
||||
} |
|
||||
|
|
||||
chapter.EditedDate = now |
|
||||
|
|
||||
if chapter.CreatedDate.After(story.UpdatedDate) { |
|
||||
if err := storyCollection.UpdateId(story.ID, bson.M{"$set": bson.M{"updatedDate": chapter.CreatedDate}}); err == nil { |
|
||||
story.UpdatedDate = chapter.CreatedDate |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return chapter, nil |
|
||||
} |
|
@ -1,24 +0,0 @@ |
|||||
package chapters |
|
||||
|
|
||||
import ( |
|
||||
"git.aiterp.net/rpdata/api/models" |
|
||||
"github.com/globalsign/mgo/bson" |
|
||||
) |
|
||||
|
|
||||
// Remove removes a chapter.
|
|
||||
func Remove(chapter models.Chapter) (models.Chapter, error) { |
|
||||
if err := collection.RemoveId(chapter.ID); err != nil { |
|
||||
return models.Chapter{}, err |
|
||||
} |
|
||||
|
|
||||
return chapter, nil |
|
||||
} |
|
||||
|
|
||||
// RemoveStory removes all chapters belonging to a story
|
|
||||
func RemoveStory(story models.Story) error { |
|
||||
if _, err := collection.RemoveAll(bson.M{"storyId": story.ID}); err != nil { |
|
||||
return err |
|
||||
} |
|
||||
|
|
||||
return nil |
|
||||
} |
|
@ -1,27 +0,0 @@ |
|||||
package characters |
|
||||
|
|
||||
import ( |
|
||||
"errors" |
|
||||
"strings" |
|
||||
|
|
||||
"git.aiterp.net/rpdata/api/models" |
|
||||
"github.com/globalsign/mgo/bson" |
|
||||
) |
|
||||
|
|
||||
// AddNick adds a nick to a characters
|
|
||||
func AddNick(character models.Character, nick string) (models.Character, error) { |
|
||||
for i := range character.Nicks { |
|
||||
if strings.EqualFold(character.Nicks[i], nick) { |
|
||||
return models.Character{}, errors.New("Nick already exists") |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
err := collection.UpdateId(character.ID, bson.M{"$push": bson.M{"nicks": nick}}) |
|
||||
if err != nil { |
|
||||
return models.Character{}, err |
|
||||
} |
|
||||
|
|
||||
character.Nicks = append(character.Nicks, nick) |
|
||||
|
|
||||
return character, nil |
|
||||
} |
|
@ -1,48 +0,0 @@ |
|||||
package characters |
|
||||
|
|
||||
import ( |
|
||||
"errors" |
|
||||
"strconv" |
|
||||
"strings" |
|
||||
|
|
||||
"git.aiterp.net/rpdata/api/internal/counter" |
|
||||
"git.aiterp.net/rpdata/api/models" |
|
||||
) |
|
||||
|
|
||||
// Add creates a Character and pushes it to the database. It does some validation
|
|
||||
// on nick, name, shortName and author. It will generate a shortname from the first
|
|
||||
// name if a blank one is provided.
|
|
||||
func Add(nick, name, shortName, author, description string) (models.Character, error) { |
|
||||
if len(nick) < 1 || len(name) < 1 || len(author) < 1 { |
|
||||
return models.Character{}, errors.New("Nick, name, or author name too short or empty") |
|
||||
} |
|
||||
if shortName == "" { |
|
||||
shortName = strings.SplitN(name, " ", 2)[0] |
|
||||
} |
|
||||
|
|
||||
char, err := FindNick(nick) |
|
||||
if err == nil && char.ID != "" { |
|
||||
return models.Character{}, errors.New("Nick is occupied") |
|
||||
} |
|
||||
|
|
||||
nextID, err := counter.Next("auto_increment", "Character") |
|
||||
if err != nil { |
|
||||
return models.Character{}, err |
|
||||
} |
|
||||
|
|
||||
character := models.Character{ |
|
||||
ID: "C" + strconv.Itoa(nextID), |
|
||||
Nicks: []string{nick}, |
|
||||
Name: name, |
|
||||
ShortName: shortName, |
|
||||
Author: author, |
|
||||
Description: description, |
|
||||
} |
|
||||
|
|
||||
err = collection.Insert(character) |
|
||||
if err != nil { |
|
||||
return models.Character{}, err |
|
||||
} |
|
||||
|
|
||||
return character, nil |
|
||||
} |
|
@ -1,55 +0,0 @@ |
|||||
package characters |
|
||||
|
|
||||
import ( |
|
||||
"log" |
|
||||
|
|
||||
"git.aiterp.net/rpdata/api/internal/store" |
|
||||
"git.aiterp.net/rpdata/api/models" |
|
||||
"github.com/globalsign/mgo" |
|
||||
) |
|
||||
|
|
||||
var collection *mgo.Collection |
|
||||
|
|
||||
func find(query interface{}) (models.Character, error) { |
|
||||
character := models.Character{} |
|
||||
err := collection.Find(query).One(&character) |
|
||||
if err != nil { |
|
||||
return models.Character{}, err |
|
||||
} |
|
||||
|
|
||||
return character, nil |
|
||||
} |
|
||||
|
|
||||
func list(query interface{}) ([]models.Character, error) { |
|
||||
characters := make([]models.Character, 0, 64) |
|
||||
err := collection.Find(query).All(&characters) |
|
||||
if err != nil { |
|
||||
return nil, err |
|
||||
} |
|
||||
|
|
||||
return characters, nil |
|
||||
} |
|
||||
|
|
||||
func init() { |
|
||||
store.HandleInit(func(db *mgo.Database) { |
|
||||
collection = db.C("common.characters") |
|
||||
|
|
||||
collection.EnsureIndexKey("name") |
|
||||
collection.EnsureIndexKey("shortName") |
|
||||
collection.EnsureIndexKey("author") |
|
||||
err := collection.EnsureIndex(mgo.Index{ |
|
||||
Key: []string{"nicks"}, |
|
||||
Unique: true, |
|
||||
DropDups: true, |
|
||||
}) |
|
||||
if err != nil { |
|
||||
log.Fatalln("init common.characters:", err) |
|
||||
} |
|
||||
err = collection.EnsureIndex(mgo.Index{ |
|
||||
Key: []string{"$text:description"}, |
|
||||
}) |
|
||||
if err != nil { |
|
||||
log.Fatalln("init common.characters:", err) |
|
||||
} |
|
||||
}) |
|
||||
} |
|
@ -1,36 +0,0 @@ |
|||||
package characters |
|
||||
|
|
||||
import ( |
|
||||
"git.aiterp.net/rpdata/api/models" |
|
||||
"github.com/globalsign/mgo/bson" |
|
||||
) |
|
||||
|
|
||||
// Edit sets the fields of metadata. Only non-empty and different fields will be set in the
|
|
||||
// database, preventing out of order edits to two fields from conflicting
|
|
||||
func Edit(character models.Character, name, shortName, description *string) (models.Character, error) { |
|
||||
changes := bson.M{} |
|
||||
|
|
||||
if name != nil && *name != character.Name { |
|
||||
character.Name = *name |
|
||||
changes["name"] = *name |
|
||||
} |
|
||||
if shortName != nil && *shortName != character.ShortName { |
|
||||
character.ShortName = *shortName |
|
||||
changes["shortName"] = *shortName |
|
||||
} |
|
||||
if description != nil && *description != character.Description { |
|
||||
character.Description = *description |
|
||||
changes["description"] = *description |
|
||||
} |
|
||||
|
|
||||
if len(changes) == 0 { |
|
||||
return character, nil |
|
||||
} |
|
||||
|
|
||||
err := collection.UpdateId(character.ID, bson.M{"$set": changes}) |
|
||||
if err != nil { |
|
||||
return models.Character{}, err |
|
||||
} |
|
||||
|
|
||||
return character, nil |
|
||||
} |
|
@ -1,16 +0,0 @@ |
|||||
package characters |
|
||||
|
|
||||
import ( |
|
||||
"git.aiterp.net/rpdata/api/models" |
|
||||
"github.com/globalsign/mgo/bson" |
|
||||
) |
|
||||
|
|
||||
// FindID finds a character by id.
|
|
||||
func FindID(id string) (models.Character, error) { |
|
||||
return find(bson.M{"_id": id}) |
|
||||
} |
|
||||
|
|
||||
// FindNick finds a character by nick
|
|
||||
func FindNick(nick string) (models.Character, error) { |
|
||||
return find(bson.M{"nicks": nick}) |
|
||||
} |
|
@ -1,56 +0,0 @@ |
|||||
package characters |
|
||||
|
|
||||
import ( |
|
||||
"git.aiterp.net/rpdata/api/models" |
|
||||
"github.com/globalsign/mgo/bson" |
|
||||
) |
|
||||
|
|
||||
// Filter is used to filter the list of characters
|
|
||||
type Filter struct { |
|
||||
IDs []string `json:"ids"` |
|
||||
Nicks []string `json:"nicks"` |
|
||||
Names []string `json:"names"` |
|
||||
Author *string `json:"author"` |
|
||||
Search *string `json:"search"` |
|
||||
} |
|
||||
|
|
||||
// List lists all characters
|
|
||||
func List(filter *Filter) ([]models.Character, error) { |
|
||||
query := bson.M{} |
|
||||
|
|
||||
if filter != nil { |
|
||||
if len(filter.IDs) > 1 { |
|
||||
query["_id"] = bson.M{"$in": filter.IDs} |
|
||||
} else if len(filter.IDs) == 1 { |
|
||||
query["_id"] = filter.IDs[0] |
|
||||
} |
|
||||
|
|
||||
if len(filter.Nicks) == 1 { |
|
||||
query["nicks"] = filter.Nicks[0] |
|
||||
} else if filter.Nicks != nil { |
|
||||
query["nicks"] = bson.M{"$in": filter.Nicks} |
|
||||
} |
|
||||
|
|
||||
if len(filter.Names) > 1 { |
|
||||
query["$or"] = bson.M{ |
|
||||
"name": bson.M{"$in": filter.Names}, |
|
||||
"shortName": bson.M{"$in": filter.Names}, |
|
||||
} |
|
||||
} else if len(filter.Names) == 1 { |
|
||||
query["$or"] = bson.M{ |
|
||||
"name": filter.Names[0], |
|
||||
"shortName": filter.Names[0], |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if filter.Author != nil { |
|
||||
query["author"] = *filter.Author |
|
||||
} |
|
||||
|
|
||||
if filter.Search != nil { |
|
||||
query["$text"] = bson.M{"$search": *filter.Search} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return list(query) |
|
||||
} |
|
@ -1,32 +0,0 @@ |
|||||
package characters |
|
||||
|
|
||||
import ( |
|
||||
"errors" |
|
||||
"strings" |
|
||||
|
|
||||
"git.aiterp.net/rpdata/api/models" |
|
||||
"github.com/globalsign/mgo/bson" |
|
||||
) |
|
||||
|
|
||||
// RemoveNick removes a nick to a characters
|
|
||||
func RemoveNick(character models.Character, nick string) (models.Character, error) { |
|
||||
index := -1 |
|
||||
for i := range character.Nicks { |
|
||||
if strings.EqualFold(character.Nicks[i], nick) { |
|
||||
index = i |
|
||||
break |
|
||||
} |
|
||||
} |
|
||||
if index == -1 { |
|
||||
return models.Character{}, errors.New("Nick does not exist") |
|
||||
} |
|
||||
|
|
||||
err := collection.UpdateId(character.ID, bson.M{"$pull": bson.M{"nicks": nick}}) |
|
||||
if err != nil { |
|
||||
return models.Character{}, err |
|
||||
} |
|
||||
|
|
||||
character.Nicks = append(character.Nicks[:index], character.Nicks[index+1:]...) |
|
||||
|
|
||||
return character, nil |
|
||||
} |
|
@ -1,13 +0,0 @@ |
|||||
package characters |
|
||||
|
|
||||
import "git.aiterp.net/rpdata/api/models" |
|
||||
|
|
||||
// Remove removes a character, returning it if it succeeds
|
|
||||
func Remove(character models.Character) (models.Character, error) { |
|
||||
err := collection.RemoveId(character.ID) |
|
||||
if err != nil { |
|
||||
return models.Character{}, err |
|
||||
} |
|
||||
|
|
||||
return character, nil |
|
||||
} |
|
@ -1,35 +0,0 @@ |
|||||
package comments |
|
||||
|
|
||||
import ( |
|
||||
"time" |
|
||||
|
|
||||
"git.aiterp.net/rpdata/api/models" |
|
||||
) |
|
||||
|
|
||||
// Add adds a comment.
|
|
||||
func Add(chapter models.Chapter, subject, author, source, characterName string, character *models.Character, createdDate time.Time, fictionalDate time.Time) (models.Comment, error) { |
|
||||
characterID := "" |
|
||||
if character != nil { |
|
||||
characterID = character.ID |
|
||||
} |
|
||||
|
|
||||
comment := models.Comment{ |
|
||||
ID: makeCommentID(), |
|
||||
ChapterID: chapter.ID, |
|
||||
Subject: subject, |
|
||||
Author: author, |
|
||||
CharacterName: characterName, |
|
||||
CharacterID: characterID, |
|
||||
FictionalDate: fictionalDate, |
|
||||
CreatedDate: createdDate, |
|
||||
EditedDate: createdDate, |
|
||||
Source: source, |
|
||||
} |
|
||||
|
|
||||
err := collection.Insert(comment) |
|
||||
if err != nil { |
|
||||
return models.Comment{}, err |
|
||||
} |
|
||||
|
|
||||
return comment, nil |
|
||||
} |
|
@ -1,66 +0,0 @@ |
|||||
package comments |
|
||||
|
|
||||
import ( |
|
||||
"crypto/rand" |
|
||||
"encoding/binary" |
|
||||
"strconv" |
|
||||
|
|
||||
"git.aiterp.net/rpdata/api/internal/store" |
|
||||
"git.aiterp.net/rpdata/api/models" |
|
||||
"github.com/globalsign/mgo" |
|
||||
) |
|
||||
|
|
||||
var collection *mgo.Collection |
|
||||
|
|
||||
func find(query interface{}) (models.Comment, error) { |
|
||||
comment := models.Comment{} |
|
||||
err := collection.Find(query).One(&comment) |
|
||||
|
|
||||
return comment, err |
|
||||
} |
|
||||
|
|
||||
func list(query interface{}, limit int) ([]models.Comment, error) { |
|
||||
allocSize := 32 |
|
||||
if limit >= 0 { |
|
||||
allocSize = limit |
|
||||
} else { |
|
||||
limit = 0 |
|
||||
} |
|
||||
|
|
||||
comments := make([]models.Comment, 0, allocSize) |
|
||||
err := collection.Find(query).Sort("createdDate").Limit(limit).All(&comments) |
|
||||
if err != nil { |
|
||||
return nil, err |
|
||||
} |
|
||||
|
|
||||
return comments, nil |
|
||||
} |
|
||||
|
|
||||
func makeCommentID() string { |
|
||||
result := "SCC" |
|
||||
offset := 0 |
|
||||
data := make([]byte, 48) |
|
||||
|
|
||||
rand.Read(data) |
|
||||
for len(result) < 32 { |
|
||||
result += strconv.FormatUint(binary.LittleEndian.Uint64(data[offset:]), 36) |
|
||||
offset += 8 |
|
||||
|
|
||||
if offset >= 48 { |
|
||||
rand.Read(data) |
|
||||
offset = 0 |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return result[:32] |
|
||||
} |
|
||||
|
|
||||
func init() { |
|
||||
store.HandleInit(func(db *mgo.Database) { |
|
||||
collection = db.C("story.comments") |
|
||||
|
|
||||
collection.EnsureIndexKey("chapterId") |
|
||||
collection.EnsureIndexKey("author") |
|
||||
collection.EnsureIndexKey("createdDate") |
|
||||
}) |
|
||||
} |
|
@ -1,44 +0,0 @@ |
|||||
package comments |
|
||||
|
|
||||
import ( |
|
||||
"time" |
|
||||
|
|
||||
"git.aiterp.net/rpdata/api/models" |
|
||||
"github.com/globalsign/mgo/bson" |
|
||||
) |
|
||||
|
|
||||
// Edit edits a comment.
|
|
||||
func Edit(comment models.Comment, source, characterName, characterID, subject *string, fictionalDate *time.Time) (models.Comment, error) { |
|
||||
changes := make(bson.M, 6) |
|
||||
|
|
||||
comment.EditedDate = time.Now() |
|
||||
changes["editedDate"] = comment.EditedDate |
|
||||
|
|
||||
if source != nil { |
|
||||
comment.Source = *source |
|
||||
changes["source"] = *source |
|
||||
} |
|
||||
if characterName != nil { |
|
||||
comment.CharacterName = *characterName |
|
||||
changes["characterName"] = *characterName |
|
||||
} |
|
||||
if characterID != nil { |
|
||||
comment.CharacterID = *characterID |
|
||||
changes["characterId"] = *characterID |
|
||||
} |
|
||||
if subject != nil { |
|
||||
comment.Subject = *subject |
|
||||
changes["subject"] = *subject |
|
||||
} |
|
||||
if fictionalDate != nil { |
|
||||
comment.FictionalDate = *fictionalDate |
|
||||
changes["fictionalDate"] = *fictionalDate |
|
||||
} |
|
||||
|
|
||||
err := collection.UpdateId(comment.ID, bson.M{"$set": changes}) |
|
||||
if err != nil { |
|
||||
return models.Comment{}, err |
|
||||
} |
|
||||
|
|
||||
return comment, nil |
|
||||
} |
|
@ -1,11 +0,0 @@ |
|||||
package comments |
|
||||
|
|
||||
import ( |
|
||||
"git.aiterp.net/rpdata/api/models" |
|
||||
"github.com/globalsign/mgo/bson" |
|
||||
) |
|
||||
|
|
||||
// Find finds a comment by ID.
|
|
||||
func Find(id string) (models.Comment, error) { |
|
||||
return find(bson.M{"_id": id}) |
|
||||
} |
|
@ -1,11 +0,0 @@ |
|||||
package comments |
|
||||
|
|
||||
import ( |
|
||||
"git.aiterp.net/rpdata/api/models" |
|
||||
"github.com/globalsign/mgo/bson" |
|
||||
) |
|
||||
|
|
||||
// ListChapterID lists all comments by chapter-ID
|
|
||||
func ListChapterID(chapterID string, limit int) ([]models.Comment, error) { |
|
||||
return list(bson.M{"chapterId": chapterID}, limit) |
|
||||
} |
|
@ -1,17 +0,0 @@ |
|||||
package comments |
|
||||
|
|
||||
import ( |
|
||||
"git.aiterp.net/rpdata/api/models" |
|
||||
"github.com/globalsign/mgo/bson" |
|
||||
) |
|
||||
|
|
||||
// Remove removes one comment.
|
|
||||
func Remove(comment models.Comment) error { |
|
||||
return collection.RemoveId(comment.ID) |
|
||||
} |
|
||||
|
|
||||
// RemoveChapter removes all comments for the given chapter.
|
|
||||
func RemoveChapter(chapter models.Chapter) error { |
|
||||
_, err := collection.RemoveAll(bson.M{"chapterId": chapter.ID}) |
|
||||
return err |
|
||||
} |
|
@ -1,36 +0,0 @@ |
|||||
package posts |
|
||||
|
|
||||
import ( |
|
||||
"git.aiterp.net/rpdata/api/internal/counter" |
|
||||
"git.aiterp.net/rpdata/api/models" |
|
||||
) |
|
||||
|
|
||||
// AddMany adds multiple posts in on query. Each post gets a new ID and is associated with the log.
|
|
||||
func AddMany(log models.Log, posts []models.Post) ([]models.Post, error) { |
|
||||
docs := make([]interface{}, len(posts)) |
|
||||
copies := make([]models.Post, len(posts)) |
|
||||
|
|
||||
mutex.RLock() |
|
||||
defer mutex.RUnlock() |
|
||||
|
|
||||
startPosition, err := counter.NextMany("next_post_id", log.ShortID, len(posts)) |
|
||||
if err != nil { |
|
||||
return nil, err |
|
||||
} |
|
||||
|
|
||||
for i, post := range posts { |
|
||||
post.ID = generateID(post.Time) |
|
||||
post.LogID = log.ShortID |
|
||||
post.Position = startPosition + i |
|
||||
|
|
||||
docs[i] = post |
|
||||
copies[i] = post |
|
||||
} |
|
||||
|
|
||||
err = collection.Insert(docs...) |
|
||||
if err != nil { |
|
||||
return nil, err |
|
||||
} |
|
||||
|
|
||||
return copies, nil |
|
||||
} |
|
@ -1,51 +0,0 @@ |
|||||
package posts |
|
||||
|
|
||||
import ( |
|
||||
"crypto/rand" |
|
||||
"encoding/binary" |
|
||||
"errors" |
|
||||
"strconv" |
|
||||
"time" |
|
||||
|
|
||||
"git.aiterp.net/rpdata/api/internal/counter" |
|
||||
"git.aiterp.net/rpdata/api/models" |
|
||||
) |
|
||||
|
|
||||
// Add creates a new post.
|
|
||||
func Add(log models.Log, time time.Time, kind, nick, text string) (models.Post, error) { |
|
||||
if kind == "" || nick == "" || text == "" { |
|
||||
return models.Post{}, errors.New("Missing/empty parameters") |
|
||||
} |
|
||||
|
|
||||
mutex.RLock() |
|
||||
defer mutex.RUnlock() |
|
||||
|
|
||||
position, err := counter.Next("next_post_id", log.ShortID) |
|
||||
if err != nil { |
|
||||
return models.Post{}, err |
|
||||
} |
|
||||
|
|
||||
post := models.Post{ |
|
||||
ID: generateID(time), |
|
||||
Position: position, |
|
||||
LogID: log.ShortID, |
|
||||
Time: time, |
|
||||
Kind: kind, |
|
||||
Nick: nick, |
|
||||
Text: text, |
|
||||
} |
|
||||
|
|
||||
err = collection.Insert(post) |
|
||||
if err != nil { |
|
||||
return models.Post{}, err |
|
||||
} |
|
||||
|
|
||||
return post, nil |
|
||||
} |
|
||||
|
|
||||
func generateID(time time.Time) string { |
|
||||
data := make([]byte, 4) |
|
||||
rand.Read(data) |
|
||||
|
|
||||
return "P" + strconv.FormatInt(time.UnixNano(), 36) + strconv.FormatInt(int64(binary.LittleEndian.Uint32(data)), 36) |
|
||||
} |
|
@ -1,56 +0,0 @@ |
|||||
package posts |
|
||||
|
|
||||
import ( |
|
||||
"log" |
|
||||
"sync" |
|
||||
|
|
||||
"git.aiterp.net/rpdata/api/internal/store" |
|
||||
"git.aiterp.net/rpdata/api/models" |
|
||||
"github.com/globalsign/mgo" |
|
||||
) |
|
||||
|
|
||||
var collection *mgo.Collection |
|
||||
var mutex sync.RWMutex |
|
||||
|
|
||||
func find(query interface{}) (models.Post, error) { |
|
||||
post := models.Post{} |
|
||||
err := collection.Find(query).One(&post) |
|
||||
if err != nil { |
|
||||
return models.Post{}, err |
|
||||
} |
|
||||
|
|
||||
return post, nil |
|
||||
} |
|
||||
|
|
||||
func list(query interface{}, limit int, sort ...string) ([]models.Post, error) { |
|
||||
size := 64 |
|
||||
if limit > 0 { |
|
||||
size = limit |
|
||||
} |
|
||||
posts := make([]models.Post, 0, size) |
|
||||
|
|
||||
err := collection.Find(query).Limit(limit).Sort(sort...).All(&posts) |
|
||||
if err != nil { |
|
||||
return nil, err |
|
||||
} |
|
||||
|
|
||||
return posts, nil |
|
||||
} |
|
||||
|
|
||||
func init() { |
|
||||
store.HandleInit(func(db *mgo.Database) { |
|
||||
collection = db.C("logbot3.posts") |
|
||||
|
|
||||
collection.EnsureIndexKey("logId") |
|
||||
collection.EnsureIndexKey("time") |
|
||||
collection.EnsureIndexKey("kind") |
|
||||
collection.EnsureIndexKey("position") |
|
||||
|
|
||||
err := collection.EnsureIndex(mgo.Index{ |
|
||||
Key: []string{"$text:text"}, |
|
||||
}) |
|
||||
if err != nil { |
|
||||
log.Fatalln("init logbot3.logs:", err) |
|
||||
} |
|
||||
}) |
|
||||
} |
|
@ -1,44 +0,0 @@ |
|||||
package posts |
|
||||
|
|
||||
import ( |
|
||||
"time" |
|
||||
|
|
||||
"git.aiterp.net/rpdata/api/models" |
|
||||
"github.com/globalsign/mgo/bson" |
|
||||
) |
|
||||
|
|
||||
// Edit edits a post and returns the result if the edit succeeded.
|
|
||||
func Edit(post models.Post, time *time.Time, kind *string, nick *string, text *string) (models.Post, error) { |
|
||||
mutex.RLock() |
|
||||
defer mutex.RUnlock() |
|
||||
|
|
||||
changes := bson.M{} |
|
||||
|
|
||||
if time != nil && !time.IsZero() && !time.Equal(post.Time) { |
|
||||
changes["time"] = *time |
|
||||
post.Time = *time |
|
||||
} |
|
||||
if kind != nil && *kind != "" && *kind != post.Kind { |
|
||||
changes["kind"] = *kind |
|
||||
post.Kind = *kind |
|
||||
} |
|
||||
if nick != nil && *nick != "" && *nick != post.Nick { |
|
||||
changes["nick"] = *nick |
|
||||
post.Nick = *nick |
|
||||
} |
|
||||
if text != nil && *text != "" && *text != post.Text { |
|
||||
changes["text"] = *text |
|
||||
post.Text = *text |
|
||||
} |
|
||||
|
|
||||
if len(changes) == 0 { |
|
||||
return post, nil |
|
||||
} |
|
||||
|
|
||||
err := collection.UpdateId(post.ID, bson.M{"$set": changes}) |
|
||||
if err != nil { |
|
||||
return models.Post{}, err |
|
||||
} |
|
||||
|
|
||||
return post, nil |
|
||||
} |
|
@ -1,14 +0,0 @@ |
|||||
package posts |
|
||||
|
|
||||
import ( |
|
||||
"git.aiterp.net/rpdata/api/models" |
|
||||
"github.com/globalsign/mgo/bson" |
|
||||
) |
|
||||
|
|
||||
// FindID finds a log post by ID.
|
|
||||
func FindID(id string) (models.Post, error) { |
|
||||
mutex.RLock() |
|
||||
defer mutex.RUnlock() |
|
||||
|
|
||||
return find(bson.M{"_id": id}) |
|
||||
} |
|
@ -1,57 +0,0 @@ |
|||||
package posts |
|
||||
|
|
||||
import ( |
|
||||
"strings" |
|
||||
|
|
||||
"git.aiterp.net/rpdata/api/models" |
|
||||
"github.com/globalsign/mgo/bson" |
|
||||
) |
|
||||
|
|
||||
// Filter is used to generate a query to the database.
|
|
||||
type Filter struct { |
|
||||
ID []string |
|
||||
Kind []string |
|
||||
LogID *string |
|
||||
Search *string |
|
||||
Limit int |
|
||||
} |
|
||||
|
|
||||
// List lists the posts according to the filter
|
|
||||
func List(filter *Filter) ([]models.Post, error) { |
|
||||
mutex.RLock() |
|
||||
defer mutex.RUnlock() |
|
||||
|
|
||||
limit := 256 |
|
||||
query := bson.M{} |
|
||||
|
|
||||
if filter != nil { |
|
||||
if filter.LogID != nil { |
|
||||
query["logId"] = filter.LogID |
|
||||
} |
|
||||
|
|
||||
if len(filter.ID) > 1 { |
|
||||
query["_id"] = bson.M{"$in": filter.ID} |
|
||||
} else if len(filter.ID) == 1 { |
|
||||
query["_id"] = filter.ID[0] |
|
||||
} |
|
||||
|
|
||||
if len(filter.Kind) > 1 { |
|
||||
for i := range filter.Kind { |
|
||||
filter.Kind[i] = strings.ToLower(filter.Kind[i]) |
|
||||
} |
|
||||
|
|
||||
query["kind"] = bson.M{"$in": filter.Kind} |
|
||||
} else if len(filter.Kind) == 1 { |
|
||||
query["kind"] = strings.ToLower(filter.Kind[0]) |
|
||||
} |
|
||||
|
|
||||
limit = filter.Limit |
|
||||
} |
|
||||
|
|
||||
posts, err := list(query, limit, "position") |
|
||||
if err != nil { |
|
||||
return nil, err |
|
||||
} |
|
||||
|
|
||||
return posts, nil |
|
||||
} |
|
@ -1,69 +0,0 @@ |
|||||
package posts |
|
||||
|
|
||||
import ( |
|
||||
"errors" |
|
||||
|
|
||||
"git.aiterp.net/rpdata/api/models" |
|
||||
"github.com/globalsign/mgo/bson" |
|
||||
) |
|
||||
|
|
||||
// Move the post
|
|
||||
func Move(post models.Post, toPosition int) ([]models.Post, error) { |
|
||||
if toPosition < 1 { |
|
||||
return nil, errors.New("Invalid position") |
|
||||
} |
|
||||
|
|
||||
mutex.Lock() |
|
||||
defer mutex.Unlock() |
|
||||
|
|
||||
// To avoid problems, only allow target indices that are allowed. If it's 1, then there is bound to
|
|
||||
// be a post at the position.
|
|
||||
if toPosition > 1 { |
|
||||
existing := models.Post{} |
|
||||
err := collection.Find(bson.M{"logId": post.LogID, "position": toPosition}).One(&existing) |
|
||||
|
|
||||
if err != nil || existing.Position != toPosition { |
|
||||
return nil, errors.New("No post found at the position") |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
query := bson.M{"logId": post.LogID} |
|
||||
operation := bson.M{"$inc": bson.M{"position": 1}} |
|
||||
|
|
||||
if toPosition < post.Position { |
|
||||
query["$and"] = []bson.M{ |
|
||||
bson.M{"position": bson.M{"$gte": toPosition}}, |
|
||||
bson.M{"position": bson.M{"$lt": post.Position}}, |
|
||||
} |
|
||||
} else { |
|
||||
query["$and"] = []bson.M{ |
|
||||
bson.M{"position": bson.M{"$gt": post.Position}}, |
|
||||
bson.M{"position": bson.M{"$lte": toPosition}}, |
|
||||
} |
|
||||
|
|
||||
operation["$inc"] = bson.M{"position": -1} |
|
||||
} |
|
||||
|
|
||||
_, err := collection.UpdateAll(query, operation) |
|
||||
if err != nil { |
|
||||
return nil, errors.New("Moving others failed: " + err.Error()) |
|
||||
} |
|
||||
|
|
||||
err = collection.UpdateId(post.ID, bson.M{"$set": bson.M{"position": toPosition}}) |
|
||||
if err != nil { |
|
||||
return nil, errors.New("Moving failed: " + err.Error() + " (If you see this on the page, please let me know ASAP)") |
|
||||
} |
|
||||
|
|
||||
from, to := post.Position, toPosition |
|
||||
if to < from { |
|
||||
from, to = to, from |
|
||||
} |
|
||||
|
|
||||
posts := make([]models.Post, 0, (to-from)+1) |
|
||||
err = collection.Find(bson.M{"logId": post.LogID, "position": bson.M{"$gte": from, "$lte": to}}).Sort("position").All(&posts) |
|
||||
if err != nil { |
|
||||
return nil, errors.New("The move completed successfully, but finding the moved posts failed: " + err.Error()) |
|
||||
} |
|
||||
|
|
||||
return posts, nil |
|
||||
} |
|
@ -1,33 +0,0 @@ |
|||||
package posts |
|
||||
|
|
||||
import ( |
|
||||
"git.aiterp.net/rpdata/api/internal/counter" |
|
||||
"git.aiterp.net/rpdata/api/models" |
|
||||
"github.com/globalsign/mgo/bson" |
|
||||
) |
|
||||
|
|
||||
// Remove removes a post, moving all subsequent post up one position
|
|
||||
func Remove(post models.Post) (models.Post, error) { |
|
||||
mutex.Lock() |
|
||||
defer mutex.Unlock() |
|
||||
|
|
||||
err := collection.RemoveId(post.ID) |
|
||||
if err != nil { |
|
||||
return models.Post{}, err |
|
||||
} |
|
||||
|
|
||||
_, err = collection.UpdateAll(bson.M{"logId": post.LogID, "position": bson.M{"$gt": post.Position}}, bson.M{"$inc": bson.M{"position": -1}}) |
|
||||
if err != nil { |
|
||||
return models.Post{}, err |
|
||||
} |
|
||||
|
|
||||
counter.NextMany("next_post_id", post.LogID, -1) |
|
||||
|
|
||||
return post, nil |
|
||||
} |
|
||||
|
|
||||
// RemoveAllInLog removes all posts for the given log.
|
|
||||
func RemoveAllInLog(log models.Log) error { |
|
||||
_, err := collection.RemoveAll(bson.M{"logId": log.ShortID}) |
|
||||
return err |
|
||||
} |
|
@ -1,29 +0,0 @@ |
|||||
package stories |
|
||||
|
|
||||
import ( |
|
||||
"errors" |
|
||||
|
|
||||
"git.aiterp.net/rpdata/api/models" |
|
||||
"github.com/globalsign/mgo/bson" |
|
||||
) |
|
||||
|
|
||||
// ErrTagAlreadyExists is an error returned by Story.AddTag
|
|
||||
var ErrTagAlreadyExists = errors.New("Tag already exists on story") |
|
||||
|
|
||||
// AddTag adds a tag to the story. It returns ErrTagAlreadyExists if the tag is already there
|
|
||||
func AddTag(story models.Story, tag models.Tag) (models.Story, error) { |
|
||||
for i := range story.Tags { |
|
||||
if story.Tags[i].Equal(tag) { |
|
||||
return models.Story{}, ErrTagAlreadyExists |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
err := collection.UpdateId(story.ID, bson.M{"$push": bson.M{"tags": tag}}) |
|
||||
if err != nil { |
|
||||
return models.Story{}, err |
|
||||
} |
|
||||
|
|
||||
story.Tags = append(story.Tags, tag) |
|
||||
|
|
||||
return story, nil |
|
||||
} |
|
@ -1,30 +0,0 @@ |
|||||
package stories |
|
||||
|
|
||||
import ( |
|
||||
"time" |
|
||||
|
|
||||
"git.aiterp.net/rpdata/api/models" |
|
||||
) |
|
||||
|
|
||||
// Add creates a new story.
|
|
||||
func Add(name, author string, category models.StoryCategory, listed, open bool, tags []models.Tag, createdDate, fictionalDate time.Time) (models.Story, error) { |
|
||||
story := models.Story{ |
|
||||
ID: makeStoryID(), |
|
||||
Name: name, |
|
||||
Author: author, |
|
||||
Category: category, |
|
||||
Listed: listed, |
|
||||
Open: open, |
|
||||
Tags: tags, |
|
||||
CreatedDate: createdDate, |
|
||||
FictionalDate: fictionalDate, |
|
||||
UpdatedDate: createdDate, |
|
||||
} |
|
||||
|
|
||||
err := collection.Insert(story) |
|
||||
if err != nil { |
|
||||
return models.Story{}, err |
|
||||
} |
|
||||
|
|
||||
return story, nil |
|
||||
} |
|
@ -1,59 +0,0 @@ |
|||||
package stories |
|
||||
|
|
||||
import ( |
|
||||
"crypto/rand" |
|
||||
"encoding/binary" |
|
||||
"strconv" |
|
||||
|
|
||||
"git.aiterp.net/rpdata/api/internal/store" |
|
||||
"git.aiterp.net/rpdata/api/models" |
|
||||
"github.com/globalsign/mgo" |
|
||||
) |
|
||||
|
|
||||
var collection *mgo.Collection |
|
||||
|
|
||||
func find(query interface{}) (models.Story, error) { |
|
||||
story := models.Story{} |
|
||||
err := collection.Find(query).One(&story) |
|
||||
|
|
||||
return story, err |
|
||||
} |
|
||||
|
|
||||
func list(query interface{}, limit int) ([]models.Story, error) { |
|
||||
stories := make([]models.Story, 0, 64) |
|
||||
err := collection.Find(query).Limit(limit).Sort("-updatedDate").All(&stories) |
|
||||
|
|
||||
return stories, err |
|
||||
} |
|
||||
|
|
||||
// makeStoryID makes a random story ID that's 16 characters long
|
|
||||
func makeStoryID() string { |
|
||||
result := "S" |
|
||||
offset := 0 |
|
||||
data := make([]byte, 32) |
|
||||
|
|
||||
rand.Read(data) |
|
||||
for len(result) < 16 { |
|
||||
result += strconv.FormatUint(binary.LittleEndian.Uint64(data[offset:]), 36) |
|
||||
offset += 8 |
|
||||
|
|
||||
if offset >= 32 { |
|
||||
rand.Read(data) |
|
||||
offset = 0 |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return result[:16] |
|
||||
} |
|
||||
|
|
||||
func init() { |
|
||||
store.HandleInit(func(db *mgo.Database) { |
|
||||
collection = db.C("story.stories") |
|
||||
|
|
||||
collection.EnsureIndexKey("tags") |
|
||||
collection.EnsureIndexKey("author") |
|
||||
collection.EnsureIndexKey("updatedDate") |
|
||||
collection.EnsureIndexKey("fictionalDate") |
|
||||
collection.EnsureIndexKey("listed") |
|
||||
}) |
|
||||
} |
|
@ -1,45 +0,0 @@ |
|||||
package stories |
|
||||
|
|
||||
import ( |
|
||||
"time" |
|
||||
|
|
||||
"git.aiterp.net/rpdata/api/models" |
|
||||
"github.com/globalsign/mgo/bson" |
|
||||
) |
|
||||
|
|
||||
// Edit edits the story and returns the edited story if it succeeds.
|
|
||||
func Edit(story models.Story, name *string, category *models.StoryCategory, listed, open *bool, fictionalDate *time.Time) (models.Story, error) { |
|
||||
changes := bson.M{} |
|
||||
|
|
||||
if name != nil && *name != story.Name { |
|
||||
changes["name"] = *name |
|
||||
story.Name = *name |
|
||||
} |
|
||||
if category != nil && *category != story.Category { |
|
||||
changes["category"] = *category |
|
||||
story.Category = *category |
|
||||
} |
|
||||
if listed != nil && *listed != story.Listed { |
|
||||
changes["listed"] = *listed |
|
||||
story.Listed = *listed |
|
||||
} |
|
||||
if open != nil && *open != story.Open { |
|
||||
changes["open"] = *open |
|
||||
story.Open = *open |
|
||||
} |
|
||||
if fictionalDate != nil && !fictionalDate.Equal(story.FictionalDate) { |
|
||||
changes["fictionalDate"] = *fictionalDate |
|
||||
story.FictionalDate = *fictionalDate |
|
||||
} |
|
||||
|
|
||||
if len(changes) == 0 { |
|
||||
return story, nil |
|
||||
} |
|
||||
|
|
||||
err := collection.UpdateId(story.ID, bson.M{"$set": changes}) |
|
||||
if err != nil { |
|
||||
return models.Story{}, err |
|
||||
} |
|
||||
|
|
||||
return story, nil |
|
||||
} |
|
@ -1,11 +0,0 @@ |
|||||
package stories |
|
||||
|
|
||||
import ( |
|
||||
"git.aiterp.net/rpdata/api/models" |
|
||||
"github.com/globalsign/mgo/bson" |
|
||||
) |
|
||||
|
|
||||
// FindID finds a story by ID
|
|
||||
func FindID(id string) (models.Story, error) { |
|
||||
return find(bson.M{"_id": id}) |
|
||||
} |
|
@ -1,67 +0,0 @@ |
|||||
package stories |
|
||||
|
|
||||
import ( |
|
||||
"time" |
|
||||
|
|
||||
"git.aiterp.net/rpdata/api/models" |
|
||||
"github.com/globalsign/mgo/bson" |
|
||||
) |
|
||||
|
|
||||
// Filter for stories.List
|
|
||||
type Filter struct { |
|
||||
Author *string |
|
||||
Tags []models.Tag |
|
||||
EarliestFictionalDate time.Time |
|
||||
LatestFictionalDate time.Time |
|
||||
Category *models.StoryCategory |
|
||||
Open *bool |
|
||||
Unlisted *bool |
|
||||
Limit int |
|
||||
} |
|
||||
|
|
||||
// List lists stories by any non-zero criteria passed with it.
|
|
||||
func List(filter *Filter) ([]models.Story, error) { |
|
||||
query := bson.M{"listed": true} |
|
||||
limit := 0 |
|
||||
|
|
||||
if filter != nil { |
|
||||
if filter.Author != nil { |
|
||||
query["author"] = *filter.Author |
|
||||
} |
|
||||
|
|
||||
if len(filter.Tags) > 0 { |
|
||||
query["tags"] = bson.M{"$in": filter.Tags} |
|
||||
} |
|
||||
|
|
||||
if !filter.EarliestFictionalDate.IsZero() && !filter.LatestFictionalDate.IsZero() { |
|
||||
query["fictionalDate"] = bson.M{ |
|
||||
"$gte": filter.EarliestFictionalDate, |
|
||||
"$lt": filter.LatestFictionalDate, |
|
||||
} |
|
||||
} else if !filter.LatestFictionalDate.IsZero() { |
|
||||
query["fictionalDate"] = bson.M{ |
|
||||
"$lt": filter.LatestFictionalDate, |
|
||||
} |
|
||||
} else if !filter.EarliestFictionalDate.IsZero() { |
|
||||
query["fictionalDate"] = bson.M{ |
|
||||
"$gte": filter.EarliestFictionalDate, |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if filter.Category != nil { |
|
||||
query["category"] = *filter.Category |
|
||||
} |
|
||||
|
|
||||
if filter.Open != nil { |
|
||||
query["open"] = *filter.Open |
|
||||
} |
|
||||
|
|
||||
if filter.Unlisted != nil { |
|
||||
query["listed"] = !*filter.Unlisted |
|
||||
} |
|
||||
|
|
||||
limit = filter.Limit |
|
||||
} |
|
||||
|
|
||||
return list(query, limit) |
|
||||
} |
|
@ -1,34 +0,0 @@ |
|||||
package stories |
|
||||
|
|
||||
import ( |
|
||||
"errors" |
|
||||
|
|
||||
"git.aiterp.net/rpdata/api/models" |
|
||||
"github.com/globalsign/mgo/bson" |
|
||||
) |
|
||||
|
|
||||
// ErrTagNotExists is an error returned by Story.RemoveTag
|
|
||||
var ErrTagNotExists = errors.New("Tag does not exist on story") |
|
||||
|
|
||||
// RemoveTag removes a tag to the story. It returns ErrTagNotExists if the tag does not exist.
|
|
||||
func RemoveTag(story models.Story, tag models.Tag) (models.Story, error) { |
|
||||
index := -1 |
|
||||
for i := range story.Tags { |
|
||||
if story.Tags[i].Equal(tag) { |
|
||||
index = i |
|
||||
break |
|
||||
} |
|
||||
} |
|
||||
if index == -1 { |
|
||||
return models.Story{}, ErrTagNotExists |
|
||||
} |
|
||||
|
|
||||
err := collection.UpdateId(story.ID, bson.M{"$pull": bson.M{"tags": tag}}) |
|
||||
if err != nil { |
|
||||
return models.Story{}, err |
|
||||
} |
|
||||
|
|
||||
story.Tags = append(story.Tags[:index], story.Tags[index+1:]...) |
|
||||
|
|
||||
return story, nil |
|
||||
} |
|
@ -1,10 +0,0 @@ |
|||||
package stories |
|
||||
|
|
||||
import ( |
|
||||
"git.aiterp.net/rpdata/api/models" |
|
||||
) |
|
||||
|
|
||||
// Remove the story from the database
|
|
||||
func Remove(story models.Story) (models.Story, error) { |
|
||||
return story, collection.RemoveId(story.ID) |
|
||||
} |
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue