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 }