Implement image macro for rendering images

This commit is contained in:
Brian Picciano 2022-05-20 14:30:09 -06:00
parent 16cfbd1915
commit af434077ef
7 changed files with 88 additions and 33 deletions

View File

@ -25,12 +25,10 @@
# pow # pow
export MEDIOCRE_BLOG_POW_SECRET="${config.powSecret}" export MEDIOCRE_BLOG_POW_SECRET="${config.powSecret}"
# listening # http
export MEDIOCRE_BLOG_LISTEN_PROTO="${config.listenProto}" export MEDIOCRE_BLOG_LISTEN_PROTO="${config.listenProto}"
export MEDIOCRE_BLOG_LISTEN_ADDR="${config.listenAddr}" export MEDIOCRE_BLOG_LISTEN_ADDR="${config.listenAddr}"
export MEDIOCRE_BLOG_HTTP_AUTH_USERS='${builtins.toJSON config.httpAuthUsers}'
# api
export MEDIOCRE_BLOG_API_AUTH_USERS='${builtins.toJSON config.httpAuthUsers}'
''; '';
build = buildGoModule { build = buildGoModule {

View File

@ -2,7 +2,6 @@ package main
import ( import (
"context" "context"
"encoding/json"
"os" "os"
"os/signal" "os/signal"
"syscall" "syscall"
@ -56,8 +55,6 @@ func main() {
pathPrefix := cfg.String("path-prefix", "", "Prefix which is optionally applied to all URL paths rendered by the blog") pathPrefix := cfg.String("path-prefix", "", "Prefix which is optionally applied to all URL paths rendered by the blog")
httpAuthUsersStr := cfg.String("http-auth-users", "{}", "JSON object with usernames as values and password hashes (produced by the hash-password binary) as values. Denotes users which are able to edit server-side data")
// initialization // initialization
err := cfg.Init(ctx) err := cfg.Init(ctx)
@ -131,11 +128,6 @@ func main() {
postStore := post.NewStore(postSQLDB) postStore := post.NewStore(postSQLDB)
postAssetStore := post.NewAssetStore(postSQLDB) postAssetStore := post.NewAssetStore(postSQLDB)
var httpAuthUsers map[string]string
if err := json.Unmarshal([]byte(*httpAuthUsersStr), &httpAuthUsers); err != nil {
logger.Fatal(ctx, "unmarshaling -http-auth-users", err)
}
httpParams.Logger = logger.WithNamespace("http") httpParams.Logger = logger.WithNamespace("http")
httpParams.PowManager = powMgr httpParams.PowManager = powMgr
httpParams.PathPrefix = *pathPrefix httpParams.PathPrefix = *pathPrefix
@ -144,7 +136,6 @@ func main() {
httpParams.MailingList = ml httpParams.MailingList = ml
httpParams.GlobalRoom = chatGlobalRoom httpParams.GlobalRoom = chatGlobalRoom
httpParams.UserIDCalculator = chatUserIDCalc httpParams.UserIDCalculator = chatUserIDCalc
httpParams.AuthUsers = httpAuthUsers
logger.Info(ctx, "listening") logger.Info(ctx, "listening")
httpAPI, err := http.New(httpParams) httpAPI, err := http.New(httpParams)

View File

@ -4,6 +4,7 @@ package http
import ( import (
"context" "context"
"embed" "embed"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"html/template" "html/template"
@ -57,6 +58,15 @@ type Params struct {
func (p *Params) SetupCfg(cfg *cfg.Cfg) { func (p *Params) SetupCfg(cfg *cfg.Cfg) {
cfg.StringVar(&p.ListenProto, "listen-proto", "tcp", "Protocol to listen for HTTP requests with") cfg.StringVar(&p.ListenProto, "listen-proto", "tcp", "Protocol to listen for HTTP requests with")
cfg.StringVar(&p.ListenAddr, "listen-addr", ":4000", "Address/path to listen for HTTP requests on") cfg.StringVar(&p.ListenAddr, "listen-addr", ":4000", "Address/path to listen for HTTP requests on")
httpAuthUsersStr := cfg.String("http-auth-users", "{}", "JSON object with usernames as values and password hashes (produced by the hash-password binary) as values. Denotes users which are able to edit server-side data")
cfg.OnInit(func(context.Context) error {
if err := json.Unmarshal([]byte(*httpAuthUsersStr), &p.AuthUsers); err != nil {
return fmt.Errorf("unmarshaling -http-auth-users: %w", err)
}
return nil
})
} }
// Annotate implements mctx.Annotator interface. // Annotate implements mctx.Annotator interface.

View File

@ -17,6 +17,15 @@ import (
"golang.org/x/image/draw" "golang.org/x/image/draw"
) )
func isImgResizable(id string) bool {
switch strings.ToLower(filepath.Ext(id)) {
case ".jpg", ".jpeg", ".png":
return true
default:
return false
}
}
func resizeImage(out io.Writer, in io.Reader, maxWidth float64) error { func resizeImage(out io.Writer, in io.Reader, maxWidth float64) error {
img, format, err := image.Decode(in) img, format, err := image.Decode(in)
@ -123,24 +132,21 @@ func (a *api) getPostAssetHandler() http.Handler {
return return
} }
switch ext := strings.ToLower(strings.TrimPrefix(filepath.Ext(id), ".")); ext { if !isImgResizable(id) {
case "jpg", "jpeg", "png": apiutil.BadRequest(rw, r, fmt.Errorf("cannot resize file %q", id))
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 return
} }
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,
),
)
}
}) })
} }

View File

@ -1,6 +1,7 @@
package http package http
import ( import (
"bytes"
"errors" "errors"
"fmt" "fmt"
"html/template" "html/template"
@ -23,13 +24,24 @@ type postTplPayload struct {
} }
func (a *api) postToPostTplPayload(storedPost post.StoredPost) (postTplPayload, error) { func (a *api) postToPostTplPayload(storedPost post.StoredPost) (postTplPayload, error) {
bodyTpl, err := a.parseTpl(storedPost.Body)
if err != nil {
return postTplPayload{}, fmt.Errorf("parsing post body as template: %w", err)
}
bodyBuf := new(bytes.Buffer)
if err := bodyTpl.Execute(bodyBuf, nil); err != nil {
return postTplPayload{}, fmt.Errorf("executing post body as template: %w", err)
}
parserExt := parser.CommonExtensions | parser.AutoHeadingIDs parserExt := parser.CommonExtensions | parser.AutoHeadingIDs
parser := parser.NewWithExtensions(parserExt) parser := parser.NewWithExtensions(parserExt)
htmlFlags := html.CommonFlags | html.HrefTargetBlank htmlFlags := html.CommonFlags | html.HrefTargetBlank
htmlRenderer := html.NewRenderer(html.RendererOptions{Flags: htmlFlags}) htmlRenderer := html.NewRenderer(html.RendererOptions{Flags: htmlFlags})
renderedBody := markdown.ToHTML([]byte(storedPost.Body), parser, htmlRenderer) renderedBody := markdown.ToHTML(bodyBuf.Bytes(), parser, htmlRenderer)
tplPayload := postTplPayload{ tplPayload := postTplPayload{
StoredPost: storedPost, StoredPost: storedPost,

View File

@ -1,6 +1,7 @@
package http package http
import ( import (
"bytes"
"embed" "embed"
"fmt" "fmt"
"html/template" "html/template"
@ -27,7 +28,7 @@ func mustReadTplFile(fileName string) string {
return string(b) return string(b)
} }
func (a *api) mustParseTpl(name string) *template.Template { func (a *api) parseTpl(tplBody string) (*template.Template, error) {
blogURL := func(path string) string { blogURL := func(path string) string {
@ -43,7 +44,9 @@ func (a *api) mustParseTpl(name string) *template.Template {
return path return path
} }
tpl := template.New("").Funcs(template.FuncMap{ tpl := template.New("root")
tpl = tpl.Funcs(template.FuncMap{
"BlogURL": blogURL, "BlogURL": blogURL,
"StaticURL": func(path string) string { "StaticURL": func(path string) string {
path = filepath.Join("static", path) path = filepath.Join("static", path)
@ -62,9 +65,39 @@ func (a *api) mustParseTpl(name string) *template.Template {
}, },
}) })
tpl = template.Must(tpl.Parse(mustReadTplFile(name))) tpl = template.Must(tpl.New("image.html").Parse(mustReadTplFile("image.html")))
return tpl tpl = tpl.Funcs(template.FuncMap{
"Image": func(id string) (template.HTML, error) {
tplPayload := struct {
ID string
Resizable bool
}{
ID: id,
Resizable: isImgResizable(id),
}
buf := new(bytes.Buffer)
if err := tpl.ExecuteTemplate(buf, "image.html", tplPayload); err != nil {
return "", err
}
return template.HTML(buf.Bytes()), nil
},
})
var err error
if tpl, err = tpl.New("").Parse(tplBody); err != nil {
return nil, err
}
return tpl, nil
}
func (a *api) mustParseTpl(name string) *template.Template {
return template.Must(a.parseTpl(mustReadTplFile(name)))
} }
func (a *api) mustParseBasedTpl(name string) *template.Template { func (a *api) mustParseBasedTpl(name string) *template.Template {

View File

@ -0,0 +1,5 @@
<div style="text-align: center;">
<a href="{{ AssetURL .ID }}" target="_blank">
<img src="{{ AssetURL .ID }}{{ if .Resizable }}?w=800{{ end }}" />
</a>
</div>