Anki/http.go

136 lines
3.1 KiB
Go

package main
import (
"context"
_ "embed"
"encoding/json"
"fmt"
"net/http"
"os"
"os/signal"
"path"
"strings"
"syscall"
"time"
)
type Context struct {
DB DB
User IDU
}
func HTTP(port int, db DB) error {
ctx, can := signal.NotifyContext(context.Background(), syscall.SIGINT)
defer can()
foo := func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" {
httpGUI(w, r)
} else if strings.HasPrefix(r.URL.Path, "/api/questions") && strings.HasSuffix(r.URL.Path, "/answers") && r.Method == http.MethodPost {
httpPostQuestionAnswers(w, r)
} else {
http.NotFound(w, r)
}
}
foo = withAuth(foo)
foo = withDB(foo, db)
foo = withCtx(foo, ctx)
go func() {
http.ListenAndServe(fmt.Sprintf(":%d", port), http.HandlerFunc(foo))
}()
<-ctx.Done()
return nil
}
func extract(ctx context.Context) Context {
v := ctx.Value("__context")
v2, _ := v.(Context)
return v2
}
func inject(ctx context.Context, v Context) context.Context {
return context.WithValue(ctx, "__context", v)
}
func withCtx(foo http.HandlerFunc, ctx context.Context) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
r = r.WithContext(ctx)
foo(w, r)
}
}
func withDB(foo http.HandlerFunc, db DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
c := extract(r.Context())
c.DB = db
r = r.WithContext(inject(r.Context(), c))
foo(w, r)
}
}
func withAuth(foo http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
u, _, ok := r.BasicAuth()
if !ok || u == "" {
w.Header().Set("WWW-Authenticate", "Basic")
w.WriteHeader(401)
return
}
c := extract(r.Context())
c.User = IDU(u)
r = r.WithContext(inject(r.Context(), c))
foo(w, r)
}
}
//go:embed public/root.html
var httpGUIHTML string
func httpGUI(w http.ResponseWriter, r *http.Request) {
body := httpGUIHTML
if os.Getenv("DEBUG") != "" {
b, _ := os.ReadFile("public/root.html")
body = string(b)
}
ctx := extract(r.Context())
body = strings.ReplaceAll(body, "{{USER}}", string(ctx.User))
assignments, err := httpAssignments(r.Context())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
assignmentsB, _ := json.Marshal(assignments)
body = strings.ReplaceAll(body, "{{ASSIGNMENTS_JSON}}", string(assignmentsB))
w.Write([]byte(body))
}
func httpAssignments(ctx context.Context) (interface{}, error) {
db := extract(ctx).DB
user := extract(ctx).User
todo := map[IDQ]Question{}
for q, _ := range db.HistoryOf(user) {
if time.Until(db.Next(user, q)) > 0 {
continue
}
todo[q] = db.Question(q)
}
return todo, nil
}
func httpPostQuestionAnswers(w http.ResponseWriter, r *http.Request) {
idq := IDQ(path.Base(strings.Split(r.URL.Path, "/answers")[0]))
var payload struct {
Answer string `json:"answer"`
Passed bool `json:"passed"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
ctx := extract(r.Context())
if err := ctx.DB.PushAnswer(ctx.User, idq, Renderable(payload.Answer), payload.Passed); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}