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