Browse Source
models: Painstakingly converted half the model package to a models & repostiry stucture. All binaries are probably dead at this point.
1.0
models: Painstakingly converted half the model package to a models & repostiry stucture. All binaries are probably dead at this point.
1.0
Gisle Aune
6 years ago
38 changed files with 878 additions and 64 deletions
-
22graph2/gqlgen.yml
-
6graph2/graph.go
-
11graph2/queries/channel.go
-
15graph2/queries/character.go
-
28graph2/queries/log.go
-
29graph2/queries/post.go
-
7graph2/queries/tags.go
-
5graph2/schema/root.gql
-
2graph2/schema/types/Log.gql
-
8graph2/schema/types/Post.gql
-
40graph2/types/log.go
-
17internal/loader/channel.go
-
24internal/loader/character.go
-
6internal/loader/loader.go
-
10models/channel.go
-
33models/channels/add.go
-
19models/channels/db.go
-
11models/channels/find.go
-
46models/channels/list.go
-
20models/character.go
-
48models/characters/add.go
-
55models/characters/db.go
-
16models/characters/find.go
-
61models/characters/list.go
-
16models/log.go
-
65models/logs/db.go
-
16models/logs/find.go
-
70models/logs/list.go
-
14models/post.go
-
56models/posts/db.go
-
14models/posts/find.go
-
57models/posts/list.go
-
1models/posts/search.go
-
0models/scalars/date.go
-
47models/tag-kind.go
-
7models/tag.go
-
14models/tags/db.go
-
26models/tags/list.go
@ -0,0 +1,40 @@ |
|||
package types |
|||
|
|||
import ( |
|||
"context" |
|||
"errors" |
|||
|
|||
"git.aiterp.net/rpdata/api/internal/loader" |
|||
"git.aiterp.net/rpdata/api/models" |
|||
"git.aiterp.net/rpdata/api/models/posts" |
|||
) |
|||
|
|||
type logResolver struct{} |
|||
|
|||
func (r *logResolver) Channel(ctx context.Context, log *models.Log) (models.Channel, error) { |
|||
loader := loader.FromContext(ctx) |
|||
if loader == nil { |
|||
return models.Channel{}, errors.New("no loader") |
|||
} |
|||
|
|||
return loader.Channel("name", log.ChannelName) |
|||
} |
|||
|
|||
func (r *logResolver) Characters(ctx context.Context, log *models.Log) ([]models.Character, error) { |
|||
loader := loader.FromContext(ctx) |
|||
if loader == nil { |
|||
return nil, errors.New("no loader") |
|||
} |
|||
|
|||
return loader.Characters("id", log.CharacterIDs...) |
|||
} |
|||
|
|||
func (r *logResolver) Posts(ctx context.Context, log *models.Log, kinds []string) ([]models.Post, error) { |
|||
return posts.List(&posts.Filter{ |
|||
LogID: &log.ShortID, |
|||
Kind: kinds, |
|||
}) |
|||
} |
|||
|
|||
// LogResolver is a resolver
|
|||
var LogResolver logResolver |
@ -0,0 +1,10 @@ |
|||
package models |
|||
|
|||
// A Channel represents information abount an IRC RP channel, and whether it should be logged
|
|||
type Channel struct { |
|||
Name string `bson:"_id"` |
|||
Logged bool `bson:"logged"` |
|||
Hub bool `bson:"hub"` |
|||
EventName string `bson:"event,omitempty"` |
|||
LocationName string `bson:"location,omitempty"` |
|||
} |
@ -0,0 +1,33 @@ |
|||
package channels |
|||
|
|||
import ( |
|||
"errors" |
|||
"strings" |
|||
|
|||
"git.aiterp.net/rpdata/api/models" |
|||
) |
|||
|
|||
// ErrInvalidName is an error for an invalid channel name.
|
|||
var ErrInvalidName = errors.New("Invalid channel name") |
|||
|
|||
// Add creates a new channel.
|
|||
func Add(name string, logged, hub bool, event, location string) (models.Channel, error) { |
|||
if len(name) < 3 && !strings.HasPrefix(name, "#") { |
|||
return models.Channel{}, ErrInvalidName |
|||
} |
|||
|
|||
channel := models.Channel{ |
|||
Name: name, |
|||
Logged: logged, |
|||
Hub: hub, |
|||
EventName: event, |
|||
LocationName: location, |
|||
} |
|||
|
|||
err := collection.Insert(channel) |
|||
if err != nil { |
|||
return models.Channel{}, err |
|||
} |
|||
|
|||
return channel, nil |
|||
} |
@ -0,0 +1,19 @@ |
|||
package channels |
|||
|
|||
import ( |
|||
"git.aiterp.net/rpdata/api/internal/store" |
|||
"github.com/globalsign/mgo" |
|||
) |
|||
|
|||
var collection *mgo.Collection |
|||
|
|||
func init() { |
|||
store.HandleInit(func(db *mgo.Database) { |
|||
collection = db.C("common.channels") |
|||
|
|||
collection.EnsureIndexKey("logged") |
|||
collection.EnsureIndexKey("hub") |
|||
collection.EnsureIndexKey("event") |
|||
collection.EnsureIndexKey("location") |
|||
}) |
|||
} |
@ -0,0 +1,11 @@ |
|||
package channels |
|||
|
|||
import "git.aiterp.net/rpdata/api/models" |
|||
|
|||
// FindName finds a channel by its id (its name).
|
|||
func FindName(name string) (models.Channel, error) { |
|||
channel := models.Channel{} |
|||
err := collection.FindId(name).One(&channel) |
|||
|
|||
return channel, err |
|||
} |
@ -0,0 +1,46 @@ |
|||
package channels |
|||
|
|||
import ( |
|||
"git.aiterp.net/rpdata/api/models" |
|||
"github.com/globalsign/mgo/bson" |
|||
) |
|||
|
|||
// Filter for searching
|
|||
type Filter struct { |
|||
Logged *bool `json:"logged"` |
|||
EventName string `json:"eventName"` |
|||
LocationName string `json:"locationName"` |
|||
} |
|||
|
|||
// List finds channels, if logged is true it will be limited to logged
|
|||
// channels
|
|||
func List(filter *Filter) ([]models.Channel, error) { |
|||
query := bson.M{} |
|||
|
|||
if filter != nil { |
|||
if filter.Logged != nil { |
|||
query["logged"] = *filter.Logged |
|||
} |
|||
if filter.EventName != "" { |
|||
query["eventName"] = filter.EventName |
|||
} |
|||
if filter.LocationName != "" { |
|||
query["locationName"] = filter.LocationName |
|||
} |
|||
} |
|||
|
|||
channels := make([]models.Channel, 0, 128) |
|||
err := collection.Find(query).All(&channels) |
|||
|
|||
return channels, err |
|||
} |
|||
|
|||
// ListNames finds channels by the names provided
|
|||
func ListNames(names ...string) ([]models.Channel, error) { |
|||
query := bson.M{"_id": bson.M{"$in": names}} |
|||
|
|||
channels := make([]models.Channel, 0, 32) |
|||
err := collection.Find(query).All(&channels) |
|||
|
|||
return channels, err |
|||
} |
@ -0,0 +1,20 @@ |
|||
package models |
|||
|
|||
// Character is a common data model representing an RP character or NPC.
|
|||
type Character struct { |
|||
ID string `json:"id" bson:"_id"` |
|||
Nicks []string `json:"nicks" bson:"nicks"` |
|||
Name string `json:"name" bson:"name"` |
|||
ShortName string `json:"shortName" bson:"shortName"` |
|||
Author string `json:"author" bson:"author"` |
|||
Description string `json:"description" bson:"description"` |
|||
} |
|||
|
|||
// Nick gets the character's nick.
|
|||
func (character *Character) Nick() *string { |
|||
if len(character.Nicks[0]) == 0 { |
|||
return nil |
|||
} |
|||
|
|||
return &character.Nicks[0] |
|||
} |
@ -0,0 +1,48 @@ |
|||
package characters |
|||
|
|||
import ( |
|||
"errors" |
|||
"strconv" |
|||
"strings" |
|||
|
|||
"git.aiterp.net/rpdata/api/model/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", "models.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 |
|||
} |
@ -0,0 +1,55 @@ |
|||
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) |
|||
} |
|||
}) |
|||
} |
@ -0,0 +1,16 @@ |
|||
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}) |
|||
} |
@ -0,0 +1,61 @@ |
|||
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"` |
|||
Logged *bool `json:"logged"` |
|||
} |
|||
|
|||
// 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"] = bson.M{"$in": filter.Nicks} |
|||
} else if len(filter.Nicks) == 1 { |
|||
query["nicks"] = filter.Nicks[0] |
|||
} |
|||
|
|||
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.Logged != nil { |
|||
query["logged"] = *filter.Logged |
|||
} |
|||
|
|||
if filter.Author != nil { |
|||
query["author"] = *filter.Author |
|||
} |
|||
|
|||
if filter.Search != nil { |
|||
query["$text"] = bson.M{"$search": *filter.Search} |
|||
} |
|||
} |
|||
|
|||
return list(query) |
|||
} |
@ -0,0 +1,16 @@ |
|||
package models |
|||
|
|||
import "time" |
|||
|
|||
// Log is the header/session for a log file.
|
|||
type Log struct { |
|||
ID string `bson:"_id"` |
|||
ShortID string `bson:"shortId"` |
|||
Date time.Time `bson:"date"` |
|||
ChannelName string `bson:"channel"` |
|||
EventName string `bson:"event,omitempty"` |
|||
Title string `bson:"title,omitempty"` |
|||
Description string `bson:"description,omitempty"` |
|||
Open bool `bson:"open"` |
|||
CharacterIDs []string `bson:"characterIds"` |
|||
} |
@ -0,0 +1,65 @@ |
|||
package logs |
|||
|
|||
import ( |
|||
"fmt" |
|||
"log" |
|||
"time" |
|||
|
|||
"git.aiterp.net/rpdata/api/internal/store" |
|||
"git.aiterp.net/rpdata/api/models" |
|||
"github.com/globalsign/mgo" |
|||
) |
|||
|
|||
var collection *mgo.Collection |
|||
var postCollection *mgo.Collection |
|||
|
|||
func find(query interface{}) (models.Log, error) { |
|||
log := models.Log{} |
|||
err := collection.Find(query).One(&log) |
|||
if err != nil { |
|||
return models.Log{}, err |
|||
} |
|||
|
|||
return log, nil |
|||
} |
|||
|
|||
func list(query interface{}, limit int) ([]models.Log, error) { |
|||
logs := make([]models.Log, 0, 64) |
|||
err := collection.Find(query).Limit(limit).Sort("-date").All(&logs) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
return logs, nil |
|||
} |
|||
|
|||
func iter(query interface{}, limit int) *mgo.Iter { |
|||
return collection.Find(query).Sort("-date").Limit(limit).Batch(8).Iter() |
|||
} |
|||
|
|||
func makeLogID(date time.Time, channel string) string { |
|||
return fmt.Sprintf("%s%03d_%s", date.UTC().Format("2006-01-02_150405"), (date.Nanosecond() / int(time.Millisecond/time.Nanosecond)), channel[1:]) |
|||
} |
|||
|
|||
func init() { |
|||
store.HandleInit(func(db *mgo.Database) { |
|||
collection = db.C("logbot3.logs") |
|||
postCollection = db.C("logbo3.posts") |
|||
|
|||
collection.EnsureIndexKey("date") |
|||
collection.EnsureIndexKey("channel") |
|||
collection.EnsureIndexKey("characterIds") |
|||
collection.EnsureIndexKey("event") |
|||
collection.EnsureIndex(mgo.Index{ |
|||
Key: []string{"channel", "open"}, |
|||
}) |
|||
err := collection.EnsureIndex(mgo.Index{ |
|||
Key: []string{"shortId"}, |
|||
Unique: true, |
|||
DropDups: true, |
|||
}) |
|||
if err != nil { |
|||
log.Fatalln("init logbot3.logs:", err) |
|||
} |
|||
}) |
|||
} |
@ -0,0 +1,16 @@ |
|||
package logs |
|||
|
|||
import ( |
|||
"git.aiterp.net/rpdata/api/models" |
|||
"github.com/globalsign/mgo/bson" |
|||
) |
|||
|
|||
// FindID finds a log either by it's ID or short ID.
|
|||
func FindID(id string) (models.Log, error) { |
|||
return find(bson.M{ |
|||
"$or": []bson.M{ |
|||
bson.M{"_id": id}, |
|||
bson.M{"shortId": id}, |
|||
}, |
|||
}) |
|||
} |
@ -0,0 +1,70 @@ |
|||
package logs |
|||
|
|||
import ( |
|||
"git.aiterp.net/rpdata/api/models" |
|||
"github.com/globalsign/mgo/bson" |
|||
) |
|||
|
|||
// Filter for the List() function
|
|||
type Filter struct { |
|||
Search *string |
|||
Characters *[]string |
|||
Channels *[]string |
|||
Events *[]string |
|||
Open *bool |
|||
Limit int |
|||
} |
|||
|
|||
// List lists all logs
|
|||
func List(filter *Filter) ([]models.Log, error) { |
|||
query := bson.M{} |
|||
limit := 0 |
|||
|
|||
if filter != nil { |
|||
// Run a text search
|
|||
if filter.Search != nil { |
|||
searchResults, err := search(*filter.Search) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
// Posts always use shortId to refer to the log
|
|||
query["shortId"] = bson.M{"$in": searchResults} |
|||
} |
|||
|
|||
// Find logs including any of the specified events and channels
|
|||
if filter.Channels != nil { |
|||
query["channel"] = bson.M{"$in": *filter.Channels} |
|||
} |
|||
if filter.Events != nil { |
|||
query["event"] = bson.M{"$in": *filter.Events} |
|||
} |
|||
|
|||
// Find logs including all of the specified character IDs.
|
|||
if filter.Characters != nil { |
|||
query["characterIds"] = bson.M{"$all": *filter.Characters} |
|||
} |
|||
|
|||
// Limit to only open logs
|
|||
if filter.Open != nil { |
|||
query["open"] = *filter.Open |
|||
} |
|||
|
|||
// Set the limit from the filter
|
|||
limit = filter.Limit |
|||
} |
|||
|
|||
return list(query, limit) |
|||
} |
|||
|
|||
func search(text string) ([]string, error) { |
|||
query := bson.M{ |
|||
"$text": bson.M{"$search": text}, |
|||
"logId": bson.M{"$ne": nil}, |
|||
} |
|||
|
|||
ids := make([]string, 0, 64) |
|||
err := postCollection.Find(query).Distinct("logId", ids) |
|||
|
|||
return ids, err |
|||
} |
@ -0,0 +1,14 @@ |
|||
package models |
|||
|
|||
import "time" |
|||
|
|||
// A Post is a part of a log file.
|
|||
type Post struct { |
|||
ID string `bson:"_id"` |
|||
LogID string `bson:"logId"` |
|||
Time time.Time `bson:"time"` |
|||
Kind string `bson:"kind"` |
|||
Nick string `bson:"nick"` |
|||
Text string `bson:"text"` |
|||
Position int `bson:"position"` |
|||
} |
@ -0,0 +1,56 @@ |
|||
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) |
|||
} |
|||
}) |
|||
} |
@ -0,0 +1,14 @@ |
|||
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}) |
|||
} |
@ -0,0 +1,57 @@ |
|||
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 |
|||
} |
@ -0,0 +1 @@ |
|||
package posts |
@ -0,0 +1,47 @@ |
|||
package models |
|||
|
|||
import ( |
|||
"fmt" |
|||
"io" |
|||
) |
|||
|
|||
// TagKind represents the kind of tags.
|
|||
type TagKind string |
|||
|
|||
const ( |
|||
// TagKindOrganization is a tag kind, see GraphQL documentation.
|
|||
TagKindOrganization TagKind = "Organization" |
|||
|
|||
// TagKindCharacter is a tag kind, see GraphQL documentation.
|
|||
TagKindCharacter TagKind = "Character" |
|||
|
|||
// TagKindLocation is a tag kind, see GraphQL documentation.
|
|||
TagKindLocation TagKind = "Location" |
|||
|
|||
// TagKindEvent is a tag kind, see GraphQL documentation.
|
|||
TagKindEvent TagKind = "Event" |
|||
|
|||
// TagKindSeries is a tag kind, see GraphQL documentation.
|
|||
TagKindSeries TagKind = "Series" |
|||
) |
|||
|
|||
// UnmarshalGQL unmarshals
|
|||
func (e *TagKind) UnmarshalGQL(v interface{}) error { |
|||
str, ok := v.(string) |
|||
if !ok { |
|||
return fmt.Errorf("enums must be strings") |
|||
} |
|||
|
|||
*e = TagKind(str) |
|||
switch *e { |
|||
case TagKindOrganization, TagKindCharacter, TagKindLocation, TagKindEvent, TagKindSeries: |
|||
return nil |
|||
default: |
|||
return fmt.Errorf("%s is not a valid TagKind", str) |
|||
} |
|||
} |
|||
|
|||
// MarshalGQL turns it into a JSON string
|
|||
func (e TagKind) MarshalGQL(w io.Writer) { |
|||
fmt.Fprint(w, "\""+string(e), "\"") |
|||
} |
@ -0,0 +1,7 @@ |
|||
package models |
|||
|
|||
// A Tag associates a story with other content, like other stories, logs and more.
|
|||
type Tag struct { |
|||
Kind TagKind `bson:"kind"` |
|||
Name string `bson:"name"` |
|||
} |
@ -0,0 +1,14 @@ |
|||
package tags |
|||
|
|||
import ( |
|||
"git.aiterp.net/rpdata/api/internal/store" |
|||
"github.com/globalsign/mgo" |
|||
) |
|||
|
|||
var storyCollection *mgo.Collection |
|||
|
|||
func init() { |
|||
store.HandleInit(func(db *mgo.Database) { |
|||
storyCollection = db.C("story.stories") |
|||
}) |
|||
} |
@ -0,0 +1,26 @@ |
|||
package tags |
|||
|
|||
import ( |
|||
"sort" |
|||
"strings" |
|||
|
|||
"git.aiterp.net/rpdata/api/models" |
|||
"github.com/globalsign/mgo/bson" |
|||
) |
|||
|
|||
// List lists all tags
|
|||
func List() ([]models.Tag, error) { |
|||
tags := make([]models.Tag, 0, 64) |
|||
err := storyCollection.Find(bson.M{"listed": true, "tags": bson.M{"$ne": nil}}).Distinct("tags", &tags) |
|||
|
|||
sort.Slice(tags, func(i, j int) bool { |
|||
kindCmp := strings.Compare(string(tags[i].Kind), string(tags[j].Kind)) |
|||
if kindCmp != 0 { |
|||
return kindCmp < 0 |
|||
} |
|||
|
|||
return strings.Compare(tags[i].Name, tags[j].Name) < 0 |
|||
}) |
|||
|
|||
return tags, err |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue