Browse Source

Deleted a bunch of old db code.

thegreatrefactor
Gisle Aune 5 years ago
parent
commit
690eb4f121
  1. 4
      cmd/rpdata-server/main.go
  2. 25
      models/changes/db.go
  3. 42
      models/changes/list.go
  4. 125
      models/changes/submit.go
  5. 90
      models/changes/subscribe.go
  6. 40
      models/chapters/add.go
  7. 61
      models/chapters/db.go
  8. 46
      models/chapters/edit.go
  9. 11
      models/chapters/find.go
  10. 11
      models/chapters/list.go
  11. 28
      models/chapters/move.go
  12. 24
      models/chapters/remove.go
  13. 27
      models/characters/add-nick.go
  14. 48
      models/characters/add.go
  15. 55
      models/characters/db.go
  16. 36
      models/characters/edit.go
  17. 16
      models/characters/find.go
  18. 56
      models/characters/list.go
  19. 32
      models/characters/remove-nick.go
  20. 13
      models/characters/remove.go
  21. 35
      models/comments/add.go
  22. 66
      models/comments/db.go
  23. 44
      models/comments/edit.go
  24. 11
      models/comments/find.go
  25. 11
      models/comments/list.go
  26. 17
      models/comments/remove.go
  27. 36
      models/posts/add-many.go
  28. 51
      models/posts/add.go
  29. 56
      models/posts/db.go
  30. 44
      models/posts/edit.go
  31. 14
      models/posts/find.go
  32. 57
      models/posts/list.go
  33. 69
      models/posts/move.go
  34. 33
      models/posts/remove.go
  35. 29
      models/stories/add-tag.go
  36. 30
      models/stories/add.go
  37. 59
      models/stories/db.go
  38. 45
      models/stories/edit.go
  39. 11
      models/stories/find.go
  40. 67
      models/stories/list.go
  41. 34
      models/stories/remove-tag.go
  42. 10
      models/stories/remove.go

4
cmd/rpdata-server/main.go

@ -13,7 +13,6 @@ import (
"git.aiterp.net/rpdata/api/internal/auth"
"git.aiterp.net/rpdata/api/internal/config"
"git.aiterp.net/rpdata/api/internal/instrumentation"
"git.aiterp.net/rpdata/api/internal/loader"
"git.aiterp.net/rpdata/api/internal/store"
"git.aiterp.net/rpdata/api/models"
"git.aiterp.net/rpdata/api/services"
@ -102,9 +101,6 @@ func queryHandler(services *services.Bundle) http.HandlerFunc {
handler.ResolverMiddleware(instrumentation.ResolverMiddleware()),
)
return func(w http.ResponseWriter, r *http.Request) {
l := loader.New()
r = r.WithContext(l.ToContext(r.Context()))
// >_>
if strings.HasPrefix(r.Header.Get("Authorization"), "Bearer of the curse") {
w.Header().Set("X-Emerald-Herald", "Seek souls. Larger, more powerful souls.")

25
models/changes/db.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")
})
}

42
models/changes/list.go

@ -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)
}

125
models/changes/submit.go

@ -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
}

90
models/changes/subscribe.go

@ -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)
}

40
models/chapters/add.go

@ -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
}

61
models/chapters/db.go

@ -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")
})
}

46
models/chapters/edit.go

@ -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
}

11
models/chapters/find.go

@ -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})
}

11
models/chapters/list.go

@ -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})
}

28
models/chapters/move.go

@ -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
}

24
models/chapters/remove.go

@ -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
}

27
models/characters/add-nick.go

@ -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
}

48
models/characters/add.go

@ -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
}

55
models/characters/db.go

@ -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)
}
})
}

36
models/characters/edit.go

@ -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
}

16
models/characters/find.go

@ -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})
}

56
models/characters/list.go

@ -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)
}

32
models/characters/remove-nick.go

@ -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
}

13
models/characters/remove.go

@ -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
}

35
models/comments/add.go

@ -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
}

66
models/comments/db.go

@ -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")
})
}

44
models/comments/edit.go

@ -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
}

11
models/comments/find.go

@ -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})
}

11
models/comments/list.go

@ -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)
}

17
models/comments/remove.go

@ -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
}

36
models/posts/add-many.go

@ -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
}

51
models/posts/add.go

@ -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)
}

56
models/posts/db.go

@ -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)
}
})
}

44
models/posts/edit.go

@ -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
}

14
models/posts/find.go

@ -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})
}

57
models/posts/list.go

@ -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
}

69
models/posts/move.go

@ -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
}

33
models/posts/remove.go

@ -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
}

29
models/stories/add-tag.go

@ -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
}

30
models/stories/add.go

@ -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
}

59
models/stories/db.go

@ -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")
})
}

45
models/stories/edit.go

@ -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
}

11
models/stories/find.go

@ -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})
}

67
models/stories/list.go

@ -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)
}

34
models/stories/remove-tag.go

@ -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
}

10
models/stories/remove.go

@ -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)
}
Loading…
Cancel
Save