v2 #43

Merged
h4kor merged 41 commits from v2 into main 2023-07-19 19:09:19 +00:00
13 changed files with 359 additions and 38 deletions
Showing only changes of commit 687707a3e8 - Show all commits

View File

@ -34,5 +34,16 @@ func (s *EntryService) FindAllByType(types *[]string) ([]model.Entry, error) {
} }
func (s *EntryService) FindAll() ([]model.Entry, error) { func (s *EntryService) FindAll() ([]model.Entry, error) {
return s.EntryRepository.FindAll(nil) entries, err := s.EntryRepository.FindAll(nil)
if err != nil {
return nil, err
}
// filter unpublished entries
publishedEntries := make([]model.Entry, 0)
for _, entry := range entries {
if entry.PublishedAt() != nil && !entry.PublishedAt().IsZero() {
publishedEntries = append(publishedEntries, entry)
}
}
return publishedEntries, nil
} }

BIN
assets/owl.png Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

200
assets/owl.svg Normal file
View File

@ -0,0 +1,200 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="100mm"
height="100mm"
viewBox="0 0 100 100"
version="1.1"
id="svg8"
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
sodipodi:docname="owl.svg">
<defs
id="defs2">
<inkscape:path-effect
effect="mirror_symmetry"
start_point="101.5,113.98198"
end_point="101.5,177.55836"
center_point="101.5,145.77017"
id="path-effect4762"
is_visible="true"
mode="free"
discard_orig_path="false"
fuse_paths="true"
oposite_fuse="false" />
<inkscape:path-effect
effect="mirror_symmetry"
start_point="101.6,77.962793"
end_point="101.6,178.13471"
center_point="101.6,128.04875"
id="path-effect4630"
is_visible="true"
mode="free"
discard_orig_path="false"
fuse_paths="true"
oposite_fuse="false" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.98994949"
inkscape:cx="-5.4384962"
inkscape:cy="476.07169"
inkscape:document-units="mm"
inkscape:current-layer="layer5"
showgrid="false"
inkscape:window-width="2560"
inkscape:window-height="1391"
inkscape:window-x="0"
inkscape:window-y="25"
inkscape:window-maximized="1" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Body"
inkscape:groupmode="layer"
id="layer1"
style="display:inline"
transform="translate(0,-197)" />
<g
inkscape:groupmode="layer"
id="layer6"
inkscape:label="Front"
transform="translate(0,-197)" />
<g
inkscape:groupmode="layer"
id="layer5"
inkscape:label="Ref"
style="display:inline"
transform="translate(0,-197)" />
<g
inkscape:groupmode="layer"
id="layer4"
inkscape:label="Feet"
transform="translate(0,-197)" />
<g
inkscape:groupmode="layer"
id="layer3"
inkscape:label="Nose"
transform="translate(0,-197)" />
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Eyes"
transform="translate(0,-197)">
<path
style="display:inline;opacity:1;fill:#686560;fill-opacity:1;stroke:#000000;stroke-width:1.46500003;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 72.665922,98.468004 c -3.804657,-4.064063 -8.937651,-7.726186 -7.559523,-20.505208 1.511903,2.173362 6.898064,8.031993 12.095236,8.031993 7.291682,0 22.616673,0.08369 24.398365,0.09355 1.78169,-0.0099 17.10668,-0.09355 24.39836,-0.09355 5.19718,0 10.58334,-5.858631 12.09524,-8.031993 1.37813,12.779022 -3.75487,16.441145 -7.55952,20.505208 0,0 4.84169,14.668796 9.88119,20.229646 10.76577,11.87955 9.28012,38.96737 -13.7775,54.51257 -4.9083,3.30912 -14.73063,5.13452 -25.03777,4.90519 -10.307138,0.22933 -20.129467,-1.59607 -25.037771,-4.90519 -23.057616,-15.5452 -24.543272,-42.63302 -13.777496,-54.51257 5.039494,-5.56085 9.881189,-20.229646 9.881189,-20.229646 z"
id="path46"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccsccssc"
inkscape:path-effect="#path-effect4630"
inkscape:original-d="m 72.665922,98.468004 c -3.804657,-4.064063 -8.937651,-7.726186 -7.559523,-20.505208 1.511903,2.173362 6.898064,8.031993 12.095236,8.031993 7.748513,0 24.568455,0.0945 24.568455,0.0945 9.17095,40.967981 -1.73329,86.382581 24.08866,86.819411 -14.73188,7.45015 -40.288984,6.3743 -49.296521,0.30152 -23.057616,-15.5452 -24.543272,-42.63302 -13.777496,-54.51257 5.039494,-5.56085 9.881189,-20.229646 9.881189,-20.229646 z"
transform="matrix(0.89013051,0,0,0.89013051,-40.43726,131.70004)" />
<path
style="display:inline;fill:#fbe9c4;fill-opacity:1;stroke:#262626;stroke-width:4.06500006;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 74.568197,129.95654 c -1.207452,8.188 -1.187282,34.70878 17.572973,44.03265 3.623482,1.36045 5.80487,3.13501 9.35883,3.62684 3.55396,-0.49183 5.73535,-2.26639 9.35883,-3.62684 18.76025,-9.32387 18.78042,-35.84465 17.57297,-44.03265 C 126.73222,117.4929 107.35585,115.8851 101.5,115.68436 95.644152,115.8851 76.267784,117.4929 74.568197,129.95654 Z"
id="path4760"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccscc"
inkscape:path-effect="#path-effect4762"
inkscape:original-d="m 74.568197,129.95654 c -1.207452,8.188 -1.187282,34.70878 17.572973,44.03265 4.717147,1.77107 6.990299,4.24396 13.02939,3.67496 5.2517,-4.28707 -4.51571,-38.74118 3.27405,-50.98166 11.76071,-18.48023 -5.27857,-11.02487 -5.27857,-11.02487 0,0 -26.593322,-0.4009 -28.597843,14.29892 z"
transform="matrix(0.89013051,0,0,0.89013051,-40.43726,131.70004)" />
<rect
style="opacity:1;fill:#674808;fill-opacity:1;stroke:#efc48c;stroke-width:2.72825003;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4767"
width="23.046696"
height="2.0186887"
x="39.805622"
y="258.96619" />
<rect
style="opacity:1;fill:#674808;fill-opacity:1;stroke:#efc48c;stroke-width:2.72825003;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4767-7"
width="23.046698"
height="2.0186887"
x="39.805622"
y="266.51617" />
<rect
style="opacity:1;fill:#674808;fill-opacity:1;stroke:#efc48c;stroke-width:2.72825003;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4767-7-0"
width="23.046698"
height="2.0186887"
x="39.80563"
y="274.1886" />
<path
style="display:inline;opacity:1;fill:#f1cd6b;fill-opacity:1;stroke:#000000;stroke-width:1.21502817;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 37.113903,280.91958 a 11.943909,11.943909 0 0 0 -11.943581,11.94359 h 23.88762 A 11.943909,11.943909 0 0 0 37.113903,280.91958 Z"
id="path4732"
inkscape:connector-curvature="0" />
<path
inkscape:connector-curvature="0"
style="display:inline;opacity:1;fill:#f1cd6b;fill-opacity:1;stroke:#000000;stroke-width:1.21502817;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 62.767977,281.00369 A 11.943909,11.943909 0 0 0 50.824392,292.94728 H 74.712015 A 11.943909,11.943909 0 0 0 62.767977,281.00369 Z"
id="path4732-2" />
<path
style="fill:#dd9829;fill-opacity:1;stroke:#000000;stroke-width:1.21502817;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 45.440938,246.0794 c 0,0 2.139571,9.72892 5.947619,9.7541 3.839356,0.0254 6.126056,-9.7541 6.126056,-9.7541 l -6.140761,-8.69398 z"
id="path4721"
inkscape:connector-curvature="0"
sodipodi:nodetypes="caccc" />
<path
style="opacity:1;fill:#fedf89;fill-opacity:1;stroke:#000000;stroke-width:1.95828712;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 34.506691,216.30236 a 18.692076,18.692076 0 0 0 -18.692058,18.69205 18.692076,18.692076 0 0 0 18.692058,18.69206 18.692076,18.692076 0 0 0 16.928008,-10.81661 18.692078,18.692078 0 0 0 16.968945,10.90079 18.692078,18.692078 0 0 0 18.69206,-18.69206 18.692078,18.692078 0 0 0 -18.69206,-18.69252 18.692078,18.692078 0 0 0 -16.929847,10.81983 18.692076,18.692076 0 0 0 -16.967106,-10.90354 z"
id="path4607"
inkscape:connector-curvature="0" />
<path
style="opacity:1;fill:#fffefc;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 64.955116,221.31207 a 13.794374,13.429993 0 0 0 -13.301924,9.88009 13.794374,13.429993 0 0 0 -13.277543,-9.79636 13.794374,13.429993 0 0 0 -13.794111,13.43025 13.794374,13.429993 0 0 0 13.794111,13.4298 13.794374,13.429993 0 0 0 13.301003,-9.92837 13.794374,13.429993 0 0 0 13.278464,9.8442 13.794374,13.429993 0 0 0 13.794572,-13.4298 13.794374,13.429993 0 0 0 -13.794572,-13.42981 z"
id="path4609"
inkscape:connector-curvature="0" />
<ellipse
style="opacity:1;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path4611"
cx="40.394402"
cy="235.28864"
rx="8.9999876"
ry="8.8738194" />
<circle
style="opacity:1;fill:#fffefc;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path4609-3"
cy="230.89021"
cx="35.68087"
r="5.6899729" />
<ellipse
style="opacity:1;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path4611-9"
cx="-63.020538"
cy="235.37274"
rx="8.9999876"
ry="8.8738194"
transform="scale(-1,1)" />
<circle
style="opacity:1;fill:#fffefc;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path4609-3-1"
cx="-67.734055"
cy="230.97433"
transform="scale(-1,1)"
r="5.0387244" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -3,4 +3,6 @@ package model
type Author struct { type Author struct {
Name string Name string
PasswordHash string PasswordHash string
FullUrl string
AvatarUrl string
} }

