diff --git a/srv/src/api/api.go b/srv/src/api/api.go index cf34157..a5ada2d 100644 --- a/srv/src/api/api.go +++ b/srv/src/api/api.go @@ -5,6 +5,7 @@ import ( "context" "errors" "fmt" + "html/template" "net" "net/http" "net/http/httputil" @@ -101,6 +102,8 @@ type API interface { type api struct { params Params srv *http.Server + + redirectTpl *template.Template } // New initializes and returns a new API instance, including setting up all @@ -122,6 +125,8 @@ func New(params Params) (API, error) { params: params, } + a.redirectTpl = a.mustParseTpl("redirect.html") + a.srv = &http.Server{Handler: a.handler()} go func() { @@ -201,11 +206,13 @@ func (a *api) handler() http.Handler { v2Mux := http.NewServeMux() v2Mux.Handle("/follow.html", a.renderDumbHandler("follow.html")) v2Mux.Handle("/posts/", a.renderPostHandler()) - v2Mux.Handle("/assets", apiutil.MethodMux(map[string]http.Handler{ - "GET": a.renderPostAssetsIndexHandler(), - "POST": formMiddleware(a.uploadPostAssetHandler()), - })) - v2Mux.Handle("/assets/", a.servePostAssetHandler()) + v2Mux.Handle("/assets/", http.StripPrefix("/assets", + apiutil.MethodMux(map[string]http.Handler{ + "GET": a.getPostAssetHandler(), + "POST": formMiddleware(a.postPostAssetHandler()), + "DELETE": formMiddleware(a.deletePostAssetHandler()), + }), + )) v2Mux.Handle("/", a.renderIndexHandler()) mux.Handle("/v2/", http.StripPrefix("/v2", v2Mux)) diff --git a/srv/src/api/apiutil/apiutil.go b/srv/src/api/apiutil/apiutil.go index f7830ae..d427b65 100644 --- a/srv/src/api/apiutil/apiutil.go +++ b/srv/src/api/apiutil/apiutil.go @@ -121,7 +121,13 @@ func MethodMux(handlers map[string]http.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - handler, ok := handlers[strings.ToUpper(r.Method)] + method := strings.ToUpper(r.FormValue("method")) + + if method == "" { + method = strings.ToUpper(r.Method) + } + + handler, ok := handlers[method] if !ok { http.Error(rw, "Method not allowed", http.StatusMethodNotAllowed) diff --git a/srv/src/api/assets.go b/srv/src/api/assets.go index c0a6fd9..7065ff6 100644 --- a/srv/src/api/assets.go +++ b/srv/src/api/assets.go @@ -50,12 +50,19 @@ func resizeImage(out io.Writer, in io.Reader, maxWidth float64) error { } } -func (a *api) servePostAssetHandler() http.Handler { +func (a *api) getPostAssetHandler() http.Handler { + + renderHandler := a.renderPostAssetsIndexHandler() return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { id := filepath.Base(r.URL.Path) + if id == "/" { + renderHandler.ServeHTTP(rw, r) + return + } + maxWidth, err := apiutil.StrToInt(r.FormValue("w"), 0) if err != nil { apiutil.BadRequest(rw, r, fmt.Errorf("invalid w parameter: %w", err)) @@ -112,9 +119,7 @@ func (a *api) servePostAssetHandler() http.Handler { }) } -func (a *api) uploadPostAssetHandler() http.Handler { - - renderIndex := a.renderPostAssetsIndexHandler() +func (a *api) postPostAssetHandler() http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { @@ -136,6 +141,33 @@ func (a *api) uploadPostAssetHandler() http.Handler { return } - renderIndex.ServeHTTP(rw, r) + a.executeRedirectTpl(rw, r, "assets/") + }) +} + +func (a *api) deletePostAssetHandler() http.Handler { + + return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + + id := filepath.Base(r.URL.Path) + + if id == "" { + apiutil.BadRequest(rw, r, errors.New("id is required")) + return + } + + err := a.params.PostAssetStore.Delete(id) + + if errors.Is(err, post.ErrAssetNotFound) { + http.Error(rw, "Asset not found", 404) + return + } else if err != nil { + apiutil.InternalServerError( + rw, r, fmt.Errorf("deleting asset with id %q: %w", id, err), + ) + return + } + + a.executeRedirectTpl(rw, r, "assets/") }) } diff --git a/srv/src/api/render.go b/srv/src/api/render.go index c0d0777..b6f9572 100644 --- a/srv/src/api/render.go +++ b/srv/src/api/render.go @@ -20,21 +20,29 @@ import ( //go:embed tpl var tplFS embed.FS -func (a *api) mustParseTpl(name string) *template.Template { +func mustReadTplFile(fileName string) string { + path := filepath.Join("tpl", fileName) - mustRead := func(fileName string) string { - path := filepath.Join("tpl", fileName) + b, err := fs.ReadFile(tplFS, path) + if err != nil { + panic(fmt.Errorf("reading file %q from tplFS: %w", path, err)) + } - b, err := fs.ReadFile(tplFS, path) - if err != nil { - panic(fmt.Errorf("reading file %q from tplFS: %w", path, err)) - } + return string(b) +} - return string(b) - } +func (a *api) mustParseTpl(name string) *template.Template { blogURL := func(path string) string { - return filepath.Join(a.params.PathPrefix, "/v2", path) + + trailingSlash := strings.HasSuffix(path, "/") + path = filepath.Join(a.params.PathPrefix, "/v2", path) + + if trailingSlash { + path += "/" + } + + return path } tpl := template.New("").Funcs(template.FuncMap{ @@ -45,9 +53,14 @@ func (a *api) mustParseTpl(name string) *template.Template { }, }) - tpl = template.Must(tpl.Parse(mustRead(name))) - tpl = template.Must(tpl.New("base.html").Parse(mustRead("base.html"))) + tpl = template.Must(tpl.Parse(mustReadTplFile(name))) + + return tpl +} +func (a *api) mustParseBasedTpl(name string) *template.Template { + tpl := a.mustParseTpl(name) + tpl = template.Must(tpl.New("base.html").Parse(mustReadTplFile("base.html"))) return tpl } @@ -84,9 +97,19 @@ func executeTemplate( } } +func (a *api) executeRedirectTpl( + rw http.ResponseWriter, r *http.Request, path string, +) { + executeTemplate(rw, r, a.redirectTpl, struct { + Path string + }{ + Path: path, + }) +} + func (a *api) renderIndexHandler() http.Handler { - tpl := a.mustParseTpl("index.html") + tpl := a.mustParseBasedTpl("index.html") const pageCount = 10 return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { @@ -135,7 +158,7 @@ func (a *api) renderIndexHandler() http.Handler { func (a *api) renderPostHandler() http.Handler { - tpl := a.mustParseTpl("post.html") + tpl := a.mustParseBasedTpl("post.html") return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { @@ -208,7 +231,7 @@ func (a *api) renderPostHandler() http.Handler { func (a *api) renderDumbHandler(tplName string) http.Handler { - tpl := a.mustParseTpl(tplName) + tpl := a.mustParseBasedTpl(tplName) return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { if err := tpl.Execute(rw, nil); err != nil { @@ -222,7 +245,7 @@ func (a *api) renderDumbHandler(tplName string) http.Handler { func (a *api) renderPostAssetsIndexHandler() http.Handler { - tpl := a.mustParseTpl("admin-assets.html") + tpl := a.mustParseBasedTpl("assets.html") return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { diff --git a/srv/src/api/tpl/admin-assets.html b/srv/src/api/tpl/assets.html similarity index 68% rename from srv/src/api/tpl/admin-assets.html rename to srv/src/api/tpl/assets.html index 036002e..5ed6515 100644 --- a/srv/src/api/tpl/admin-assets.html +++ b/srv/src/api/tpl/assets.html @@ -1,5 +1,7 @@ {{ define "body" }} +{{ $csrfFormInput := .CSRFFormInput }} +

Upload Asset

@@ -7,8 +9,8 @@ overwritten.

-
- {{ .CSRFFormInput }} + + {{ $csrfFormInput }}
@@ -30,7 +32,10 @@ {{ . }} - Delete (TODO) + + {{ $csrfFormInput }} + + {{ end }} diff --git a/srv/src/api/tpl/redirect.html b/srv/src/api/tpl/redirect.html new file mode 100644 index 0000000..ed12a2e --- /dev/null +++ b/srv/src/api/tpl/redirect.html @@ -0,0 +1,9 @@ + + + + + + +

Redirecting...

+ +