summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrian Picciano <mediocregopher@gmail.com>2022-05-17 13:29:12 -0600
committerBrian Picciano <mediocregopher@gmail.com>2022-05-17 13:29:12 -0600
commit0fdece68c07836a566909d75a7f3836f229334b5 (patch)
tree65f47e7dc4e8f2bce269054d2d8db978381c4f7d
parentf9d1f664f0a554b58e674f195572cc8a71d0bbfa (diff)
Add /v2/assets/ handler, with resizing
-rw-r--r--srv/src/api/api.go5
-rw-r--r--srv/src/api/assets.go113
-rw-r--r--srv/src/cmd/mediocre-blog/main.go2
-rw-r--r--srv/src/go.mod1
-rw-r--r--srv/src/go.sum3
5 files changed, 123 insertions, 1 deletions
diff --git a/srv/src/api/api.go b/srv/src/api/api.go
index 79979be..37ea1fc 100644
--- a/srv/src/api/api.go
+++ b/srv/src/api/api.go
@@ -26,7 +26,8 @@ type Params struct {
Logger *mlog.Logger
PowManager pow.Manager
- PostStore post.Store
+ PostStore post.Store
+ PostAssetStore post.AssetStore
MailingList mailinglist.MailingList
@@ -192,6 +193,8 @@ func (a *api) handler() http.Handler {
mux.Handle("/v2/posts/", a.renderPostHandler())
mux.Handle("/v2/", a.renderIndexHandler())
+ mux.Handle("/v2/assets/", a.servePostAssetHandler())
+
var globalHandler http.Handler = mux
globalHandler = setLoggerMiddleware(a.params.Logger, globalHandler)
diff --git a/srv/src/api/assets.go b/srv/src/api/assets.go
new file mode 100644
index 0000000..e94d324
--- /dev/null
+++ b/srv/src/api/assets.go
@@ -0,0 +1,113 @@
+package api
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "image"
+ "image/jpeg"
+ "image/png"
+ "io"
+ "net/http"
+ "path/filepath"
+ "strings"
+
+ "github.com/mediocregopher/blog.mediocregopher.com/srv/api/apiutil"
+ "github.com/mediocregopher/blog.mediocregopher.com/srv/post"
+ "golang.org/x/image/draw"
+)
+
+func resizeImage(out io.Writer, in io.Reader, maxWidth float64) error {
+
+ img, format, err := image.Decode(in)
+ if err != nil {
+ return fmt.Errorf("decoding image: %w", err)
+ }
+
+ imgRect := img.Bounds()
+ imgW, imgH := float64(imgRect.Dx()), float64(imgRect.Dy())
+
+ if imgW > maxWidth {
+
+ newH := imgH * maxWidth / imgW
+ newImg := image.NewRGBA(image.Rect(0, 0, int(maxWidth), int(newH)))
+
+ // Resize
+ draw.BiLinear.Scale(
+ newImg, newImg.Bounds(), img, img.Bounds(), draw.Over, nil,
+ )
+
+ img = newImg
+ }
+
+ switch format {
+ case "jpeg":
+ return jpeg.Encode(out, img, nil)
+ case "png":
+ return png.Encode(out, img)
+ default:
+ return fmt.Errorf("unknown image format %q", format)
+ }
+}
+
+func (a *api) servePostAssetHandler() http.Handler {
+
+ return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
+
+ id := filepath.Base(r.URL.Path)
+
+ maxWidth, err := apiutil.StrToInt(r.FormValue("w"), 0)
+ if err != nil {
+ apiutil.BadRequest(rw, r, fmt.Errorf("invalid w parameter: %w", err))
+ return
+ }
+
+ buf := new(bytes.Buffer)
+
+ err = a.params.PostAssetStore.Get(id, buf)
+
+ if errors.Is(err, post.ErrAssetNotFound) {
+ 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
+ }
+
+ if maxWidth == 0 {
+
+ if _, err := io.Copy(rw, buf); err != nil {
+ apiutil.InternalServerError(
+ rw, r,
+ fmt.Errorf(
+ "copying asset with id %q to response writer: %w",
+ id, err,
+ ),
+ )
+ }
+
+ return
+ }
+
+ switch ext := strings.ToLower(strings.TrimPrefix(filepath.Ext(id), ".")); ext {
+ case "jpg", "jpeg", "png":
+
+ if err := resizeImage(rw, buf, float64(maxWidth)); err != nil {
+ apiutil.InternalServerError(
+ rw, r,
+ fmt.Errorf(
+ "resizing image with id %q to size %d: %w",
+ id, maxWidth, err,
+ ),
+ )
+ }
+
+ default:
+ apiutil.BadRequest(rw, r, fmt.Errorf("cannot resize file with extension %q", ext))
+ return
+ }
+
+ })
+}
diff --git a/srv/src/cmd/mediocre-blog/main.go b/srv/src/cmd/mediocre-blog/main.go
index bdcd1b9..2660ea4 100644
--- a/srv/src/cmd/mediocre-blog/main.go
+++ b/srv/src/cmd/mediocre-blog/main.go
@@ -120,10 +120,12 @@ func main() {
defer postSQLDB.Close()
postStore := post.NewStore(postSQLDB)
+ postAssetStore := post.NewAssetStore(postSQLDB)
apiParams.Logger = logger.WithNamespace("api")
apiParams.PowManager = powMgr
apiParams.PostStore = postStore
+ apiParams.PostAssetStore = postAssetStore
apiParams.MailingList = ml
apiParams.GlobalRoom = chatGlobalRoom
apiParams.UserIDCalculator = chatUserIDCalc
diff --git a/srv/src/go.mod b/srv/src/go.mod
index 2f9bf4b..be8d39e 100644
--- a/srv/src/go.mod
+++ b/srv/src/go.mod
@@ -17,6 +17,7 @@ require (
github.com/tilinna/clock v1.1.0
github.com/ziutek/mymysql v1.5.4 // indirect
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
+ golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
)
diff --git a/srv/src/go.sum b/srv/src/go.sum
index 358c3d6..c0b0014 100644
--- a/srv/src/go.sum
+++ b/srv/src/go.sum
@@ -184,6 +184,8 @@ golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9 h1:LRtI4W37N+KFebI/qV0OFiLUv4GLOWeEW5hn/KEJvxE=
+golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@@ -218,6 +220,7 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=