View File

@ -4,15 +4,18 @@
<head> <head>
<meta charset='utf-8'> <meta charset='utf-8'>
<title>{{template "title" .}} - Owl Blog</title> <title>{{template "title" .}} - Owl Blog</title>
<link rel='stylesheet' href='/static/pico.min.css'>
</head> </head>
<body> <body>
<header> <header class="container">
Owl Blog <a href="/">Owl Blog</a>
</header> </header>
<main> <main class="container">
{{template "main" .}} {{template "main" .}}
</main> </main>
<footer>Powered by <a href='https://golang.org/'>Go</a></footer> <footer class="container">
Powered by <a href='https://golang.org/'>Go</a>
</footer>
</body> </body>
</html> </html>
{{end}} {{end}}

View File

@ -1,7 +1,3 @@
<h1>
{{.MetaData.Title}}
</h1>
<img src="/media/{{.MetaData.ImageId}}"> <img src="/media/{{.MetaData.ImageId}}">
{{.MetaData.Content}} {{.MetaData.Content}}

View File

@ -1,17 +1,33 @@
{{define "title"}}{{.Title}}{{end}} {{define "title"}}{{.Entry.Title}}{{end}}
{{define "main"}} {{define "main"}}
{{if .Title}} <div class="h-entry">
<h1>{{.Title}}</h1> <hgroup>
{{end}} {{if .Entry.Title}}
<p> <h1 class="p-name">{{.Entry.Title}}</h1>
Published: {{.PublishedAt}} {{end}}
</p> <small>
<a class="u-url" href="">#</a>
Published:
<time class="dt-published" datetime="{{.Entry.PublishedAt}}">
{{.Entry.PublishedAt}}
</time>
{{ if .Author.Name }}
by
<a class="p-author h-card" href="{{.Author.FullUrl}}">
{{ if .Author.AvatarUrl }}
<img class="u-photo u-logo" style="height: 1em;" src="{{ .Author.AvatarUrl }}" alt="{{ .Author.Config.Title }}" />
{{ end }}
{{.Author.Name}}
</a>
{{ end }}
</small>
</hgroup>
{{.Entry.Content}}
{{.Content}} </div>
{{end}} {{end}}

