A fast and simple blog backend.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
mediocre-blog/src/http/assets.go

235 lines
4.7 KiB

package http
import (
"bytes"
"compress/gzip"
"errors"
"fmt"
"io"
"io/fs"
"net/http"
"path/filepath"
"time"
"github.com/mediocregopher/blog.mediocregopher.com/srv/http/apiutil"
"github.com/mediocregopher/blog.mediocregopher.com/srv/post/asset"
"github.com/omeid/go-tarfs"
)
func (a *api) managePostAssetsHandler() http.Handler {
tpl := a.mustParseBasedTpl("post-assets-manage.html")
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
ids, err := a.params.PostAssetStore.List()
if err != nil {
apiutil.InternalServerError(
rw, r, fmt.Errorf("getting list of asset ids: %w", err),
)
return
}
tplPayload := struct {
IDs []string
}{
IDs: ids,
}
executeTemplate(rw, r, tpl, tplPayload)
})
}
func (a *api) writePostAsset(
rw http.ResponseWriter,
r *http.Request,
path string,
from io.ReadSeeker,
) {
maxWidth, err := apiutil.StrToInt(r.FormValue("w"), 0)
if err != nil {
apiutil.BadRequest(rw, r, fmt.Errorf("invalid w parameter: %w", err))
return
}
if maxWidth == 0 {
http.ServeContent(rw, r, path, time.Time{}, from)
return
}
if !isImgResizable(path) {
apiutil.BadRequest(rw, r, fmt.Errorf("cannot resize asset %q", path))
return
}
resizedBuf := new(bytes.Buffer)
if err := resizeImage(resizedBuf, from, float64(maxWidth)); err != nil {
apiutil.InternalServerError(
rw, r,
fmt.Errorf(
"resizing image %q to size %d: %w",
path, maxWidth, err,
),
)
}
http.ServeContent(
rw, r, path, time.Time{}, bytes.NewReader(resizedBuf.Bytes()),
)
}
func (a *api) handleGetPostAssetArchive(
rw http.ResponseWriter,
r *http.Request,
info postAssetArchiveInfo,
) {
buf := new(bytes.Buffer)
err := a.params.PostAssetStore.Get(info.id, buf)
if errors.Is(err, asset.ErrNotFound) {
http.Error(rw, "asset not found", 404)
return
} else if err != nil {
apiutil.InternalServerError(
rw, r,
fmt.Errorf("fetching archive asset with id %q: %w", info.id, err),
)
return
}
var from io.Reader = buf
if info.isGzipped {
if from, err = gzip.NewReader(from); err != nil {
apiutil.InternalServerError(
rw, r,
fmt.Errorf("decompressing archive asset with id %q: %w", info.id, err),
)
return
}
}
// TODO everything from here down needs to be moved to the archive loader
tarFS, err := tarfs.New(from)
if err != nil {
apiutil.InternalServerError(
rw, r,
fmt.Errorf("reading archive asset with id %q as fs: %w", info.id, err),
)
return
}
f, err := tarFS.Open(info.subPath)
if errors.Is(err, fs.ErrExist) {
http.Error(rw, "Asset not found", 404)
return
} else if err != nil {
apiutil.InternalServerError(
rw, r,
fmt.Errorf(
"opening path %q from archive asset with id %q as fs: %w",
info.subPath, info.id, err,
),
)
return
}
defer f.Close()
a.writePostAsset(rw, r, info.path, f)
}
func (a *api) getPostAssetHandler() http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
archiveInfo, ok := extractPostAssetArchiveInfo(r.URL.Path)
if ok {
a.handleGetPostAssetArchive(rw, r, archiveInfo)
return
}
id := filepath.Base(r.URL.Path)
buf := new(bytes.Buffer)
err := a.params.PostAssetStore.Get(id, buf)
if errors.Is(err, asset.ErrNotFound) {
http.Error(rw, "Asset not found", 404)
return
} else if err != nil {
apiutil.InternalServerError(
rw, r, fmt.Errorf("fetching asset with id %q: %w", id, err),
)
return
}
a.writePostAsset(rw, r, id, bytes.NewReader(buf.Bytes()))
})
}
func (a *api) postPostAssetHandler() http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
id := r.PostFormValue("id")
if id == "" {
apiutil.BadRequest(rw, r, errors.New("id is required"))
return
}
file, _, err := r.FormFile("file")
if err != nil {
apiutil.BadRequest(rw, r, fmt.Errorf("reading multipart file: %w", err))
return
}
defer file.Close()
if err := a.params.PostAssetStore.Set(id, file); err != nil {
apiutil.InternalServerError(rw, r, fmt.Errorf("storing file: %w", err))
return
}
a.executeRedirectTpl(rw, r, a.manageAssetsURL(false))
})
}
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, asset.ErrNotFound) {
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, a.manageAssetsURL(false))
})
}