@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user