186 lines
4.0 KiB
Plaintext
186 lines
4.0 KiB
Plaintext
package pages
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"path"
|
|
|
|
"forge.capytal.company/capytal/www/templates/layouts"
|
|
|
|
"forge.capytal.company/loreddev/x/groute/router/rerrors"
|
|
"forge.capytal.company/loreddev/x/groute/router"
|
|
"github.com/yuin/goldmark"
|
|
"github.com/yuin/goldmark/extension"
|
|
"github.com/yuin/goldmark/parser"
|
|
"github.com/yuin/goldmark-meta"
|
|
)
|
|
|
|
type EntryTemplate func(html []byte) templ.Component
|
|
|
|
type Blog struct {
|
|
repo string
|
|
owner string
|
|
endpoint string
|
|
|
|
md goldmark.Markdown
|
|
entryTemplate EntryTemplate
|
|
}
|
|
|
|
type BlogOptions struct {
|
|
EntryTemplate EntryTemplate
|
|
}
|
|
|
|
func NewBlog(owner, repo, endpoint string, opts ...BlogOptions) *Blog {
|
|
/*
|
|
opt := BlogOptions{}
|
|
if len(opts) > 0 {
|
|
opt = opts[0]
|
|
}
|
|
*/
|
|
|
|
u, err := url.Parse(endpoint)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("Blog Forgejo endpoint is not a valid URL: %v", err))
|
|
}
|
|
|
|
md := goldmark.New(
|
|
goldmark.WithExtensions(extension.GFM, meta.Meta),
|
|
goldmark.WithParserOptions(parser.WithAutoHeadingID()),
|
|
)
|
|
|
|
return &Blog{
|
|
repo: repo,
|
|
owner: owner,
|
|
endpoint: u.String(),
|
|
md: md,
|
|
// entryTemplate: opt.EntryTemplate,
|
|
entryTemplate: template,
|
|
}
|
|
}
|
|
|
|
func (p *Blog) Routes() router.Router {
|
|
r := router.NewRouter()
|
|
|
|
r.HandleFunc("/{entry...}", func(w http.ResponseWriter, r *http.Request) {
|
|
pv := r.PathValue("entry")
|
|
if pv == "" {
|
|
p.listPosts(w, r)
|
|
} else {
|
|
p.blogEntry(w, r)
|
|
}
|
|
})
|
|
|
|
return r
|
|
}
|
|
|
|
func (p *Blog) listPosts(w http.ResponseWriter, r *http.Request) {
|
|
_, body, rerr := p.get(fmt.Sprintf("/repos/%s/%s/contents/daily-blogs", p.owner, p.repo))
|
|
if rerr != nil {
|
|
rerr.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
|
|
var list []forgejoFile
|
|
|
|
err := json.Unmarshal(body, &list)
|
|
if err != nil {
|
|
rerrors.InternalError(errors.New("failed to parse list of entries"), err).ServeHTTP(w, r)
|
|
return
|
|
}
|
|
|
|
err = p.blogEntryList(list).Render(r.Context(), w)
|
|
if err != nil {
|
|
rerrors.InternalError(err).ServeHTTP(w, r)
|
|
}
|
|
}
|
|
|
|
templ (p *Blog) blogEntryList(entries []forgejoFile) {
|
|
@layouts.Page() {
|
|
<ul>
|
|
for _, e := range entries {
|
|
<li><a href={ templ.SafeURL(path.Join(".", e.Path)) }>{ e.Name }</a></li>
|
|
}
|
|
</ul>
|
|
}
|
|
}
|
|
|
|
func (p *Blog) blogEntry(w http.ResponseWriter, r *http.Request) {
|
|
_, body, rerr := p.get(fmt.Sprintf("/repos/%s/%s/raw/%s", p.owner, p.repo, r.PathValue("entry")))
|
|
if rerr != nil {
|
|
rerr.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
|
|
buf := bytes.NewBuffer([]byte(""))
|
|
err := p.md.Convert(body, buf)
|
|
if err != nil {
|
|
rerrors.InternalError(errors.New("failed to render markdown"), err).ServeHTTP(w, r)
|
|
return
|
|
}
|
|
|
|
html, err := io.ReadAll(buf)
|
|
if err != nil {
|
|
rerrors.InternalError(errors.New("failed to read markdown html")).ServeHTTP(w, r)
|
|
return
|
|
}
|
|
|
|
err = p.entryTemplate(html).Render(r.Context(), w)
|
|
if err != nil {
|
|
rerrors.InternalError(errors.New("failed to write response"), err).ServeHTTP(w, r)
|
|
return
|
|
}
|
|
}
|
|
|
|
func (p *Blog) get(endpoint string) (http.Header, []byte, *rerrors.RouteError) {
|
|
u, _ := url.Parse(p.endpoint)
|
|
u.Path = path.Join(u.Path, endpoint)
|
|
|
|
r, err := http.Get(u.String())
|
|
if err != nil {
|
|
e := rerrors.InternalError(
|
|
fmt.Errorf("failed to make request to endpoint %s", u.String()),
|
|
err,
|
|
)
|
|
return nil, nil, &e
|
|
}
|
|
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
e := rerrors.InternalError(
|
|
fmt.Errorf("failed to read response body of request to endpoint %s", u.String()),
|
|
err,
|
|
)
|
|
return nil, nil, &e
|
|
} else if r.StatusCode != http.StatusOK {
|
|
e := rerrors.InternalError(
|
|
fmt.Errorf("request to endpoint %s returned non-200 code %q.\n%s", u.String(), r.Status, string(body)),
|
|
)
|
|
return nil, nil, &e
|
|
}
|
|
|
|
return r.Header, body, nil
|
|
}
|
|
|
|
type forgejoFile struct {
|
|
Name string `json:"name"`
|
|
Path string `json:"path"`
|
|
Sha string `json:"sha"`
|
|
LastCommitSha string `json:"last_commit_sha"`
|
|
Type string `json:"type"`
|
|
}
|
|
|
|
templ template(html []byte) {
|
|
@layouts.Page() {
|
|
<div class="w-100% py-10rem flex justify-center">
|
|
<main class="w-60vw">
|
|
@templ.Raw(string(html))
|
|
</main>
|
|
</div>
|
|
}
|
|
}
|