v2 #43

Merged
h4kor merged 41 commits from v2 into main 2023-07-19 19:09:19 +00:00
17 changed files with 201 additions and 69 deletions
Showing only changes of commit cecd94b296 - Show all commits

View File

@ -4,7 +4,6 @@ import (
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"owl-blogs/app/repository" "owl-blogs/app/repository"
"owl-blogs/config"
"owl-blogs/domain/model" "owl-blogs/domain/model"
"strings" "strings"
@ -13,11 +12,11 @@ import (
type AuthorService struct { type AuthorService struct {
repo repository.AuthorRepository repo repository.AuthorRepository
config config.Config siteConfigRepo repository.SiteConfigRepository
} }
func NewAuthorService(repo repository.AuthorRepository, config config.Config) *AuthorService { func NewAuthorService(repo repository.AuthorRepository, siteConfigRepo repository.SiteConfigRepository) *AuthorService {
return &AuthorService{repo: repo, config: config} return &AuthorService{repo: repo, siteConfigRepo: siteConfigRepo}
} }
func hashPassword(password string) (string, error) { func hashPassword(password string) (string, error) {
@ -50,7 +49,18 @@ func (s *AuthorService) Authenticate(name string, password string) bool {
} }
func (s *AuthorService) getSecretKey() string { func (s *AuthorService) getSecretKey() string {
return s.config.SECRET_KEY() config, err := s.siteConfigRepo.Get()
if err != nil {
panic(err)
}
if config.Secret == "" {
config.Secret = RandStringRunes(64)
err = s.siteConfigRepo.Update(config)
if err != nil {
panic(err)
}
}
return config.Secret
} }
func (s *AuthorService) CreateToken(name string) (string, error) { func (s *AuthorService) CreateToken(name string) (string, error) {

View File

@ -2,6 +2,7 @@ package app_test
import ( import (
"owl-blogs/app" "owl-blogs/app"
"owl-blogs/domain/model"
"owl-blogs/infra" "owl-blogs/infra"
"owl-blogs/test" "owl-blogs/test"
"strings" "strings"
@ -10,17 +11,25 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
type testConfig struct { type testConfigRepo struct {
config model.SiteConfig
} }
func (c *testConfig) SECRET_KEY() string { // Get implements repository.SiteConfigRepository.
return "test" func (c *testConfigRepo) Get() (model.SiteConfig, error) {
return c.config, nil
}
// Update implements repository.SiteConfigRepository.
func (c *testConfigRepo) Update(siteConfig model.SiteConfig) error {
c.config = siteConfig
return nil
} }
func getAutherService() *app.AuthorService { func getAutherService() *app.AuthorService {
db := test.NewMockDb() db := test.NewMockDb()
authorRepo := infra.NewDefaultAuthorRepo(db) authorRepo := infra.NewDefaultAuthorRepo(db)
authorService := app.NewAuthorService(authorRepo, &testConfig{}) authorService := app.NewAuthorService(authorRepo, &testConfigRepo{})
return authorService return authorService
} }

15
app/utils.go Normal file
View File

@ -0,0 +1,15 @@
package app
import (
"math/rand"
)
var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
func RandStringRunes(n int) string {
b := make([]rune, n)
for i := range b {
b[i] = letterRunes[rand.Intn(len(letterRunes))]
}
return string(b)
}

View File

@ -9,7 +9,7 @@ import (
"net/http/httptest" "net/http/httptest"
"os" "os"
"owl-blogs/app" "owl-blogs/app"
"owl-blogs/domain/model" entrytypes "owl-blogs/entry_types"
"owl-blogs/infra" "owl-blogs/infra"
"owl-blogs/test" "owl-blogs/test"
"path" "path"
@ -94,8 +94,8 @@ func TestEditorFormPost(t *testing.T) {
id := strings.Split(resp.Header.Get("Location"), "/")[2] id := strings.Split(resp.Header.Get("Location"), "/")[2]
entry, err := repo.FindById(id) entry, err := repo.FindById(id)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, "test content", entry.MetaData().(*model.ImageMetaData).Content) require.Equal(t, "test content", entry.MetaData().(*entrytypes.ImageMetaData).Content)
imageId := entry.MetaData().(*model.ImageMetaData).ImageId imageId := entry.MetaData().(*entrytypes.ImageMetaData).ImageId
require.NotZero(t, imageId) require.NotZero(t, imageId)
bin, err := binRepo.FindById(imageId) bin, err := binRepo.FindById(imageId)
require.NoError(t, err) require.NoError(t, err)

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"os" "os"
"owl-blogs/domain/model" "owl-blogs/domain/model"
entrytypes "owl-blogs/entry_types"
"owl-blogs/importer" "owl-blogs/importer"
"owl-blogs/infra" "owl-blogs/infra"
"path" "path"
@ -49,7 +50,7 @@ var importCmd = &cobra.Command{
files := importer.ListDir(mediaDir) files := importer.ListDir(mediaDir)
for _, file := range files { for _, file := range files {
// mock entry to pass to binary service // mock entry to pass to binary service
entry := &model.Article{} entry := &entrytypes.Article{}
entry.SetID(post.Id) entry.SetID(post.Id)
fileData, err := os.ReadFile(path.Join(mediaDir, file)) fileData, err := os.ReadFile(path.Join(mediaDir, file))
@ -63,10 +64,10 @@ var importCmd = &cobra.Command{
switch post.Meta.Type { switch post.Meta.Type {
case "article": case "article":
entry = &model.Article{} entry = &entrytypes.Article{}
entry.SetID(post.Id) entry.SetID(post.Id)
entry.SetPublishedAt(&post.Meta.Date) entry.SetPublishedAt(&post.Meta.Date)
entry.SetMetaData(&model.ArticleMetaData{ entry.SetMetaData(&entrytypes.ArticleMetaData{
Title: post.Meta.Title, Title: post.Meta.Title,
Content: post.Content, Content: post.Content,
}) })
@ -75,26 +76,26 @@ var importCmd = &cobra.Command{
case "reply": case "reply":
case "photo": case "photo":
entry = &model.Image{} entry = &entrytypes.Image{}
entry.SetID(post.Id) entry.SetID(post.Id)
entry.SetPublishedAt(&post.Meta.Date) entry.SetPublishedAt(&post.Meta.Date)
entry.SetMetaData(&model.ImageMetaData{ entry.SetMetaData(&entrytypes.ImageMetaData{
Title: post.Meta.Title, Title: post.Meta.Title,
Content: post.Content, Content: post.Content,
ImageId: post.Meta.PhotoPath, ImageId: post.Meta.PhotoPath,
}) })
case "note": case "note":
entry = &model.Note{} entry = &entrytypes.Note{}
entry.SetID(post.Id) entry.SetID(post.Id)
entry.SetPublishedAt(&post.Meta.Date) entry.SetPublishedAt(&post.Meta.Date)
entry.SetMetaData(&model.NoteMetaData{ entry.SetMetaData(&entrytypes.NoteMetaData{
Content: post.Content, Content: post.Content,
}) })
case "recipe": case "recipe":
entry = &model.Recipe{} entry = &entrytypes.Recipe{}
entry.SetID(post.Id) entry.SetID(post.Id)
entry.SetPublishedAt(&post.Meta.Date) entry.SetPublishedAt(&post.Meta.Date)
entry.SetMetaData(&model.RecipeMetaData{ entry.SetMetaData(&entrytypes.RecipeMetaData{
Title: post.Meta.Title, Title: post.Meta.Title,
Yield: post.Meta.Recipe.Yield, Yield: post.Meta.Recipe.Yield,
Duration: post.Meta.Recipe.Duration, Duration: post.Meta.Recipe.Duration,
@ -102,10 +103,10 @@ var importCmd = &cobra.Command{
Content: post.Content, Content: post.Content,
}) })
case "page": case "page":
entry = &model.Page{} entry = &entrytypes.Page{}
entry.SetID(post.Id) entry.SetID(post.Id)
entry.SetPublishedAt(&post.Meta.Date) entry.SetPublishedAt(&post.Meta.Date)
entry.SetMetaData(&model.PageMetaData{ entry.SetMetaData(&entrytypes.PageMetaData{
Title: post.Meta.Title, Title: post.Meta.Title,
Content: post.Content, Content: post.Content,
}) })

View File

@ -4,8 +4,7 @@ import (
"fmt" "fmt"
"os" "os"
"owl-blogs/app" "owl-blogs/app"
"owl-blogs/config" entrytypes "owl-blogs/entry_types"
"owl-blogs/domain/model"
"owl-blogs/infra" "owl-blogs/infra"
"owl-blogs/web" "owl-blogs/web"
@ -27,22 +26,21 @@ func Execute() {
} }
func App(db infra.Database) *web.WebApp { func App(db infra.Database) *web.WebApp {
config := config.NewConfig()
registry := app.NewEntryTypeRegistry() registry := app.NewEntryTypeRegistry()
registry.Register(&model.Image{}) registry.Register(&entrytypes.Image{})
registry.Register(&model.Article{}) registry.Register(&entrytypes.Article{})
registry.Register(&model.Page{}) registry.Register(&entrytypes.Page{})
registry.Register(&model.Recipe{}) registry.Register(&entrytypes.Recipe{})
registry.Register(&model.Note{}) registry.Register(&entrytypes.Note{})
entryRepo := infra.NewEntryRepository(db, registry) entryRepo := infra.NewEntryRepository(db, registry)
binRepo := infra.NewBinaryFileRepo(db) binRepo := infra.NewBinaryFileRepo(db)
authorRepo := infra.NewDefaultAuthorRepo(db) authorRepo := infra.NewDefaultAuthorRepo(db)
siteConfigRepo := infra.NewSiteConfigRepo(db)
entryService := app.NewEntryService(entryRepo) entryService := app.NewEntryService(entryRepo)
binaryService := app.NewBinaryFileService(binRepo) binaryService := app.NewBinaryFileService(binRepo)
authorService := app.NewAuthorService(authorRepo, config) authorService := app.NewAuthorService(authorRepo, siteConfigRepo)
return web.NewWebApp(entryService, registry, binaryService, authorService) return web.NewWebApp(entryService, registry, binaryService, authorService)

View File

@ -3,11 +3,9 @@ package config
import "os" import "os"
type Config interface { type Config interface {
SECRET_KEY() string
} }
type EnvConfig struct { type EnvConfig struct {
secretKey string
} }
func getEnvOrPanic(key string) string { func getEnvOrPanic(key string) string {
@ -19,11 +17,5 @@ func getEnvOrPanic(key string) string {
} }
func NewConfig() Config { func NewConfig() Config {
return &EnvConfig{ return &EnvConfig{}
secretKey: getEnvOrPanic("OWL_SECRET_KEY"),
}
}
func (c *EnvConfig) SECRET_KEY() string {
return c.secretKey
} }

View File

@ -30,4 +30,5 @@ type SiteConfig struct {
HeaderMenu []MenuItem HeaderMenu []MenuItem
FooterMenu []MenuItem FooterMenu []MenuItem
Secret string Secret string
AvatarUrl string
} }

View File

@ -1,12 +1,13 @@
package model package entrytypes
import ( import (
"fmt" "fmt"
"owl-blogs/domain/model"
"owl-blogs/render" "owl-blogs/render"
) )
type Article struct { type Article struct {
EntryBase model.EntryBase
meta ArticleMetaData meta ArticleMetaData
} }
@ -19,12 +20,12 @@ func (e *Article) Title() string {
return e.meta.Title return e.meta.Title
} }
func (e *Article) Content() EntryContent { func (e *Article) Content() model.EntryContent {
str, err := render.RenderTemplateToString("entry/Article", e) str, err := render.RenderTemplateToString("entry/Article", e)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
} }
return EntryContent(str) return model.EntryContent(str)
} }
func (e *Article) MetaData() interface{} { func (e *Article) MetaData() interface{} {

View File

@ -1,12 +1,13 @@
package model package entrytypes
import ( import (
"fmt" "fmt"
"owl-blogs/domain/model"
"owl-blogs/render" "owl-blogs/render"
) )
type Image struct { type Image struct {
EntryBase model.EntryBase
meta ImageMetaData meta ImageMetaData
} }
@ -20,12 +21,12 @@ func (e *Image) Title() string {
return e.meta.Title return e.meta.Title
} }
func (e *Image) Content() EntryContent { func (e *Image) Content() model.EntryContent {
str, err := render.RenderTemplateToString("entry/Image", e) str, err := render.RenderTemplateToString("entry/Image", e)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
} }
return EntryContent(str) return model.EntryContent(str)
} }
func (e *Image) MetaData() interface{} { func (e *Image) MetaData() interface{} {

View File

@ -1,7 +1,9 @@
package model package entrytypes
import "owl-blogs/domain/model"
type Note struct { type Note struct {
EntryBase model.EntryBase
meta NoteMetaData meta NoteMetaData
} }
@ -13,8 +15,8 @@ func (e *Note) Title() string {
return "" return ""
} }
func (e *Note) Content() EntryContent { func (e *Note) Content() model.EntryContent {
return EntryContent(e.meta.Content) return model.EntryContent(e.meta.Content)
} }
func (e *Note) MetaData() interface{} { func (e *Note) MetaData() interface{} {

View File

@ -1,12 +1,13 @@
package model package entrytypes
import ( import (
"fmt" "fmt"
"owl-blogs/domain/model"
"owl-blogs/render" "owl-blogs/render"
) )
type Page struct { type Page struct {
EntryBase model.EntryBase
meta PageMetaData meta PageMetaData
} }
@ -19,12 +20,12 @@ func (e *Page) Title() string {
return e.meta.Title return e.meta.Title
} }
func (e *Page) Content() EntryContent { func (e *Page) Content() model.EntryContent {
str, err := render.RenderTemplateToString("entry/Page", e) str, err := render.RenderTemplateToString("entry/Page", e)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
} }
return EntryContent(str) return model.EntryContent(str)
} }
func (e *Page) MetaData() interface{} { func (e *Page) MetaData() interface{} {

View File

@ -1,12 +1,13 @@
package model package entrytypes
import ( import (
"fmt" "fmt"
"owl-blogs/domain/model"
"owl-blogs/render" "owl-blogs/render"
) )
type Recipe struct { type Recipe struct {
EntryBase model.EntryBase
meta RecipeMetaData meta RecipeMetaData
} }
@ -22,12 +23,12 @@ func (e *Recipe) Title() string {
return e.meta.Title return e.meta.Title
} }
func (e *Recipe) Content() EntryContent { func (e *Recipe) Content() model.EntryContent {
str, err := render.RenderTemplateToString("entry/Recipe", e) str, err := render.RenderTemplateToString("entry/Recipe", e)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
} }
return EntryContent(str) return model.EntryContent(str)
} }
func (e *Recipe) MetaData() interface{} { func (e *Recipe) MetaData() interface{} {

View File

@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"embed" "embed"
"io" "io"
"owl-blogs/domain/model"
"text/template" "text/template"
"github.com/yuin/goldmark" "github.com/yuin/goldmark"
@ -12,6 +13,11 @@ import (
"github.com/yuin/goldmark/renderer/html" "github.com/yuin/goldmark/renderer/html"
) )
type TemplateData struct {
Data interface{}
SiteConfig model.SiteConfig
}
//go:embed templates //go:embed templates
var templates embed.FS var templates embed.FS
@ -42,7 +48,10 @@ func RenderTemplateWithBase(w io.Writer, templateName string, data interface{})
return err return err
} }
err = t.ExecuteTemplate(w, "base", data) err = t.ExecuteTemplate(w, "base", TemplateData{
Data: data,
SiteConfig: model.SiteConfig{},
})
return err return err

View File

@ -3,15 +3,94 @@
<html lang='en'> <html lang='en'>
<head> <head>
<meta charset='utf-8'> <meta charset='utf-8'>
<title>{{template "title" .}} - Owl Blog</title> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>{{template "title" .Data}} - {{ .SiteConfig.Title }}</title>
<meta property="og:title" content="{{template "title" .Data}}" />
<link rel='stylesheet' href='/static/pico.min.css'> <link rel='stylesheet' href='/static/pico.min.css'>
<style>
header {
background-color: {{.SiteConfig.HeaderColor}};
padding-bottom: 1rem !important;
}
footer {
border-top: dashed 2px;
border-color: #ccc;
}
.avatar {
float: left;
margin-right: 1rem;
}
.header {
display: flex;
flex-flow: row wrap;
justify-content: space-between;
align-items: flex-start;
}
.header-title {
order: 0;
}
.header-profile {
order: 1;
}
hgroup h2 a { color: inherit; }
.photo-grid {
display: flex;
flex-wrap: wrap;
padding: 0 4px;
}
.photo-grid-item {
flex: 1 0 25%;
padding: 4px;
}
.photo-grid-item img {
width: 100%;
height: 100%;
aspect-ratio: 1 / 1 ;
object-fit: cover;
}
</style>
</head> </head>
<body> <body>
<header class="container"> <header>
<a href="/">Owl Blog</a> <div class="container header h-card">
<hgroup class="header-title">
<h2><a class="p-name u-url" href="/">{{ .SiteConfig.Title }}</a></h2>
<h3 class="p-note">{{ .SiteConfig.SubTitle }}</h3>
</hgroup>
<div class="header-profile">
{{ if .SiteConfig.AvatarUrl }}
<img class="u-photo u-logo avatar" src="{{ .SiteConfig.AvatarUrl }}" alt="{{ .SiteConfig.Title }}" width="100" height="100" />
{{ end }}
<div style="float: right; list-style: none;">
{{ range $me := .SiteConfig.Me }}
<li><a href="{{$me.Url}}" rel="me">{{$me.Name}}</a>
</li>
{{ end }}
</div>
</div>
</div>
<div class="container">
<nav>
<ul>
</ul>
</nav>
</div>
</header> </header>
<main class="container"> <main class="container">
{{template "main" .}} {{template "main" .Data}}
</main> </main>
<footer class="container"> <footer class="container">
Powered by <a href='https://golang.org/'>Go</a> Powered by <a href='https://golang.org/'>Go</a>

View File

@ -0,0 +1,11 @@
{{define "title"}}Editor{{end}}
{{define "main"}}
<a href="/editor">Back</a>
<br>
<br>
{{.}}
{{end}}

View File

@ -3,6 +3,7 @@ package web
import ( import (
"owl-blogs/app" "owl-blogs/app"
"owl-blogs/domain/model" "owl-blogs/domain/model"
"owl-blogs/render"
"owl-blogs/web/editor" "owl-blogs/web/editor"
"time" "time"
@ -49,7 +50,7 @@ func (h *EditorHandler) HandleGet(c *fiber.Ctx) error {
if err != nil { if err != nil {
return err return err
} }
return c.SendString(htmlForm) return render.RenderTemplateWithBase(c, "views/editor", htmlForm)
} }
func (h *EditorHandler) HandlePost(c *fiber.Ctx) error { func (h *EditorHandler) HandlePost(c *fiber.Ctx) error {