Initial implementation of post rendering over gmi
This commit is contained in:
parent
84c1322c44
commit
7878db5c95
@ -124,6 +124,8 @@ func main() {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
gmiParams.Logger = logger.WithNamespace("gmi")
|
gmiParams.Logger = logger.WithNamespace("gmi")
|
||||||
|
gmiParams.PostStore = postStore
|
||||||
|
gmiParams.PostAssetStore = postAssetStore
|
||||||
|
|
||||||
logger.Info(ctx, "starting gmi api")
|
logger.Info(ctx, "starting gmi api")
|
||||||
gmiAPI, err := gmi.New(gmiParams)
|
gmiAPI, err := gmi.New(gmiParams)
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
"git.sr.ht/~adnano/go-gemini"
|
"git.sr.ht/~adnano/go-gemini"
|
||||||
"git.sr.ht/~adnano/go-gemini/certificate"
|
"git.sr.ht/~adnano/go-gemini/certificate"
|
||||||
"github.com/mediocregopher/blog.mediocregopher.com/srv/cfg"
|
"github.com/mediocregopher/blog.mediocregopher.com/srv/cfg"
|
||||||
|
"github.com/mediocregopher/blog.mediocregopher.com/srv/post"
|
||||||
"github.com/mediocregopher/mediocre-go-lib/v2/mctx"
|
"github.com/mediocregopher/mediocre-go-lib/v2/mctx"
|
||||||
"github.com/mediocregopher/mediocre-go-lib/v2/mlog"
|
"github.com/mediocregopher/mediocre-go-lib/v2/mlog"
|
||||||
)
|
)
|
||||||
@ -19,7 +20,11 @@ import (
|
|||||||
// Params are used to instantiate a new API instance. All fields are required
|
// Params are used to instantiate a new API instance. All fields are required
|
||||||
// unless otherwise noted.
|
// unless otherwise noted.
|
||||||
type Params struct {
|
type Params struct {
|
||||||
Logger *mlog.Logger
|
Logger *mlog.Logger
|
||||||
|
|
||||||
|
PostStore post.Store
|
||||||
|
PostAssetStore post.AssetStore
|
||||||
|
|
||||||
PublicURL *url.URL
|
PublicURL *url.URL
|
||||||
ListenAddr string
|
ListenAddr string
|
||||||
CertificatesPath string
|
CertificatesPath string
|
||||||
@ -86,9 +91,14 @@ func New(params Params) (API, error) {
|
|||||||
params: params,
|
params: params,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handler, err := a.handler()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("constructing handler: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
a.srv = &gemini.Server{
|
a.srv = &gemini.Server{
|
||||||
Addr: params.ListenAddr,
|
Addr: params.ListenAddr,
|
||||||
Handler: a.handler(),
|
Handler: handler,
|
||||||
GetCertificate: certStore.Get,
|
GetCertificate: certStore.Get,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,14 +119,47 @@ func (a *api) Shutdown(ctx context.Context) error {
|
|||||||
return a.srv.Shutdown(ctx)
|
return a.srv.Shutdown(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *api) handler() gemini.Handler {
|
func postsMiddleware(tplHandler gemini.Handler) gemini.Handler {
|
||||||
|
|
||||||
return gemini.HandlerFunc(func(
|
return gemini.HandlerFunc(func(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
rw gemini.ResponseWriter,
|
rw gemini.ResponseWriter,
|
||||||
r *gemini.Request,
|
r *gemini.Request,
|
||||||
) {
|
) {
|
||||||
fmt.Fprintf(rw, "# Test\n\n")
|
|
||||||
fmt.Fprintf(rw, "HELLO WORLD\n\n")
|
id := strings.TrimPrefix(r.URL.Path, "/posts/")
|
||||||
fmt.Fprintf(rw, "=> gemini://midnight.pub Hit the pub\n\n")
|
id = strings.TrimSuffix(id, ".gmi")
|
||||||
|
|
||||||
|
if id == "index" {
|
||||||
|
tplHandler.ServeGemini(ctx, rw, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
query := r.URL.Query()
|
||||||
|
query.Set("id", id)
|
||||||
|
r.URL.RawQuery = query.Encode()
|
||||||
|
|
||||||
|
r.URL.Path = "/posts/post.gmi"
|
||||||
|
|
||||||
|
tplHandler.ServeGemini(ctx, rw, r)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *api) handler() (gemini.Handler, error) {
|
||||||
|
|
||||||
|
tplHandler, err := a.tplHandler()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("generating tpl handler: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mux := new(gemini.Mux)
|
||||||
|
mux.Handle("/posts/", postsMiddleware(tplHandler))
|
||||||
|
mux.Handle("/", tplHandler)
|
||||||
|
|
||||||
|
h := mux
|
||||||
|
|
||||||
|
// TODO logging
|
||||||
|
// TODO caching
|
||||||
|
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
134
src/gmi/tpl.go
Normal file
134
src/gmi/tpl.go
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
package gmi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"embed"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/fs"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"git.sr.ht/~adnano/go-gemini"
|
||||||
|
"github.com/mediocregopher/blog.mediocregopher.com/srv/post"
|
||||||
|
"github.com/mediocregopher/mediocre-go-lib/v2/mctx"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed tpl
|
||||||
|
var tplFS embed.FS
|
||||||
|
|
||||||
|
type rendererGetPostsRes struct {
|
||||||
|
Posts []post.StoredPost
|
||||||
|
HasMore bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type renderer struct {
|
||||||
|
url *url.URL
|
||||||
|
postStore post.Store
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r renderer) GetPosts(page, count int) (rendererGetPostsRes, error) {
|
||||||
|
posts, hasMore, err := r.postStore.Get(page, count)
|
||||||
|
return rendererGetPostsRes{posts, hasMore}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r renderer) GetPostByID(id string) (post.StoredPost, error) {
|
||||||
|
p, err := r.postStore.GetByID(id)
|
||||||
|
if err != nil {
|
||||||
|
return post.StoredPost{}, fmt.Errorf("fetching post %q: %w", id, err)
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r renderer) GetQueryValue(key, def string) string {
|
||||||
|
v := r.url.Query().Get(key)
|
||||||
|
if v == "" {
|
||||||
|
v = def
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r renderer) GetQueryIntValue(key string, def int) (int, error) {
|
||||||
|
vStr := r.GetQueryValue(key, strconv.Itoa(def))
|
||||||
|
return strconv.Atoi(vStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r renderer) Add(a, b int) int { return a + b }
|
||||||
|
|
||||||
|
func (a *api) tplHandler() (gemini.Handler, error) {
|
||||||
|
|
||||||
|
allTpls := template.New("")
|
||||||
|
|
||||||
|
err := fs.WalkDir(tplFS, "tpl", func(path string, d fs.DirEntry, err error) error {
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := fs.ReadFile(tplFS, path)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
name := strings.TrimPrefix(path, "tpl/")
|
||||||
|
|
||||||
|
allTpls, err = allTpls.New(name).Parse(string(body))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("parsing %q as template: %w", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parsing templates: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return gemini.HandlerFunc(func(
|
||||||
|
ctx context.Context,
|
||||||
|
rw gemini.ResponseWriter,
|
||||||
|
r *gemini.Request,
|
||||||
|
) {
|
||||||
|
|
||||||
|
if strings.HasSuffix(r.URL.Path, "/") {
|
||||||
|
r.URL.Path += "index.gmi"
|
||||||
|
}
|
||||||
|
|
||||||
|
tplPath := strings.TrimPrefix(r.URL.Path, "/")
|
||||||
|
|
||||||
|
ctx = mctx.Annotate(ctx,
|
||||||
|
"url", r.URL,
|
||||||
|
"tplPath", tplPath,
|
||||||
|
)
|
||||||
|
|
||||||
|
tpl := allTpls.Lookup(tplPath)
|
||||||
|
|
||||||
|
if tpl == nil {
|
||||||
|
a.params.Logger.WarnString(ctx, "page not found")
|
||||||
|
rw.WriteHeader(gemini.StatusNotFound, "Page not found, sorry!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
|
||||||
|
err := tpl.Execute(buf, renderer{
|
||||||
|
url: r.URL,
|
||||||
|
postStore: a.params.PostStore,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
a.params.Logger.Error(ctx, "rendering error", err)
|
||||||
|
rw.WriteHeader(gemini.StatusTemporaryFailure, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
io.Copy(rw, buf)
|
||||||
|
}), nil
|
||||||
|
}
|
3
src/gmi/tpl/index.gmi
Normal file
3
src/gmi/tpl/index.gmi
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Index
|
||||||
|
|
||||||
|
=> /posts/index.gmi See all posts
|
18
src/gmi/tpl/posts/index.gmi
Normal file
18
src/gmi/tpl/posts/index.gmi
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# mediocregopher's Posts
|
||||||
|
|
||||||
|
{{ $page := .GetQueryIntValue "page" 0 -}}
|
||||||
|
{{ $getPostsRes := .GetPosts $page 20 -}}
|
||||||
|
|
||||||
|
{{ if gt $page 0 -}}
|
||||||
|
=> /posts.gmi?page={{ .Add $page -1 }} Previous Page
|
||||||
|
|
||||||
|
{{ end -}}
|
||||||
|
|
||||||
|
{{ range $getPostsRes.Posts -}}
|
||||||
|
=> /posts/{{ .ID }}.gmi {{ .PublishedAt.Format "2006-01-02" }} - {{ .Title }}
|
||||||
|
|
||||||
|
{{ end -}}
|
||||||
|
|
||||||
|
{{ if $getPostsRes.HasMore -}}
|
||||||
|
=> /posts.gmi?page={{ .Add $page 1 }} Next page
|
||||||
|
{{ end -}}
|
9
src/gmi/tpl/posts/post.gmi
Normal file
9
src/gmi/tpl/posts/post.gmi
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{{ $post := .GetPostByID (.GetQueryValue "id" "") -}}
|
||||||
|
|
||||||
|
# {{ $post.Title }}
|
||||||
|
|
||||||
|
{{ if ne $post.Description "" -}}
|
||||||
|
> {{ $post.Description }}
|
||||||
|
{{ end -}}
|
||||||
|
|
||||||
|
{{ $post.Body }}
|
Loading…
Reference in New Issue
Block a user