Browse Source

Changed tags to allow same-name taggs with different categories, added tag list

master
Gisle Aune 7 years ago
parent
commit
85bf3a73db
  1. 46
      controllers/listcontroller.go
  2. 34
      controllers/tagcontroller.go
  3. 1
      main.go
  4. 43
      model/tag.go
  5. 1
      view/renderer.go
  6. 2
      view/templates/base/default.tmpl
  7. 6
      view/templates/index.tmpl
  8. 4
      view/templates/page/view.tmpl
  9. 48
      view/templates/tags/list.tmpl
  10. 46
      viewmodel/taglist.go

46
controllers/listcontroller.go

@ -52,7 +52,7 @@ func listFiltered(category model.PageCategory) wrouter.FunctionHandlerFunc {
vm := viewmodel.PageList{} vm := viewmodel.PageList{}
if tagName != "" { if tagName != "" {
tag, err := model.FindTag("name", tagName)
tag, err := model.FindTag("name", tagName, "")
if err != nil { if err != nil {
response.Text(w, 404, err.Error()) response.Text(w, 404, err.Error())
return true return true
@ -79,11 +79,53 @@ func listFiltered(category model.PageCategory) wrouter.FunctionHandlerFunc {
} }
} }
func listTagged(tagType string) wrouter.FunctionHandlerFunc {
return func(path string, w http.ResponseWriter, req *http.Request, user *auth.User) bool {
var err error
if req.Method != "GET" || strings.LastIndex(req.URL.Path, "/") >= len(path) {
return false
}
tagName := strings.Replace(req.URL.Path[len(path):], "_", " ", -1)
vm := viewmodel.PageList{}
if tagName == "" {
return false
}
tag, err := model.FindTag("name", tagName, tagType)
if err != nil {
response.Text(w, 404, err.Error())
return true
}
vm.ActiveTag = *tag
vm.Headers, err = model.ListHeadersByTag("", tag)
vm.Categories = model.PageCategories
vm.Setup(user)
if err != nil {
response.Text(w, 500, err.Error())
return true
}
view.Render(w, "index", 200, vm)
return true
}
}
func init() { func init() {
ListController.Function("/", listIndex) ListController.Function("/", listIndex)
ListController.Function("/", listFiltered(model.PageCategory{Key: ""}))
for _, category := range model.PageCategories { for _, category := range model.PageCategories {
ListController.Function(fmt.Sprintf("/%s/", strings.ToLower(category.Plural)), listFiltered(category)) ListController.Function(fmt.Sprintf("/%s/", strings.ToLower(category.Plural)), listFiltered(category))
} }
for _, tagType := range model.TagTypes {
ListController.Function(fmt.Sprintf("/%s/", strings.ToLower(tagType)), listTagged(tagType))
}
} }

34
controllers/tagcontroller.go

@ -0,0 +1,34 @@
package controllers
import (
"net/http"
"git.aiterp.net/AiteRP/aitestory/view"
"git.aiterp.net/AiteRP/aitestory/viewmodel"
"git.aiterp.net/AiteRP/aitestory/model"
"git.aiterp.net/gisle/wrouter"
"git.aiterp.net/gisle/wrouter/auth"
)
// TagController serves and handles the tag managment pages
var TagController = wrouter.Router{}
func tagList(path string, w http.ResponseWriter, req *http.Request, user *auth.User) bool {
//var err error
if (req.Method != "GET" && req.Method != "POST") || len(req.URL.Path) > len(path) {
return false
}
tl := viewmodel.TagList{}
tl.Tags, _ = model.ListTags()
tl.Setup(user)
view.Render(w, "tags/list", 200, tl)
return true
}
func init() {
TagController.Function("/", tagList)
}

1
main.go

@ -10,6 +10,7 @@ func main() {
auth.Register(&controllers.WikiAthenticator{}) auth.Register(&controllers.WikiAthenticator{})
router.Mount("/user", &controllers.UserController) router.Mount("/user", &controllers.UserController)
router.Mount("/page", &controllers.PageController) router.Mount("/page", &controllers.PageController)
router.Mount("/tags", &controllers.TagController)
router.Mount("/", &controllers.ListController) router.Mount("/", &controllers.ListController)
router.Static("/ui/", server.Main.Config.Directories.UI) router.Static("/ui/", server.Main.Config.Directories.UI)

43
model/tag.go

@ -129,8 +129,9 @@ func (tag Tag) Hook() string {
return strings.Replace(tag.Name, " ", "_", -1) return strings.Replace(tag.Name, " ", "_", -1)
} }
// FindTag finds a tag by ID
func FindTag(key string, id string) (*Tag, error) {
// 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 db := server.Main.DB
// Make damn sure that – should this take user data as key – it // Make damn sure that – should this take user data as key – it
@ -139,7 +140,24 @@ func FindTag(key string, id string) (*Tag, error) {
return nil, errors.New("invalid key") return nil, errors.New("invalid key")
} }
rows, err := db.Query("SELECT id,type,name FROM `tag` WHERE "+key+"=? AND disabled=false", id)
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 { if err != nil {
return nil, err return nil, err
} }
@ -156,7 +174,7 @@ func FindTag(key string, id string) (*Tag, error) {
// EnsureTag finds or creates a tag. // EnsureTag finds or creates a tag.
func EnsureTag(tagType, tagName string) (*Tag, error) { func EnsureTag(tagType, tagName string) (*Tag, error) {
// Try to find it first // Try to find it first
tag, err := FindTag("name", tagName)
tag, err := FindTag("name", tagName, tagType)
if err != nil && err.Error() != "not found" { // You saw nothing if err != nil && err.Error() != "not found" { // You saw nothing
return nil, err return nil, err
} }
@ -179,7 +197,15 @@ func EnsureTag(tagType, tagName string) (*Tag, error) {
func ListTags() ([]Tag, error) { func ListTags() ([]Tag, error) {
db := server.Main.DB db := server.Main.DB
rows, err := db.Query("SELECT id,type,name FROM `tag` WHERE disabled=false")
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 { if err != nil {
return nil, err return nil, err
} }
@ -187,10 +213,15 @@ func ListTags() ([]Tag, error) {
results := make([]Tag, 0, 64) results := make([]Tag, 0, 64)
for rows.Next() { for rows.Next() {
count := 0
tag := Tag{} tag := Tag{}
rows.Scan(&tag.ID, &tag.Type, &tag.Name)
rows.Scan(&tag.ID, &tag.Type, &tag.Name, &count)
// Let's pretend unused tags don't exist. >_>
if count > 0 {
results = append(results, tag) results = append(results, tag)
} }
}
return results, nil return results, nil
} }

1
view/renderer.go

@ -83,4 +83,5 @@ func init() {
Register("page/edit", "base/default") Register("page/edit", "base/default")
Register("page/view", "base/default") Register("page/view", "base/default")
Register("page/delete", "base/default") Register("page/delete", "base/default")
Register("tags/list", "base/default")
} }

2
view/templates/base/default.tmpl

@ -17,7 +17,7 @@
<link rel="stylesheet" href="/ui/css/magic.css" /> <link rel="stylesheet" href="/ui/css/magic.css" />
<link rel="stylesheet" href="/ui/css/theme.css" /> <link rel="stylesheet" href="/ui/css/theme.css" />
<link rel="stylesheet" media="screen" href="/ui/fonts/SeanSans.css" type="text/css"/>
<link rel="stylesheet" media="screen" href="/ui/fonts/source-sans-pro/source-sans-pro.css" type="text/css"/>
<script type="text/javascript" src="/ui/js/background.js"></script> <script type="text/javascript" src="/ui/js/background.js"></script>

6
view/templates/index.tmpl

@ -30,7 +30,7 @@
<ul> <ul>
{{ range .Categories }} {{ range .Categories }}
<li class="{{ if eq .Key $.ActiveCategory.Key }}selected{{end}}"><a href="/{{.URLRoot}}/"><div class="mg-icon">{{.Icon}}</div><div class="mg-label">{{.Plural}}</div></a></li>
<li class="{{ if eq .Key $.ActiveCategory.Key }}selected{{end}}"><a href="/{{.Plural}}/"><div class="mg-icon">{{.Icon}}</div><div class="mg-label">{{.Plural}}</div></a></li>
{{ end }} {{ end }}
</ul> </ul>
@ -40,6 +40,10 @@
</ul> </ul>
{{ end }} {{ end }}
<ul>
<li><a href="/tags/"><div class="mg-icon">T</div><div class="mg-label">Tags</div></a></li>
</ul>
{{ if $.User.LoggedIn }} {{ if $.User.LoggedIn }}
<ul> <ul>
<li><a href="/page/create"><div class="mg-icon">+</div><div class="mg-label">Create</div></a></li> <li><a href="/page/create"><div class="mg-icon">+</div><div class="mg-label">Create</div></a></li>

4
view/templates/page/view.tmpl

@ -5,7 +5,7 @@
{{ end }} {{ end }}
{{ define "menu" }} {{ define "menu" }}
<a href="/page/{{$.Page.ID}}"><h1>Page</h1></a>
<a href="/"><h1>Page</h1></a>
{{ if $.Page.Dated}} {{ if $.Page.Dated}}
<div class="page-property">{{$.Page.FictionalDate | formatDate}}</div> <div class="page-property">{{$.Page.FictionalDate | formatDate}}</div>
{{ else }} {{ else }}
@ -14,7 +14,7 @@
<ul> <ul>
{{ range $.Page.Tags }} {{ range $.Page.Tags }}
<li><a href="/{{.Hook}}"><div class="mg-icon">{{.Icon}}</div><div class="mg-label {{.CSSCLass}}">{{.Name}}</div></a></li>
<li><a href="/{{.Type}}/{{.Hook}}"><div class="mg-icon">{{.Icon}}</div><div class="mg-label {{.CSSCLass}}">{{.Name}}</div></a></li>
{{ end }} {{ end }}
</ul> </ul>

48
view/templates/tags/list.tmpl

@ -0,0 +1,48 @@
{{ define "content" }}
<article>
<h1>All Tags</h1>
<div class="tag-list">
{{ range $tagHeader, $tags := .Map }}
<div class="tl-item">
<h2>{{ $tagHeader }}</h2>
<ul>
{{ range $tags }}
<li><a class="ttype-{{.Type | tolower }}" href="/{{.Type}}/{{.Name}}">{{ .Name }}</a></li>
{{ end }}
</ul>
</div>
{{ end }}
</div>
</article>
{{ end }}
{{ define "menu" }}
<a href="/"><h1>Tags</h1></a>
<ul>
<li><a href="/"><div class="mg-icon">&lt;</div><div class="mg-label">Back</div></a></li>
</ul>
{{ if $.User.LoggedIn }}
<ul>
<li><a href="/page/create"><div class="mg-icon">+</div><div class="mg-label">Create</div></a></li>
</ul>
<ul>
<li><a href="/user/logout"><div class="mg-icon">A</div><div class="mg-label">Logout</div></a></li>
</ul>
{{ else }}
<ul>
<li><a href="/user/login"><div class="mg-icon">A</div><div class="mg-label">Login</div></a></li>
</ul>
{{ end }}
{{ end }}
{{ define "head" }}
{{ end }}

46
viewmodel/taglist.go

@ -0,0 +1,46 @@
package viewmodel
import (
"fmt"
"git.aiterp.net/AiteRP/aitestory/model"
"git.aiterp.net/gisle/wrouter/auth"
)
// TagList is a view model for rendering the tag lists.
type TagList struct {
Base
Type string
Tags []model.Tag
TagCategories []string
tagMap map[string][]model.Tag
}
// Map lazy-loads the map of tags, which is only
// used when listing all of them.
func (tl TagList) Map() map[string][]model.Tag {
// Set up the map
tl.tagMap = make(map[string][]model.Tag, len(model.TagTypes))
for _, tagType := range model.TagTypes {
tl.tagMap[tagType] = make([]model.Tag, 0, 64)
}
// Organize
for _, tag := range tl.Tags {
tl.tagMap[tag.Type] = append(tl.tagMap[tag.Type], tag)
}
return tl.tagMap
}
// Setup sets up the page model and the base, and should
// be run after the details have been filled in.
func (tl *TagList) Setup(user *auth.User) {
title := tl.Type
if tl.Type != "" {
title = "All"
}
tl.setupBase(user, fmt.Sprintf("%s Tags", title))
}
Loading…
Cancel
Save