The backend for the AiteStory website
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

238 lines
4.9 KiB

package model
import (
"errors"
"fmt"
"strings"
"git.aiterp.net/AiteRP/aitestory/server"
"git.aiterp.net/gisle/wrouter/generate"
)
// TagTypes are the allowed values for Tag.Type
var TagTypes = []string{
"Location",
"Character",
"Event",
"Organization",
"Series",
}
// TagTitles are the titles to use when listing tags
var TagTitles = map[string]string{
"Location": "Locations",
"Character": "Characters",
"Event": "Plots & Events",
"Organization": "Organizations",
"Series": "Series",
}
// Tag describes a tag
type Tag struct {
ID string
Type string
Name string
}
// Icon is the API used to get the icon for the tag.
func (tag Tag) Icon() string {
return tag.Type[0:1]
}
// CSSCLass gets the CSS class that colors the tag on the
// page list
func (tag Tag) CSSCLass() string {
return fmt.Sprintf("ttype-%s", strings.ToLower(tag.Type))
}
// Insert adds the tag to the database, giving it a new unique ID
func (tag *Tag) Insert() error {
db := server.Main.DB
// Validate tag type
if err := tag.Validate(); err != nil {
return err
}
// Generate an ID if none exists
if tag.ID == "" {
tag.ID = generate.ID()
}
// Do the thing
_, err := db.Exec("INSERT INTO `tag` (id,type,name,disabled) VALUES (?,?,?,false)", tag.ID, tag.Type, tag.Name)
if err != nil {
return err
}
return nil
}
// Update saves the entry for the tag in the database
func (tag *Tag) Update() error {
db := server.Main.DB
// Validate tag type
if err := tag.Validate(); err != nil {
return err
}
// Do the thing
_, err := db.Exec("UPDATE `tag` SET type=?,name=? WHERE id=?", tag.Type, tag.Name, tag.ID)
if err != nil {
return err
}
return nil
}
// Delete removes a tag from the database
func (tag *Tag) Delete() error {
db := server.Main.DB
// Do the thing
results, err := db.Exec("DELETE FROM `tag` WHERE id=? LIMIT 1", tag.ID)
if err != nil {
return err
}
// Count the stuffs that were done things to
affected, err := results.RowsAffected()
if err != nil {
return err
}
if affected == 0 {
return errors.New("tag not found")
}
return nil
}
// Validate checks the name and type, and returns an error if they're not valid. It's
// ran by Update and Insert before doing anything
func (tag *Tag) Validate() error {
validType := false
for _, tagType := range TagTypes {
if tagType == tag.Type {
validType = true
break
}
}
if !validType {
return errors.New("invalid tag type")
}
// Validate tag name
if len(tag.Name) == 0 || len(tag.Name) > 64 {
return errors.New("invalid length")
}
return nil
}
// Path returns a path used for the tag's list page
func (tag Tag) Path() string {
return fmt.Sprintf("/%s/%s", strings.ToLower(tag.Type), strings.Replace(tag.Name, " ", "_", -1))
}
// CSSClass gets the tag's css class, which determines its color.
func (tag Tag) CSSClass() string {
return fmt.Sprintf("ttype-%s", strings.ToLower(tag.Type))
}
// FindTag finds a tag by ID. Leave tagtype blank to ignore it. Both key and
// id are checked against their respective lists to prevent SQL injection attacks.
func FindTag(key string, id string, tagType string) (*Tag, error) {
db := server.Main.DB
// Make damn sure that – should this take user data as key – it
// does not open up for a SQL injection attack
if key != "name" && key != "id" {
return nil, errors.New("invalid key")
}
query := "SELECT id,type,name FROM `tag` WHERE " + key + "=? AND disabled=false"
if tagType != "" {
// Prevent more shenanigans
found := false
for _, existing := range TagTypes {
if existing == tagType {
found = true
break
}
}
if !found {
return nil, errors.New("invalid tag type")
}
query += fmt.Sprintf(` AND type="%s"`, tagType)
}
rows, err := db.Query(query, id)
if err != nil {
return nil, err
}
defer rows.Close()
if !rows.Next() {
return nil, errors.New("not found")
}
tag := new(Tag)
rows.Scan(&tag.ID, &tag.Type, &tag.Name)
return tag, nil
}
// EnsureTag finds or creates a tag.
func EnsureTag(tagType, tagName string) (*Tag, error) {
// Try to find it first
tag, err := FindTag("name", tagName, tagType)
if err != nil && err.Error() != "not found" { // You saw nothing
return nil, err
}
// Failing that, make it
if tag == nil {
tag = new(Tag)
tag.Type = tagType
tag.Name = tagName
err = tag.Insert()
if err != nil {
return nil, err
}
}
return tag, nil
}
// ListTags finds all the tags, without filter.
func ListTags() ([]Tag, error) {
db := server.Main.DB
query := `
SELECT id,type,name,count(pt.page_id) FROM tag
LEFT JOIN page_tag AS pt ON (pt.tag_id = tag.id)
WHERE disabled=false
GROUP BY pt.tag_id
ORDER BY type,name
`
rows, err := db.Query(query)
if err != nil {
return nil, err
}
defer rows.Close()
results := make([]Tag, 0, 64)
for rows.Next() {
count := 0
tag := Tag{}
rows.Scan(&tag.ID, &tag.Type, &tag.Name, &count)
// Let's pretend unused tags don't exist. >_>
if count > 0 {
results = append(results, tag)
}
}
return results, nil
}