View File

@ -2,21 +2,43 @@
{{define "main"}} {{define "main"}}
{{ range . }} <div class="h-feed">
<div> {{ range .Entries }}
<h2> <div class="h-entry">
<a href="/posts/{{ .ID }}"> <hgroup>
<h3>
<a class="u-url" href="/posts/{{ .ID }}">
{{if .Title}} {{if .Title}}
{{ .Title }} {{ .Title }}
{{else}} {{else}}
# #
{{end}} {{end}}
</a> </a>
</h2> </h3>
<p>{{ .PublishedAt }}</p> <small style="font-size: 0.75em;">
<time class="dt-published" datetime="{{ .PublishedAt }}">{{ .PublishedAt }}</time>
</small>
</hgroup>
{{ .Content }} {{ .Content }}
</div> </div>
<hr> <hr>
{{ end }} {{ end }}
</div>
<hr>
<nav class="row">
{{ if not .FirstPage }}
<div>
<a href="/?page={{ .PrevPage }}">Prev</a>
</div>
{{ end }}
<div>Page {{.Page}}</div>
{{ if not .LastPage }}
<div>
<a href="/?page={{ .NextPage }}">Next</a>
</div>
{{ end }}
</nav>
{{end}} {{end}}

View File

@ -1,12 +1,18 @@
package web package web
import ( import (
"embed"
"net/http"
"owl-blogs/app" "owl-blogs/app"
"owl-blogs/web/middleware" "owl-blogs/web/middleware"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/filesystem"
) )
//go:embed static/*
var embedDirStatic embed.FS
type WebApp struct { type WebApp struct {
FiberApp *fiber.App FiberApp *fiber.App
EntryService *app.EntryService EntryService *app.EntryService
@ -25,7 +31,7 @@ func NewWebApp(
indexHandler := NewIndexHandler(entryService) indexHandler := NewIndexHandler(entryService)
listHandler := NewListHandler(entryService) listHandler := NewListHandler(entryService)
entryHandler := NewEntryHandler(entryService, typeRegistry) entryHandler := NewEntryHandler(entryService, typeRegistry, authorService)
mediaHandler := NewMediaHandler(binService) mediaHandler := NewMediaHandler(binService)
rssHandler := NewRSSHandler(entryService) rssHandler := NewRSSHandler(entryService)
loginHandler := NewLoginHandler(authorService) loginHandler := NewLoginHandler(authorService)
@ -43,7 +49,12 @@ func NewWebApp(
editor.Get("/:editor/", editorHandler.HandleGet) editor.Get("/:editor/", editorHandler.HandleGet)
editor.Post("/:editor/", editorHandler.HandlePost) editor.Post("/:editor/", editorHandler.HandlePost)
// app.ServeFiles("/static/*filepath", http.Dir(repo.StaticDir())) // app.Static("/static/*filepath", http.Dir(repo.StaticDir()))
app.Use("/static", filesystem.New(filesystem.Config{
Root: http.FS(embedDirStatic),
PathPrefix: "static",
Browse: false,
}))
app.Get("/", indexHandler.Handle) app.Get("/", indexHandler.Handle)
app.Get("/lists/:list/", listHandler.Handle) app.Get("/lists/:list/", listHandler.Handle)
// Media // Media

View File

@ -2,6 +2,7 @@ package web
import ( import (
"owl-blogs/app" "owl-blogs/app"
"owl-blogs/domain/model"
"owl-blogs/render" "owl-blogs/render"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
@ -9,11 +10,17 @@ import (
type EntryHandler struct { type EntryHandler struct {
entrySvc *app.EntryService entrySvc *app.EntryService
authorSvc *app.AuthorService
registry *app.EntryTypeRegistry registry *app.EntryTypeRegistry
} }
func NewEntryHandler(entryService *app.EntryService, registry *app.EntryTypeRegistry) *EntryHandler { type entryData struct {
return &EntryHandler{entrySvc: entryService, registry: registry} Entry model.Entry
Author *model.Author
}
func NewEntryHandler(entryService *app.EntryService, registry *app.EntryTypeRegistry, authorService *app.AuthorService) *EntryHandler {
return &EntryHandler{entrySvc: entryService, authorSvc: authorService, registry: registry}
} }
func (h *EntryHandler) Handle(c *fiber.Ctx) error { func (h *EntryHandler) Handle(c *fiber.Ctx) error {
@ -25,5 +32,10 @@ func (h *EntryHandler) Handle(c *fiber.Ctx) error {
return err return err
} }
return render.RenderTemplateWithBase(c, "views/entry", entry) author, err := h.authorSvc.FindByName("h4kor")
if err != nil {
return err
}
return render.RenderTemplateWithBase(c, "views/entry", entryData{Entry: entry, Author: author})
} }

View File

@ -2,8 +2,10 @@ package web
import ( import (
"owl-blogs/app" "owl-blogs/app"
"owl-blogs/domain/model"
"owl-blogs/render" "owl-blogs/render"
"sort" "sort"
"strconv"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )
@ -16,19 +18,60 @@ func NewIndexHandler(entryService *app.EntryService) *IndexHandler {
return &IndexHandler{entrySvc: entryService} return &IndexHandler{entrySvc: entryService}
} }
type indexRenderData struct {
Entries []model.Entry
Page int
NextPage int
PrevPage int
FirstPage bool
LastPage bool
}
func (h *IndexHandler) Handle(c *fiber.Ctx) error { func (h *IndexHandler) Handle(c *fiber.Ctx) error {
c.Set(fiber.HeaderContentType, fiber.MIMETextHTML) c.Set(fiber.HeaderContentType, fiber.MIMETextHTML)
entries, err := h.entrySvc.FindAll() entries, err := h.entrySvc.FindAll()
if err != nil {
return err
}
// sort entries by date descending // sort entries by date descending
sort.Slice(entries, func(i, j int) bool { sort.Slice(entries, func(i, j int) bool {
return entries[i].PublishedAt().After(*entries[j].PublishedAt()) return entries[i].PublishedAt().After(*entries[j].PublishedAt())
}) })
// pagination
page := c.Query("page")
if page == "" {
page = "1"
}
pageNum, err := strconv.Atoi(page)
if err != nil {
pageNum = 1
}
limit := 10
offset := (pageNum - 1) * limit
lastPage := false
if offset > len(entries) {
offset = len(entries)
lastPage = true
}
if offset+limit > len(entries) {
limit = len(entries) - offset
lastPage = true
}
entries = entries[offset : offset+limit]
if err != nil { if err != nil {
return err return err
} }
return render.RenderTemplateWithBase(c, "views/index", entries) return render.RenderTemplateWithBase(c, "views/index", indexRenderData{
Entries: entries,
Page: pageNum,
NextPage: pageNum + 1,
PrevPage: pageNum - 1,
FirstPage: pageNum == 1,
LastPage: lastPage,
})
} }

BIN
web/static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

5
web/static/pico.min.css vendored Normal file

File diff suppressed because one or more lines are too long