feat: new rewrite
This commit is contained in:
260
app.go
Normal file
260
app.go
Normal file
@@ -0,0 +1,260 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"html/template"
|
||||
"io"
|
||||
"io/fs"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"capytal.cc/assets"
|
||||
"capytal.cc/internals/natsort"
|
||||
"capytal.cc/templates"
|
||||
"capytal.cc/tinyssert"
|
||||
"forge.capytal.company/loreddev/blogo"
|
||||
"forge.capytal.company/loreddev/blogo/plugin"
|
||||
"forge.capytal.company/loreddev/blogo/plugins"
|
||||
"forge.capytal.company/loreddev/blogo/plugins/gitea"
|
||||
"forge.capytal.company/loreddev/blogo/plugins/markdown"
|
||||
"forge.capytal.company/loreddev/x/smalltrip"
|
||||
"forge.capytal.company/loreddev/x/smalltrip/exception"
|
||||
"forge.capytal.company/loreddev/x/smalltrip/middleware"
|
||||
)
|
||||
|
||||
func NewApp(opts ...Option) (http.Handler, error) {
|
||||
app := &app{
|
||||
assets: assets.Files(),
|
||||
templates: templates.Templates(),
|
||||
|
||||
cache: true,
|
||||
log: slog.New(slog.DiscardHandler),
|
||||
assert: tinyssert.NewDisabledAssertions(),
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(app)
|
||||
}
|
||||
|
||||
app.setup()
|
||||
|
||||
return app, nil
|
||||
}
|
||||
|
||||
type Option func(a *app)
|
||||
|
||||
func WithAssets(assets fs.FS) Option {
|
||||
return func(a *app) { a.assets = assets }
|
||||
}
|
||||
|
||||
func WithTemplates(t templates.ITemplate) Option {
|
||||
return func(a *app) { a.templates = t }
|
||||
}
|
||||
|
||||
func WithCacheDisabled() Option {
|
||||
return func(a *app) { a.cache = false }
|
||||
}
|
||||
|
||||
func WithLogger(logger *slog.Logger) Option {
|
||||
return func(a *app) { a.log = logger }
|
||||
}
|
||||
|
||||
func WithAssertions(assertions tinyssert.Assertions) Option {
|
||||
return func(a *app) { a.assert = assertions }
|
||||
}
|
||||
|
||||
type app struct {
|
||||
router http.Handler
|
||||
|
||||
assets fs.FS
|
||||
templates templates.ITemplate
|
||||
|
||||
cache bool
|
||||
log *slog.Logger
|
||||
assert tinyssert.Assertions
|
||||
}
|
||||
|
||||
func (app *app) setup() {
|
||||
app.assert.NotNil(app.log)
|
||||
|
||||
router := smalltrip.NewRouter(
|
||||
smalltrip.WithAssertions(app.assert),
|
||||
smalltrip.WithLogger(app.log.WithGroup("smalltrip")),
|
||||
)
|
||||
|
||||
router.Use(middleware.Logger(app.log.WithGroup("requests")))
|
||||
|
||||
if app.cache {
|
||||
router.Use(middleware.Cache())
|
||||
} else {
|
||||
router.Use(middleware.DisableCache())
|
||||
}
|
||||
|
||||
router.Handle("/assets/", http.StripPrefix("/assets/", http.FileServerFS(app.assets)))
|
||||
|
||||
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
acceptedLang := r.Header.Get("Accept-Language")
|
||||
if strings.Contains(acceptedLang, "pt-") && r.URL.Query().Get("lang") == "" {
|
||||
http.Redirect(w, r, "/?lang=pt", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
err := app.templates.ExecuteTemplate(w, "homepage", map[string]any{
|
||||
"Lang": r.URL.Query().Get("lang"),
|
||||
})
|
||||
if err != nil {
|
||||
exception.InternalServerError(err).ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
})
|
||||
router.HandleFunc("/README.md/", func(w http.ResponseWriter, r *http.Request) {
|
||||
err := app.templates.ExecuteTemplate(w, "readme", map[string]any{
|
||||
"Lang": r.URL.Query().Get("lang"),
|
||||
})
|
||||
if err != nil {
|
||||
exception.InternalServerError(err).ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
blogEN := app.blogEN()
|
||||
blogPT := app.blogPT()
|
||||
router.Handle("/blog/", http.StripPrefix("/blog/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Query().Get("lang") {
|
||||
case "pt":
|
||||
blogPT.ServeHTTP(w, r)
|
||||
default:
|
||||
blogEN.ServeHTTP(w, r)
|
||||
}
|
||||
})))
|
||||
|
||||
app.router = router
|
||||
}
|
||||
|
||||
func (app *app) blogEN() blogo.Blogo {
|
||||
blog := blogo.New(blogo.Opts{
|
||||
Assertions: app.assert,
|
||||
Logger: app.log.WithGroup("blogo"),
|
||||
})
|
||||
|
||||
gitea := gitea.New("capytal", "capytal.cc-blog", "https://forge.capytal.company")
|
||||
blog.Use(gitea)
|
||||
|
||||
blog.Use(&listRenderer{app.templates, "en"})
|
||||
|
||||
rf := plugins.NewFoldingRenderer(plugins.FoldingRendererOpts{
|
||||
Assertions: app.assert,
|
||||
Logger: app.log.WithGroup("folding-renderer"),
|
||||
})
|
||||
|
||||
rf.Use(markdown.New())
|
||||
rf.Use(&blogPostRenderer{app.templates, "en"})
|
||||
|
||||
blog.Use(rf)
|
||||
blog.Use(plugins.NewPlainText())
|
||||
|
||||
return blog
|
||||
}
|
||||
|
||||
func (app *app) blogPT() blogo.Blogo {
|
||||
blog := blogo.New(blogo.Opts{
|
||||
Assertions: app.assert,
|
||||
Logger: app.log.WithGroup("blogo-pt"),
|
||||
})
|
||||
|
||||
gitea := gitea.New("capytal", "capytal.cc-blog", "https://forge.capytal.company", gitea.Opts{
|
||||
Ref: "main-pt",
|
||||
})
|
||||
blog.Use(gitea)
|
||||
|
||||
blog.Use(&listRenderer{app.templates, "pt"})
|
||||
|
||||
rf := plugins.NewFoldingRenderer(plugins.FoldingRendererOpts{
|
||||
Assertions: app.assert,
|
||||
Logger: app.log.WithGroup("folding-renderer"),
|
||||
})
|
||||
|
||||
rf.Use(markdown.New())
|
||||
rf.Use(&blogPostRenderer{app.templates, "pt"})
|
||||
|
||||
blog.Use(rf)
|
||||
blog.Use(plugins.NewPlainText())
|
||||
|
||||
return blog
|
||||
}
|
||||
|
||||
func (app *app) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
app.assert.NotNil(w)
|
||||
app.assert.NotNil(r)
|
||||
app.assert.NotNil(app.router)
|
||||
|
||||
app.router.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
type blogPostRenderer struct {
|
||||
templates templates.ITemplate
|
||||
lang string
|
||||
}
|
||||
|
||||
var _ plugin.Renderer = (*blogPostRenderer)(nil)
|
||||
|
||||
func (r *blogPostRenderer) Name() string {
|
||||
return "capytal-blogpostrenderer-renderer"
|
||||
}
|
||||
|
||||
func (r *blogPostRenderer) Render(src fs.File, w io.Writer) error {
|
||||
c, err := io.ReadAll(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return r.templates.ExecuteTemplate(w, "blog-post", map[string]any{
|
||||
"Lang": r.lang,
|
||||
"Content": template.HTML(string(c)),
|
||||
})
|
||||
}
|
||||
|
||||
type listRenderer struct {
|
||||
templates templates.ITemplate
|
||||
lang string
|
||||
}
|
||||
|
||||
var _ plugin.Renderer = (*listRenderer)(nil)
|
||||
|
||||
func (r *listRenderer) Name() string {
|
||||
return "capytal-list-renderer"
|
||||
}
|
||||
|
||||
func (r *listRenderer) Render(src fs.File, w io.Writer) error {
|
||||
d, ok := src.(fs.ReadDirFile)
|
||||
if !ok {
|
||||
return errors.New("renderer does not support single files")
|
||||
}
|
||||
|
||||
entries, err := d.ReadDir(-1)
|
||||
if err != nil && !errors.Is(err, io.EOF) {
|
||||
return err
|
||||
}
|
||||
|
||||
sort.Slice(entries, func(i, j int) bool {
|
||||
return natsort.Compare(entries[i].Name(), entries[j].Name())
|
||||
})
|
||||
|
||||
links := map[string]string{}
|
||||
for _, e := range entries {
|
||||
n := e.Name()
|
||||
if strings.HasPrefix(n, ".") ||
|
||||
e.IsDir() ||
|
||||
slices.Contains([]string{
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
}, n) {
|
||||
continue
|
||||
}
|
||||
links[n] = r.lang
|
||||
}
|
||||
|
||||
return r.templates.ExecuteTemplate(w, "blog", links)
|
||||
}
|
||||
Reference in New Issue
Block a user