package main import ( "database/sql" "errors" "fmt" "html/template" "io" "log" "net/http" "strings" "time" ) func baseLoadTemplate(base string, path string) (*template.Template, any) { return template.New(base).ParseFiles( "./views/"+base, "./views/"+path, "./views/partials/header.html", ) } func loadTemplate(path string) (*template.Template, any) { return baseLoadTemplate("layout.html", path) } func LoadView(writer http.ResponseWriter, path string, data interface{}) { tmpl, err := loadTemplate(path) if err != nil { fmt.Printf("Failed to load view %s\n", path) fmt.Println(err) if path == "500.html" { writer.Write([]byte("

Failed to load 500.html check console for more info

")) } else { LoadView(writer, "500.html", nil) } return } if err := tmpl.Execute(writer, data); err != nil { fmt.Printf("Failed to load view %s\n", path) fmt.Println(err) writer.WriteHeader(http.StatusInternalServerError) if path == "500.html" { writer.Write([]byte("

Failed to load 500.html check console for more info

")) } else { LoadView(writer, "500.html", nil) } return } } /** Only returns the html without template */ func LoadHtml(writer http.ResponseWriter, path string, data interface{}) { tmpl, err := baseLoadTemplate("html.html", path) if err != nil { fmt.Printf("Failed to load template %s\n", path) fmt.Println(err) writer.WriteHeader(http.StatusInternalServerError) if path == "500.html" { writer.Write([]byte("

Failed to load 500.html check console for more info

")) } else { LoadHtml(writer, "500.html", nil) } return } if err := tmpl.Execute(writer, data); err != nil { fmt.Printf("Failed to execute template %s\n", path) fmt.Println(err) writer.WriteHeader(http.StatusInternalServerError) if path == "500.html" { writer.Write([]byte("

Failed to load 500.html check console for more info

")) } else { LoadHtml(writer, "500.html", nil) } return } } type AnyMap = map[string]interface{} type Error struct { code int msg *string } type AnswerType int const ( NORMAL AnswerType = 1 << iota HTML JSON HTMLFULL ) func LoadBasedOnAnswer(ans AnswerType, w http.ResponseWriter, path string, data map[string]interface{}) { if ans == NORMAL { LoadView(w, path, data) return } else if ans == HTML { LoadHtml(w, path, data) return } else if ans == HTMLFULL { if data == nil { LoadHtml(w, path, map[string]interface{}{ "App": true, }) } else { data["App"] = true LoadHtml(w, path, data) } return } else if ans == JSON { panic("TODO JSON!") } else { panic("unreachable") } } type HandleFunc struct { path string mode AnswerType fn func(w http.ResponseWriter, r *http.Request, c *Context) *Error } type Handler interface { New() Startup() Get(fn func(w http.ResponseWriter, r *http.Request, c *Context) *Error) Post(fn func(w http.ResponseWriter, r *http.Request, c *Context) *Error) } type Handle struct { db *sql.DB gets []HandleFunc posts []HandleFunc } func decodeBody(r *http.Request) (string, *Error) { body, err := io.ReadAll(r.Body) if err == nil { return "", &Error{code: http.StatusBadRequest} } return string(body[:]), nil } func handleError(err *Error, w http.ResponseWriter, context *Context) { data := context.toMap() if err != nil { w.WriteHeader(err.code) if err.code == http.StatusNotFound { LoadBasedOnAnswer(context.Mode, w, "404.html", data) return } if err.code == http.StatusBadRequest { LoadBasedOnAnswer(context.Mode, w, "400.html", data) return } if err.msg != nil { w.Write([]byte(*err.msg)) } } } func (x *Handle) Get(path string, fn func(w http.ResponseWriter, r *http.Request, c *Context) *Error) { nhandler := HandleFunc{ fn: fn, path: path, mode: NORMAL | HTML | HTMLFULL | JSON, } x.gets = append(x.gets, nhandler) } func (x *Handle) GetHTML(path string, fn func(w http.ResponseWriter, r *http.Request, c *Context) *Error) { nhandler := HandleFunc{ fn: fn, path: path, mode: NORMAL | HTML | HTMLFULL, } x.gets = append(x.gets, nhandler) } func (x *Handle) GetJSON(path string, fn func(w http.ResponseWriter, r *http.Request, c *Context) *Error) { nhandler := HandleFunc{ fn: fn, path: path, mode: JSON, } x.gets = append(x.gets, nhandler) } func (x *Handle) Post(path string, fn func(w http.ResponseWriter, r *http.Request, c *Context) *Error) { nhandler := HandleFunc{ fn: fn, path: path, mode: NORMAL | HTML | HTMLFULL | JSON, } x.posts = append(x.posts, nhandler) } func (x *Handle) PostHTML(path string, fn func(w http.ResponseWriter, r *http.Request, c *Context) *Error) { nhandler := HandleFunc{ fn: fn, path: path, mode: NORMAL | HTML | HTMLFULL, } x.posts = append(x.posts, nhandler) } func (x *Handle) PostJSON(path string, fn func(w http.ResponseWriter, r *http.Request, c *Context) *Error) { nhandler := HandleFunc{ fn: fn, path: path, mode: JSON, } x.posts = append(x.posts, nhandler) } func (x *Handle) handleGets(w http.ResponseWriter, r *http.Request, context *Context) { for _, s := range x.gets { if s.path == r.URL.Path && context.Mode&s.mode != 0 { handleError(s.fn(w, r, context), w, context) return } } if context.Mode != HTMLFULL { w.WriteHeader(http.StatusNotFound) } LoadBasedOnAnswer(context.Mode, w, "404.html", map[string]interface{}{ "context": context, }) } func (x *Handle) handlePosts(w http.ResponseWriter, r *http.Request, context *Context) { for _, s := range x.posts { if s.path == r.URL.Path && context.Mode&s.mode != 0 { handleError(s.fn(w, r, context), w, context) return } } if context.Mode != HTMLFULL { w.WriteHeader(http.StatusNotFound) } LoadBasedOnAnswer(context.Mode, w, "404.html", map[string]interface{}{ "context": context, }) } func AnswerTemplate(path string, data AnyMap) func(w http.ResponseWriter, r *http.Request, c *Context) *Error { return func(w http.ResponseWriter, r *http.Request, c *Context) *Error { if data == nil { LoadBasedOnAnswer(c.Mode, w, path, c.toMap()) } else { LoadBasedOnAnswer(c.Mode, w, path, c.addMap(data)) } return nil } } type Context struct { Token *string User *User Mode AnswerType } func (c Context) addMap(m AnyMap) AnyMap { m["Context"] = c; return m; } func (c *Context) toMap() map[string]interface{} { return map[string]interface{}{ "Context": c, } } func (c *Context) requireAuth(w http.ResponseWriter, r *http.Request) bool { if c.User == nil { return true; } return false; } var LogoffError = errors.New("Invalid token!") func (x Handle) createContext(mode AnswerType, r *http.Request) (*Context, error) { var token *string for _, r := range r.Cookies() { if r.Name == "auth" { token = &r.Value } } if token == nil { return &Context{ Mode: mode, }, nil } user, err := userFromToken(x.db, *token) if err != nil { return nil, errors.Join(err, LogoffError) } return &Context{token, user, mode}, nil } func logoff(mode AnswerType, w http.ResponseWriter, r *http.Request) { // Delete cookie cookie := &http.Cookie{ Name: "auth", Value: "", Expires: time.Unix(0, 0), } http.SetCookie(w, cookie) // Setup response w.Header().Set("Location", "/login") if mode == JSON { w.WriteHeader(http.StatusUnauthorized) w.Write([]byte("\"Bye Bye\"")); return } if mode & (HTMLFULL | HTML) != 0 { w.WriteHeader(http.StatusUnauthorized); w.Write([]byte("Bye Bye")); } else { w.WriteHeader(http.StatusSeeOther); } } func NewHandler(db *sql.DB) *Handle { var gets []HandleFunc var posts []HandleFunc x := &Handle{ db, gets, posts, } http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { // Decide answertype ans := NORMAL if r.Header.Get("Request-Type") == "htmlfull" { ans = HTMLFULL } if r.Header.Get("Request-Type") == "html" { ans = HTML } //TODO JSON //Login state context, err := x.createContext(ans, r) if err != nil { logoff(ans, w, r) return } if r.Method == "GET" { x.handleGets(w, r, context) return } if r.Method == "POST" { x.handlePosts(w, r, context) return } panic("TODO handle: " + r.Method) }) // TODO Handle this in other way http.HandleFunc("/styles/", func(w http.ResponseWriter, r *http.Request) { path := r.URL.Path[len("/styles/"):] if !strings.HasSuffix(path, ".css") { w.WriteHeader(http.StatusNotFound) w.Write([]byte("File not found")) return } t, err := template.ParseFiles("./views/styles/" + path) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("Failed to load template")) fmt.Println("Error:") fmt.Println(err) return } w.Header().Set("Content-Type", "text/css; charset=utf-8") t.Execute(w, nil) }) // TODO Handle this in other way http.HandleFunc("/js/", func(w http.ResponseWriter, r *http.Request) { path := r.URL.Path[len("/js/"):] if !strings.HasSuffix(path, ".js") { w.WriteHeader(http.StatusNotFound) w.Write([]byte("File not found")) return } t, err := template.ParseFiles("./views/js/" + path) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("Failed to load template")) fmt.Println("Error:") fmt.Println(err) return } w.Header().Set("Content-Type", "text/javascript; charset=utf-8") t.Execute(w, nil) }) return x } func (x Handle) Startup() { fmt.Printf("Starting up!\n") log.Fatal(http.ListenAndServe(":8000", nil)) }