summaryrefslogtreecommitdiff
path: root/api
diff options
context:
space:
mode:
Diffstat (limited to 'api')
-rw-r--r--api/serve.go111
-rw-r--r--api/template.go65
2 files changed, 176 insertions, 0 deletions
diff --git a/api/serve.go b/api/serve.go
new file mode 100644
index 0000000..2b95297
--- /dev/null
+++ b/api/serve.go
@@ -0,0 +1,111 @@
+package api
+
+import (
+ "crypto/rand"
+ "database/sql"
+ "fmt"
+ "log"
+ "net/http"
+ "time"
+
+ "git.hatecomputers.club/hatecomputers/hatecomputers.club/args"
+)
+
+type RequestContext struct {
+ DBConn *sql.DB
+ Args *args.Arguments
+
+ Id string
+ Start time.Time
+}
+
+type Continuation func(*RequestContext, *http.Request, http.ResponseWriter) ContinuationChain
+type ContinuationChain func(Continuation, Continuation) ContinuationChain
+
+func randomId() string {
+ uuid := make([]byte, 16)
+ _, err := rand.Read(uuid)
+ if err != nil {
+ panic(err)
+ }
+
+ uuid[8] = uuid[8]&^0xc0 | 0x80
+ uuid[6] = uuid[6]&^0xf0 | 0x40
+
+ return fmt.Sprintf("%x-%x-%x-%x-%x", uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:])
+}
+
+func LogRequestContinuation(context *RequestContext, req *http.Request, resp http.ResponseWriter) ContinuationChain {
+ return func(success Continuation, _failure Continuation) ContinuationChain {
+ context.Start = time.Now()
+ context.Id = randomId()
+
+ log.Println(req.Method, req.URL.Path, req.RemoteAddr, context.Id)
+ return success(context, req, resp)
+ }
+}
+
+func LogExecutionTimeContinuation(context *RequestContext, req *http.Request, resp http.ResponseWriter) ContinuationChain {
+ return func(success Continuation, _failure Continuation) ContinuationChain {
+ end := time.Now()
+
+ log.Println(context.Id, "took", end.Sub(context.Start))
+
+ return success(context, req, resp)
+ }
+}
+
+func HealthCheckContinuation(context *RequestContext, req *http.Request, resp http.ResponseWriter) ContinuationChain {
+ return func(success Continuation, _failure Continuation) ContinuationChain {
+ resp.WriteHeader(200)
+ resp.Write([]byte("healthy"))
+ return success(context, req, resp)
+ }
+}
+
+func FailurePassingContinuation(context *RequestContext, req *http.Request, resp http.ResponseWriter) ContinuationChain {
+ return func(_success Continuation, failure Continuation) ContinuationChain {
+ return failure(context, req, resp)
+ }
+}
+
+func IdContinuation(context *RequestContext, req *http.Request, resp http.ResponseWriter) ContinuationChain {
+ return func(success Continuation, _failure Continuation) ContinuationChain {
+ return success(context, req, resp)
+ }
+}
+
+func MakeServer(argv *args.Arguments, dbConn *sql.DB) *http.Server {
+ mux := http.NewServeMux()
+
+ fileServer := http.FileServer(http.Dir(argv.StaticPath))
+ mux.Handle("/static/", http.StripPrefix("/static/", fileServer))
+
+ makeRequestContext := func() *RequestContext {
+ return &RequestContext{
+ DBConn: dbConn,
+ Args: argv,
+ }
+ }
+
+ mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ requestContext := makeRequestContext()
+ LogRequestContinuation(requestContext, r, w)(TemplateContinuation("home.html", nil, true), FailurePassingContinuation)(LogExecutionTimeContinuation, LogExecutionTimeContinuation)(IdContinuation, IdContinuation)
+ })
+
+ mux.HandleFunc("GET /api/health", func(w http.ResponseWriter, r *http.Request) {
+ requestContext := makeRequestContext()
+ LogRequestContinuation(requestContext, r, w)(HealthCheckContinuation, FailurePassingContinuation)(LogExecutionTimeContinuation, LogExecutionTimeContinuation)(IdContinuation, IdContinuation)
+ })
+
+ mux.HandleFunc("GET /{name}", func(w http.ResponseWriter, r *http.Request) {
+ requestContext := makeRequestContext()
+ name := r.PathValue("name")
+ LogRequestContinuation(requestContext, r, w)(TemplateContinuation(name+".html", nil, true), FailurePassingContinuation)(LogExecutionTimeContinuation, LogExecutionTimeContinuation)(IdContinuation, IdContinuation)
+ })
+
+ return &http.Server{
+ Addr: ":" + fmt.Sprint(argv.Port),
+ Handler: mux,
+ }
+}
diff --git a/api/template.go b/api/template.go
new file mode 100644
index 0000000..c666029
--- /dev/null
+++ b/api/template.go
@@ -0,0 +1,65 @@
+package api
+
+import (
+ "bytes"
+ "errors"
+ "html/template"
+ "log"
+ "net/http"
+ "os"
+)
+
+func renderTemplate(context *RequestContext, templateName string, showBaseHtml bool, data interface{}) (bytes.Buffer, error) {
+ templatePath := context.Args.TemplatePath
+ basePath := templatePath + "/base_empty.html"
+ if showBaseHtml {
+ basePath = templatePath + "/base.html"
+ }
+
+ templateLocation := templatePath + "/" + templateName
+ tmpl, err := template.New("").ParseFiles(templateLocation, basePath)
+ if err != nil {
+ return bytes.Buffer{}, err
+ }
+
+ var buffer bytes.Buffer
+ err = tmpl.ExecuteTemplate(&buffer, "base", data)
+
+ if err != nil {
+ return bytes.Buffer{}, err
+ }
+ return buffer, nil
+}
+
+func TemplateContinuation(path string, data interface{}, showBase bool) Continuation {
+ return func(context *RequestContext, req *http.Request, resp http.ResponseWriter) ContinuationChain {
+ return func(success Continuation, failure Continuation) ContinuationChain {
+ html, err := renderTemplate(context, path, true, data)
+ if errors.Is(err, os.ErrNotExist) {
+ resp.WriteHeader(404)
+ html, err = renderTemplate(context, "404.html", true, nil)
+ if err != nil {
+ log.Println("error rendering 404 template", err)
+ resp.WriteHeader(500)
+ return failure(context, req, resp)
+ }
+
+ resp.Header().Set("Content-Type", "text/html")
+ resp.Write(html.Bytes())
+ return failure(context, req, resp)
+ }
+
+ if err != nil {
+ log.Println("error rendering template", err)
+ resp.WriteHeader(500)
+ resp.Write([]byte("error rendering template"))
+ return failure(context, req, resp)
+ }
+
+ resp.WriteHeader(200)
+ resp.Header().Set("Content-Type", "text/html")
+ resp.Write(html.Bytes())
+ return success(context, req, resp)
+ }
+ }
+}