package app
import (
"embed"
"fmt"
"html/template"
"log"
"net/http"
"path"
"path/filepath"
"strings"
)
//go:embed embeded/tmpl/*.html
var tmplFS embed.FS
//go:embed embeded/static/*
var staticFS embed.FS
const tmplEmbedPath = "embeded/tmpl"
const staticEmbedPath = "embeded/static"
const redirectStatusCode = 301
var resourceHandlers = map[string]func(http.ResponseWriter, *http.Request, string){
"strat": httpHandleStrategyBoardAlias,
"markdown": httpHandleMarkdown,
"serve": httpHandleFile,
"redirect": httpHandleRedirect,
}
var baseHandlers = []func(http.ResponseWriter, *http.Request, []string) bool{
httpBaseHandleUser,
httpBaseHandleGCSS,
httpBaseHandleForm,
httpBaseHandleAPI,
httpBaseHandleCharacter,
httpBaseHandleStrategyBoard,
httpBaseHandleDocument,
httpBaseHandleResourceAliasAlias,
httpBaseHandleStatic,
}
func httpHandleErrorText(w http.ResponseWriter, err error) {
w.Header().Add("Content-Type", "text/plain")
statusCode := getErrorHttpStatusCode(err)
log.Printf(" >> %d %s", statusCode, err.Error())
w.WriteHeader(statusCode)
fmt.Fprintf(w, "%d %s", statusCode, http.StatusText(statusCode))
}
func httpHandleError(w http.ResponseWriter, err error) {
statusCode := getErrorHttpStatusCode(err)
t, perr := template.ParseFS(tmplFS, path.Join(tmplEmbedPath, "_*.html"), path.Join(tmplEmbedPath, "error.html"))
if perr != nil {
log.Printf("WARNING: %s", perr.Error())
httpHandleErrorText(w, err)
return
}
log.Printf(" >> %d %s", statusCode, err.Error())
w.Header().Add("Content-Type", "text/html")
w.WriteHeader(statusCode)
if err := t.ExecuteTemplate(w, "error.html", map[string]any{
"statusCode": statusCode,
"statusText": http.StatusText(statusCode),
"error": getErrorHttpMessage(err),
}); err != nil {
statusCode = getErrorHttpStatusCode(err)
fmt.Fprintf(w, "%d %s", statusCode, http.StatusText(statusCode))
return
}
}
func httpHandleRedirect(w http.ResponseWriter, r *http.Request, redirect string) {
log.Printf(" >> %d redirect to %s", redirectStatusCode, redirect)
w.Header().Add("Location", redirect)
w.WriteHeader(redirectStatusCode)
}
func httpHandleFile(w http.ResponseWriter, r *http.Request, file string) {
log.Printf(" >> serve file %s", file)
ext := filepath.Ext(file)
mime := "text/plain"
switch ext {
case ".png":
mime = "image/png"
w.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
case ".html":
mime = "text/html"
case ".gcss":
httpBaseHandleGCSS(w, r, []string{file})
return
}
w.Header().Set("Content-Type", mime)
http.ServeFileFS(w, r, staticFS, strings.ToLower(filepath.Join(staticEmbedPath, file)))
}
func httpHandleTemplate(w http.ResponseWriter, name string, data map[string]any) {
name = strings.TrimSuffix(name, ".html")
log.Printf(" >> serve template %s", name)
w.Header().Add("Content-Type", "text/html")
t, err := template.ParseFS(tmplFS, path.Join(tmplEmbedPath, "_*.html"), path.Join(tmplEmbedPath, name+".html"))
if err != nil {
httpHandleError(w, err)
return
}
if err := t.ExecuteTemplate(w, name+".html", data); err != nil {
statusCode := getErrorHttpStatusCode(err)
log.Printf(" >> %d %s", statusCode, err.Error())
fmt.Fprintf(w, "%d %s", statusCode, http.StatusText(statusCode))
return
}
}
func httpHandleStrategyBoardAlias(w http.ResponseWriter, r *http.Request, resource string) {
httpBaseHandleStrategyBoard(w, r, []string{"b", resource})
}
func httpBaseHandleResourceAliasAlias(w http.ResponseWriter, r *http.Request, path []string) bool {
// fetch resource alias from resources.yaml
alias := path[len(path)-1]
resource := resourceList[alias]
// fetch resource alias from database if input not found in yaml
if resource == "" {
resourceRecord, err := fetchResourceRecordByAlias(db, alias)
if err != nil {
return false
}
resource = resourceRecord.Data
}
// find and execute handler function
handlerName := strings.Split(resource, ":")[0]
handlerFunc := resourceHandlers[handlerName]
if handlerFunc != nil {
log.Printf(" >> handle '%s'", resource)
handlerFunc(w, r, stripHandlerAlias(resource))
return true
}
// assume redirect otherwise
httpHandleRedirect(w, r, stripHandlerAlias(resource))
return true
}
func httpBaseHandleStatic(w http.ResponseWriter, r *http.Request, path []string) bool {
if path[0] != "static" {
return false
}
httpHandleFile(w, r, filepath.Join(path[1:]...))
return true
}
func httpHandleMain(w http.ResponseWriter, r *http.Request) {
clientID := getUserClientID(r)
path := strings.Split(strings.Trim(r.URL.Path, "/"), "/")
log.Printf("[%s] %s %s", clientID[:4], r.Method, r.URL.Path)
// iterate handlers until one compatible with current path is found
for _, handler := range baseHandlers {
if handler(w, r, path) {
return
}
}
// no handlers
httpHandleError(w, errHttpNotFound)
}