WIP site config + copy existing design
This commit is contained in:
parent
3a1559584c
commit
cecd94b296
|
@ -4,7 +4,6 @@ import (
|
|||
"crypto/sha256"
|
||||
"fmt"
|
||||
"owl-blogs/app/repository"
|
||||
"owl-blogs/config"
|
||||
"owl-blogs/domain/model"
|
||||
"strings"
|
||||
|
||||
|
@ -12,12 +11,12 @@ import (
|
|||
)
|
||||
|
||||
type AuthorService struct {
|
||||
repo repository.AuthorRepository
|
||||
config config.Config
|
||||
repo repository.AuthorRepository
|
||||
siteConfigRepo repository.SiteConfigRepository
|
||||
}
|
||||
|
||||
func NewAuthorService(repo repository.AuthorRepository, config config.Config) *AuthorService {
|
||||
return &AuthorService{repo: repo, config: config}
|
||||
func NewAuthorService(repo repository.AuthorRepository, siteConfigRepo repository.SiteConfigRepository) *AuthorService {
|
||||
return &AuthorService{repo: repo, siteConfigRepo: siteConfigRepo}
|
||||
}
|
||||
|
||||
func hashPassword(password string) (string, error) {
|
||||
|
@ -50,7 +49,18 @@ func (s *AuthorService) Authenticate(name string, password string) bool {
|
|||
}
|
||||
|
||||
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) {
|
||||
|
|
|
@ -2,6 +2,7 @@ package app_test
|
|||
|
||||
import (
|
||||
"owl-blogs/app"
|
||||
"owl-blogs/domain/model"
|
||||
"owl-blogs/infra"
|
||||
"owl-blogs/test"
|
||||
"strings"
|
||||
|
@ -10,17 +11,25 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type testConfig struct {
|
||||
type testConfigRepo struct {
|
||||
config model.SiteConfig
|
||||
}
|
||||
|
||||
func (c *testConfig) SECRET_KEY() string {
|
||||
return "test"
|
||||
// Get implements repository.SiteConfigRepository.
|
||||
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 {
|
||||
db := test.NewMockDb()
|
||||
authorRepo := infra.NewDefaultAuthorRepo(db)
|
||||
authorService := app.NewAuthorService(authorRepo, &testConfig{})
|
||||
authorService := app.NewAuthorService(authorRepo, &testConfigRepo{})
|
||||
return authorService
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -9,7 +9,7 @@ import (
|
|||
"net/http/httptest"
|
||||
"os"
|
||||
"owl-blogs/app"
|
||||
"owl-blogs/domain/model"
|
||||
entrytypes "owl-blogs/entry_types"
|
||||
"owl-blogs/infra"
|
||||
"owl-blogs/test"
|
||||
"path"
|
||||
|
@ -94,8 +94,8 @@ func TestEditorFormPost(t *testing.T) {
|
|||
id := strings.Split(resp.Header.Get("Location"), "/")[2]
|
||||
entry, err := repo.FindById(id)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "test content", entry.MetaData().(*model.ImageMetaData).Content)
|
||||
imageId := entry.MetaData().(*model.ImageMetaData).ImageId
|
||||
require.Equal(t, "test content", entry.MetaData().(*entrytypes.ImageMetaData).Content)
|
||||
imageId := entry.MetaData().(*entrytypes.ImageMetaData).ImageId
|
||||
require.NotZero(t, imageId)
|
||||
bin, err := binRepo.FindById(imageId)
|
||||
require.NoError(t, err)
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"owl-blogs/domain/model"
|
||||
entrytypes "owl-blogs/entry_types"
|
||||
"owl-blogs/importer"
|
||||
"owl-blogs/infra"
|
||||
"path"
|
||||
|
@ -49,7 +50,7 @@ var importCmd = &cobra.Command{
|
|||
files := importer.ListDir(mediaDir)
|
||||
for _, file := range files {
|
||||
// mock entry to pass to binary service
|
||||
entry := &model.Article{}
|
||||
entry := &entrytypes.Article{}
|
||||
entry.SetID(post.Id)
|
||||
|
||||
fileData, err := os.ReadFile(path.Join(mediaDir, file))
|
||||
|
@ -63,10 +64,10 @@ var importCmd = &cobra.Command{
|
|||
|
||||
switch post.Meta.Type {
|
||||
case "article":
|
||||
entry = &model.Article{}
|
||||
entry = &entrytypes.Article{}
|
||||
entry.SetID(post.Id)
|
||||
entry.SetPublishedAt(&post.Meta.Date)
|
||||
entry.SetMetaData(&model.ArticleMetaData{
|
||||
entry.SetMetaData(&entrytypes.ArticleMetaData{
|
||||
Title: post.Meta.Title,
|
||||
Content: post.Content,
|
||||
})
|
||||
|
@ -75,26 +76,26 @@ var importCmd = &cobra.Command{
|
|||
case "reply":
|
||||
|
||||
case "photo":
|
||||
entry = &model.Image{}
|
||||
entry = &entrytypes.Image{}
|
||||
entry.SetID(post.Id)
|
||||
entry.SetPublishedAt(&post.Meta.Date)
|
||||
entry.SetMetaData(&model.ImageMetaData{
|
||||
entry.SetMetaData(&entrytypes.ImageMetaData{
|
||||
Title: post.Meta.Title,
|
||||
Content: post.Content,
|
||||
ImageId: post.Meta.PhotoPath,
|
||||
})
|
||||
case "note":
|
||||
entry = &model.Note{}
|
||||
entry = &entrytypes.Note{}
|
||||
entry.SetID(post.Id)
|
||||
entry.SetPublishedAt(&post.Meta.Date)
|
||||
entry.SetMetaData(&model.NoteMetaData{
|
||||
entry.SetMetaData(&entrytypes.NoteMetaData{
|
||||
Content: post.Content,
|
||||
})
|
||||
case "recipe":
|
||||
entry = &model.Recipe{}
|
||||
entry = &entrytypes.Recipe{}
|
||||
entry.SetID(post.Id)
|
||||
entry.SetPublishedAt(&post.Meta.Date)
|
||||
entry.SetMetaData(&model.RecipeMetaData{
|
||||
entry.SetMetaData(&entrytypes.RecipeMetaData{
|
||||
Title: post.Meta.Title,
|
||||
Yield: post.Meta.Recipe.Yield,
|
||||
Duration: post.Meta.Recipe.Duration,
|
||||
|
@ -102,10 +103,10 @@ var importCmd = &cobra.Command{
|
|||
Content: post.Content,
|
||||
})
|
||||
case "page":
|
||||
entry = &model.Page{}
|
||||
entry = &entrytypes.Page{}
|
||||
entry.SetID(post.Id)
|
||||
entry.SetPublishedAt(&post.Meta.Date)
|
||||
entry.SetMetaData(&model.PageMetaData{
|
||||
entry.SetMetaData(&entrytypes.PageMetaData{
|
||||
Title: post.Meta.Title,
|
||||
Content: post.Content,
|
||||
})
|
||||
|
|
|
@ -4,8 +4,7 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"owl-blogs/app"
|
||||
"owl-blogs/config"
|
||||
"owl-blogs/domain/model"
|
||||
entrytypes "owl-blogs/entry_types"
|
||||
"owl-blogs/infra"
|
||||
"owl-blogs/web"
|
||||
|
||||
|
@ -27,22 +26,21 @@ func Execute() {
|
|||
}
|
||||
|
||||
func App(db infra.Database) *web.WebApp {
|
||||
config := config.NewConfig()
|
||||
|
||||
registry := app.NewEntryTypeRegistry()
|
||||
registry.Register(&model.Image{})
|
||||
registry.Register(&model.Article{})
|
||||
registry.Register(&model.Page{})
|
||||
registry.Register(&model.Recipe{})
|
||||
registry.Register(&model.Note{})
|
||||
registry.Register(&entrytypes.Image{})
|
||||
registry.Register(&entrytypes.Article{})
|
||||
registry.Register(&entrytypes.Page{})
|
||||
registry.Register(&entrytypes.Recipe{})
|
||||
registry.Register(&entrytypes.Note{})
|
||||
|
||||
entryRepo := infra.NewEntryRepository(db, registry)
|
||||
binRepo := infra.NewBinaryFileRepo(db)
|
||||
authorRepo := infra.NewDefaultAuthorRepo(db)
|
||||
siteConfigRepo := infra.NewSiteConfigRepo(db)
|
||||
|
||||
entryService := app.NewEntryService(entryRepo)
|
||||
binaryService := app.NewBinaryFileService(binRepo)
|
||||
authorService := app.NewAuthorService(authorRepo, config)
|
||||
authorService := app.NewAuthorService(authorRepo, siteConfigRepo)
|
||||
|
||||
return web.NewWebApp(entryService, registry, binaryService, authorService)
|
||||
|
||||
|
|
|
@ -3,11 +3,9 @@ package config
|
|||
import "os"
|
||||
|
||||
type Config interface {
|
||||
SECRET_KEY() string
|
||||
}
|
||||
|
||||
type EnvConfig struct {
|
||||
secretKey string
|
||||
}
|
||||
|
||||
func getEnvOrPanic(key string) string {
|
||||
|
@ -19,11 +17,5 @@ func getEnvOrPanic(key string) string {
|
|||
}
|
||||
|
||||
func NewConfig() Config {
|
||||
return &EnvConfig{
|
||||
secretKey: getEnvOrPanic("OWL_SECRET_KEY"),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *EnvConfig) SECRET_KEY() string {
|
||||
return c.secretKey
|
||||
return &EnvConfig{}
|
||||
}
|
||||
|
|
|
@ -30,4 +30,5 @@ type SiteConfig struct {
|
|||
HeaderMenu []MenuItem
|
||||
FooterMenu []MenuItem
|
||||
Secret string
|
||||
AvatarUrl string
|
||||
}
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
package model
|
||||
package entrytypes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"owl-blogs/domain/model"
|
||||
"owl-blogs/render"
|
||||
)
|
||||
|
||||
type Article struct {
|
||||
EntryBase
|
||||
model.EntryBase
|
||||
meta ArticleMetaData
|
||||
}
|
||||
|
||||
|
@ -19,12 +20,12 @@ func (e *Article) Title() string {
|
|||
return e.meta.Title
|
||||
}
|
||||
|
||||
func (e *Article) Content() EntryContent {
|
||||
func (e *Article) Content() model.EntryContent {
|
||||
str, err := render.RenderTemplateToString("entry/Article", e)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
return EntryContent(str)
|
||||
return model.EntryContent(str)
|
||||
}
|
||||
|
||||
func (e *Article) MetaData() interface{} {
|
|
@ -1,12 +1,13 @@
|
|||
package model
|
||||
package entrytypes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"owl-blogs/domain/model"
|
||||
"owl-blogs/render"
|
||||
)
|
||||
|
||||
type Image struct {
|
||||
EntryBase
|
||||
model.EntryBase
|
||||
meta ImageMetaData
|
||||
}
|
||||
|
||||
|
@ -20,12 +21,12 @@ func (e *Image) Title() string {
|
|||
return e.meta.Title
|
||||
}
|
||||
|
||||
func (e *Image) Content() EntryContent {
|
||||
func (e *Image) Content() model.EntryContent {
|
||||
str, err := render.RenderTemplateToString("entry/Image", e)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
return EntryContent(str)
|
||||
return model.EntryContent(str)
|
||||
}
|
||||
|
||||
func (e *Image) MetaData() interface{} {
|
|
@ -1,7 +1,9 @@
|
|||
package model
|
||||
package entrytypes
|
||||
|
||||
import "owl-blogs/domain/model"
|
||||
|
||||
type Note struct {
|
||||
EntryBase
|
||||
model.EntryBase
|
||||
meta NoteMetaData
|
||||
}
|
||||
|
||||
|
@ -13,8 +15,8 @@ func (e *Note) Title() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
func (e *Note) Content() EntryContent {
|
||||
return EntryContent(e.meta.Content)
|
||||
func (e *Note) Content() model.EntryContent {
|
||||
return model.EntryContent(e.meta.Content)
|
||||
}
|
||||
|
||||
func (e *Note) MetaData() interface{} {
|
|
@ -1,12 +1,13 @@
|
|||
package model
|
||||
package entrytypes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"owl-blogs/domain/model"
|
||||
"owl-blogs/render"
|
||||
)
|
||||
|
||||
type Page struct {
|
||||
EntryBase
|
||||
model.EntryBase
|
||||
meta PageMetaData
|
||||
}
|
||||
|
||||
|
@ -19,12 +20,12 @@ func (e *Page) Title() string {
|
|||
return e.meta.Title
|
||||
}
|
||||
|
||||
func (e *Page) Content() EntryContent {
|
||||
func (e *Page) Content() model.EntryContent {
|
||||
str, err := render.RenderTemplateToString("entry/Page", e)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
return EntryContent(str)
|
||||
return model.EntryContent(str)
|
||||
}
|
||||
|
||||
func (e *Page) MetaData() interface{} {
|
|
@ -1,12 +1,13 @@
|
|||
package model
|
||||
package entrytypes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"owl-blogs/domain/model"
|
||||
"owl-blogs/render"
|
||||
)
|
||||
|
||||
type Recipe struct {
|
||||
EntryBase
|
||||
model.EntryBase
|
||||
meta RecipeMetaData
|
||||
}
|
||||
|
||||
|
@ -22,12 +23,12 @@ func (e *Recipe) Title() string {
|
|||
return e.meta.Title
|
||||
}
|
||||
|
||||
func (e *Recipe) Content() EntryContent {
|
||||
func (e *Recipe) Content() model.EntryContent {
|
||||
str, err := render.RenderTemplateToString("entry/Recipe", e)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
return EntryContent(str)
|
||||
return model.EntryContent(str)
|
||||
}
|
||||
|
||||
func (e *Recipe) MetaData() interface{} {
|
|
@ -4,6 +4,7 @@ import (
|
|||
"bytes"
|
||||
"embed"
|
||||
"io"
|
||||
"owl-blogs/domain/model"
|
||||
"text/template"
|
||||
|
||||
"github.com/yuin/goldmark"
|
||||
|
@ -12,6 +13,11 @@ import (
|
|||
"github.com/yuin/goldmark/renderer/html"
|
||||
)
|
||||
|
||||
type TemplateData struct {
|
||||
Data interface{}
|
||||
SiteConfig model.SiteConfig
|
||||
}
|
||||
|
||||
//go:embed templates
|
||||
var templates embed.FS
|
||||
|
||||
|
@ -42,7 +48,10 @@ func RenderTemplateWithBase(w io.Writer, templateName string, data interface{})
|
|||
return err
|
||||
}
|
||||
|
||||
err = t.ExecuteTemplate(w, "base", data)
|
||||
err = t.ExecuteTemplate(w, "base", TemplateData{
|
||||
Data: data,
|
||||
SiteConfig: model.SiteConfig{},
|
||||
})
|
||||
|
||||
return err
|
||||
|
||||
|
|
|
@ -3,15 +3,94 @@
|
|||
<html lang='en'>
|
||||
<head>
|
||||
<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'>
|
||||
<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>
|
||||
<body>
|
||||
<header class="container">
|
||||
<a href="/">Owl Blog</a>
|
||||
<header>
|
||||
<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>
|
||||
<main class="container">
|
||||
{{template "main" .}}
|
||||
{{template "main" .Data}}
|
||||
</main>
|
||||
<footer class="container">
|
||||
Powered by <a href='https://golang.org/'>Go</a>
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
{{define "title"}}Editor{{end}}
|
||||
|
||||
{{define "main"}}
|
||||
|
||||
<a href="/editor">Back</a>
|
||||
<br>
|
||||
<br>
|
||||
|
||||
{{.}}
|
||||
|
||||
{{end}}
|
|
@ -3,6 +3,7 @@ package web
|
|||
import (
|
||||
"owl-blogs/app"
|
||||
"owl-blogs/domain/model"
|
||||
"owl-blogs/render"
|
||||
"owl-blogs/web/editor"
|
||||
"time"
|
||||
|
||||
|
@ -49,7 +50,7 @@ func (h *EditorHandler) HandleGet(c *fiber.Ctx) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.SendString(htmlForm)
|
||||
return render.RenderTemplateWithBase(c, "views/editor", htmlForm)
|
||||
}
|
||||
|
||||
func (h *EditorHandler) HandlePost(c *fiber.Ctx) error {
|
||||
|
|
Loading…
Reference in New Issue