gosora/bot_routes.go
Azareal 964d219407 Added support for per-topic view counters.
Added support for shutdown tasks.
View counters are now saved on graceful shutdown.
Dynamic routes are now tracked by the route view counter.
The uploads route should now be tracked by the route view counter.
Added a WYSIWYG Editor to the profiles for Cosora.
2017-12-24 22:08:35 +00:00

279 lines
8.0 KiB
Go

package main
import (
"log"
"net/http"
"strconv"
"strings"
"./common"
"./query_gen/lib"
)
// TODO: Make this a static file somehow? Is it possible for us to put this file somewhere else?
// TODO: Add an API so that plugins can register disallowed areas. E.g. /guilds/join for plugin_guilds
func routeRobotsTxt(w http.ResponseWriter, r *http.Request) common.RouteError {
_, _ = w.Write([]byte(`User-agent: *
Disallow: /panel/
Disallow: /topics/create/
Disallow: /user/edit/
Disallow: /accounts/
Disallow: /report/
`))
return nil
}
var xmlInternalError = []byte(`<?xml version="1.0" encoding="UTF-8"?>
<error>A problem has occured</error>`)
var sitemapPageCap = 40000 // 40k, bump it up to 50k once we gzip this? Does brotli work on sitemaps?
func writeXMLHeader(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/xml")
w.Write([]byte("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"))
}
// TODO: Keep track of when a sitemap was last modifed and add a lastmod element for it
func routeSitemapXml(w http.ResponseWriter, r *http.Request) common.RouteError {
var sslBit string
if common.Site.EnableSsl {
sslBit = "s"
}
var sitemapItem = func(path string) {
w.Write([]byte(`<sitemap>
<loc>http` + sslBit + `://` + common.Site.URL + "/" + path + `</loc>
</sitemap>
`))
}
writeXMLHeader(w, r)
w.Write([]byte("<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n"))
sitemapItem("sitemaps/topics.xml")
//sitemapItem("sitemaps/forums.xml")
//sitemapItem("sitemaps/users.xml")
w.Write([]byte("</sitemapindex>"))
return nil
}
type FuzzyRoute struct {
Path string
Handle func(http.ResponseWriter, *http.Request, int) common.RouteError
}
// TODO: Add a sitemap API and clean things up
// TODO: ^-- Make sure that the API is concurrent
// TODO: Add a social group sitemap
var sitemapRoutes = map[string]func(http.ResponseWriter, *http.Request) common.RouteError{
"forums.xml": routeSitemapForums,
"topics.xml": routeSitemapTopics,
}
// TODO: Use a router capable of parsing this rather than hard-coding the logic in
var fuzzySitemapRoutes = map[string]FuzzyRoute{
"topics_page_": FuzzyRoute{"topics_page_(%d).xml", routeSitemapTopic},
}
func sitemapSwitch(w http.ResponseWriter, r *http.Request) common.RouteError {
var path = r.URL.Path[len("/sitemaps/"):]
for name, fuzzy := range fuzzySitemapRoutes {
if strings.HasPrefix(path, name) && strings.HasSuffix(path, ".xml") {
var spath = strings.TrimPrefix(path, name)
spath = strings.TrimSuffix(spath, ".xml")
page, err := strconv.Atoi(spath)
if err != nil {
if common.Dev.DebugMode {
log.Printf("Unable to convert string '%s' to integer in fuzzy route", spath)
}
return common.NotFound(w, r)
}
return fuzzy.Handle(w, r, page)
}
}
route, ok := sitemapRoutes[path]
if !ok {
return common.NotFound(w, r)
}
return route(w, r)
}
func routeSitemapForums(w http.ResponseWriter, r *http.Request) common.RouteError {
var sslBit string
if common.Site.EnableSsl {
sslBit = "s"
}
var sitemapItem = func(path string) {
w.Write([]byte(`<url>
<loc>http` + sslBit + `://` + common.Site.URL + path + `</loc>
</url>
`))
}
group, err := common.Groups.Get(common.GuestUser.Group)
if err != nil {
log.Print("The guest group doesn't exist for some reason")
// TODO: Add XML error handling to errors.go
w.WriteHeader(500)
w.Write(xmlInternalError)
return common.HandledRouteError()
}
writeXMLHeader(w, r)
w.Write([]byte("<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n"))
for _, fid := range group.CanSee {
// Avoid data races by copying the struct into something we can freely mold without worrying about breaking something somewhere else
var forum = common.Forums.DirtyGet(fid).Copy()
if forum.ParentID == 0 && forum.Name != "" && forum.Active {
sitemapItem(common.BuildForumURL(common.NameToSlug(forum.Name), forum.ID))
}
}
w.Write([]byte("</urlset>"))
return nil
}
// TODO: Add a global ratelimit. 10 50MB files (smaller if compressed better) per minute?
// ? We might have problems with banned users, if they have fewer ViewTopic permissions than guests as they'll be able to see this list. Then again, a banned user could just logout to see it
func routeSitemapTopics(w http.ResponseWriter, r *http.Request) common.RouteError {
var sslBit string
if common.Site.EnableSsl {
sslBit = "s"
}
var sitemapItem = func(path string) {
w.Write([]byte(`<sitemap>
<loc>http` + sslBit + `://` + common.Site.URL + "/" + path + `</loc>
</sitemap>
`))
}
writeXMLHeader(w, r)
group, err := common.Groups.Get(common.GuestUser.Group)
if err != nil {
log.Print("The guest group doesn't exist for some reason")
// TODO: Add XML error handling to errors.go
w.WriteHeader(500)
w.Write(xmlInternalError)
return common.HandledRouteError()
}
var argList []interface{}
var qlist string
for _, fid := range group.CanSee {
forum := common.Forums.DirtyGet(fid)
if forum.Name != "" && forum.Active {
argList = append(argList, strconv.Itoa(fid))
qlist += "?,"
}
}
if qlist != "" {
qlist = qlist[0 : len(qlist)-1]
}
// TODO: Abstract this
topicCountStmt, err := qgen.Builder.SimpleCount("topics", "parentID IN("+qlist+")", "")
if err != nil {
// TODO: Add XML error handling to errors.go
w.WriteHeader(500)
w.Write(xmlInternalError)
common.LogError(err)
return common.HandledRouteError()
}
defer topicCountStmt.Close()
var topicCount int
err = topicCountStmt.QueryRow(argList...).Scan(&topicCount)
if err != nil && err != ErrNoRows {
// TODO: Add XML error handling to errors.go
w.WriteHeader(500)
w.Write(xmlInternalError)
common.LogError(err)
return common.HandledRouteError()
}
var pageCount = topicCount / sitemapPageCap
//log.Print("topicCount", topicCount)
//log.Print("pageCount", pageCount)
w.Write([]byte("<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n"))
for i := 0; i <= pageCount; i++ {
sitemapItem("sitemaps/topics_page_" + strconv.Itoa(i) + ".xml")
}
w.Write([]byte("</sitemapindex>"))
return nil
}
func routeSitemapTopic(w http.ResponseWriter, r *http.Request, page int) common.RouteError {
/*var sslBit string
if common.Site.EnableSsl {
sslBit = "s"
}
var sitemapItem = func(path string) {
w.Write([]byte(`<url>
<loc>http` + sslBit + `://` + common.Site.URL + "/" + path + `</loc>
</url>
`))
}*/
group, err := common.Groups.Get(common.GuestUser.Group)
if err != nil {
log.Print("The guest group doesn't exist for some reason")
// TODO: Add XML error handling to errors.go
w.WriteHeader(500)
w.Write(xmlInternalError)
return common.HandledRouteError()
}
var argList []interface{}
var qlist string
for _, fid := range group.CanSee {
forum := common.Forums.DirtyGet(fid)
if forum.Name != "" && forum.Active {
argList = append(argList, strconv.Itoa(fid))
qlist += "?,"
}
}
if qlist != "" {
qlist = qlist[0 : len(qlist)-1]
}
// TODO: Abstract this
topicCountStmt, err := qgen.Builder.SimpleCount("topics", "parentID IN("+qlist+")", "")
if err != nil {
// TODO: Add XML error handling to errors.go
w.WriteHeader(500)
w.Write(xmlInternalError)
common.LogError(err)
return common.HandledRouteError()
}
defer topicCountStmt.Close()
var topicCount int
err = topicCountStmt.QueryRow(argList...).Scan(&topicCount)
if err != nil && err != ErrNoRows {
// TODO: Add XML error handling to errors.go
w.WriteHeader(500)
w.Write(xmlInternalError)
common.LogError(err)
return common.HandledRouteError()
}
var pageCount = topicCount / sitemapPageCap
//log.Print("topicCount", topicCount)
//log.Print("pageCount", pageCount)
//log.Print("page",page)
if page > pageCount {
page = pageCount
}
writeXMLHeader(w, r)
w.Write([]byte("<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n"))
w.Write([]byte("</urlset>"))
return nil
}
func routeSitemapUsers(w http.ResponseWriter, r *http.Request) common.RouteError {
writeXMLHeader(w, r)
w.Write([]byte("<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n"))
return nil
}