layouting webmention
This commit is contained in:
parent
5939bbd09d
commit
b1c46a86aa
|
@ -1,58 +1,11 @@
|
||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"owl-blogs/domain/model"
|
"owl-blogs/domain/model"
|
||||||
"reflect"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type EntryTypeRegistry struct {
|
type EntryTypeRegistry = TypeRegistry[model.Entry]
|
||||||
types map[string]model.Entry
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewEntryTypeRegistry() *EntryTypeRegistry {
|
func NewEntryTypeRegistry() *EntryTypeRegistry {
|
||||||
return &EntryTypeRegistry{types: map[string]model.Entry{}}
|
return NewTypeRegistry[model.Entry]()
|
||||||
}
|
|
||||||
|
|
||||||
func (r *EntryTypeRegistry) entryType(entry model.Entry) string {
|
|
||||||
return reflect.TypeOf(entry).Elem().Name()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *EntryTypeRegistry) Register(entry model.Entry) error {
|
|
||||||
t := r.entryType(entry)
|
|
||||||
if _, ok := r.types[t]; ok {
|
|
||||||
return errors.New("entry type already registered")
|
|
||||||
}
|
|
||||||
r.types[t] = entry
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *EntryTypeRegistry) Types() []model.Entry {
|
|
||||||
types := []model.Entry{}
|
|
||||||
for _, t := range r.types {
|
|
||||||
types = append(types, t)
|
|
||||||
}
|
|
||||||
return types
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *EntryTypeRegistry) TypeName(entry model.Entry) (string, error) {
|
|
||||||
t := r.entryType(entry)
|
|
||||||
if _, ok := r.types[t]; !ok {
|
|
||||||
return "", errors.New("entry type not registered")
|
|
||||||
}
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *EntryTypeRegistry) Type(name string) (model.Entry, error) {
|
|
||||||
if _, ok := r.types[name]; !ok {
|
|
||||||
return nil, errors.New("entry type not registered")
|
|
||||||
}
|
|
||||||
|
|
||||||
val := reflect.ValueOf(r.types[name])
|
|
||||||
if val.Kind() == reflect.Ptr {
|
|
||||||
val = reflect.Indirect(val)
|
|
||||||
}
|
|
||||||
newEntry := reflect.New(val.Type()).Interface().(model.Entry)
|
|
||||||
|
|
||||||
return newEntry, nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TypeRegistry[T any] struct {
|
||||||
|
types map[string]T
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTypeRegistry[T any]() *TypeRegistry[T] {
|
||||||
|
return &TypeRegistry[T]{types: map[string]T{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TypeRegistry[T]) entryType(entry T) string {
|
||||||
|
return reflect.TypeOf(entry).Elem().Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TypeRegistry[T]) Register(entry T) error {
|
||||||
|
t := r.entryType(entry)
|
||||||
|
if _, ok := r.types[t]; ok {
|
||||||
|
return errors.New("entry type already registered")
|
||||||
|
}
|
||||||
|
r.types[t] = entry
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TypeRegistry[T]) Types() []T {
|
||||||
|
types := []T{}
|
||||||
|
for _, t := range r.types {
|
||||||
|
types = append(types, t)
|
||||||
|
}
|
||||||
|
return types
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TypeRegistry[T]) TypeName(entry T) (string, error) {
|
||||||
|
t := r.entryType(entry)
|
||||||
|
if _, ok := r.types[t]; !ok {
|
||||||
|
return "", errors.New("entry type not registered")
|
||||||
|
}
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TypeRegistry[T]) Type(name string) (T, error) {
|
||||||
|
if _, ok := r.types[name]; !ok {
|
||||||
|
return *new(T), errors.New("entry type not registered")
|
||||||
|
}
|
||||||
|
|
||||||
|
val := reflect.ValueOf(r.types[name])
|
||||||
|
if val.Kind() == reflect.Ptr {
|
||||||
|
val = reflect.Indirect(val)
|
||||||
|
}
|
||||||
|
newEntry := reflect.New(val.Type()).Interface().(T)
|
||||||
|
|
||||||
|
return newEntry, nil
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"owl-blogs/domain/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type InteractionTypeRegistry = TypeRegistry[model.Interaction]
|
||||||
|
|
||||||
|
func NewInteractionTypeRegistry() *InteractionTypeRegistry {
|
||||||
|
return NewTypeRegistry[model.Interaction]()
|
||||||
|
}
|
|
@ -36,3 +36,11 @@ type ConfigRepository interface {
|
||||||
Get(name string, config interface{}) error
|
Get(name string, config interface{}) error
|
||||||
Update(name string, siteConfig interface{}) error
|
Update(name string, siteConfig interface{}) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type InteractionRepository interface {
|
||||||
|
Create(interaction model.Interaction) error
|
||||||
|
Update(interaction model.Interaction) error
|
||||||
|
Delete(interaction model.Interaction) error
|
||||||
|
FindById(id string) (model.Interaction, error)
|
||||||
|
FindAll(entryId string) ([]model.Interaction, error)
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import "owl-blogs/app/repository"
|
||||||
|
|
||||||
|
type WebmentionService struct {
|
||||||
|
InteractionRepository repository.InteractionRepository
|
||||||
|
EntryRepository repository.EntryRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWebmentionService(
|
||||||
|
interactionRepository repository.InteractionRepository,
|
||||||
|
entryRepository repository.EntryRepository,
|
||||||
|
) *WebmentionService {
|
||||||
|
return &WebmentionService{
|
||||||
|
InteractionRepository: interactionRepository,
|
||||||
|
EntryRepository: entryRepository,
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"owl-blogs/app"
|
"owl-blogs/app"
|
||||||
entrytypes "owl-blogs/entry_types"
|
entrytypes "owl-blogs/entry_types"
|
||||||
"owl-blogs/infra"
|
"owl-blogs/infra"
|
||||||
|
"owl-blogs/interactions"
|
||||||
"owl-blogs/web"
|
"owl-blogs/web"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
@ -26,29 +27,41 @@ func Execute() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func App(db infra.Database) *web.WebApp {
|
func App(db infra.Database) *web.WebApp {
|
||||||
registry := app.NewEntryTypeRegistry()
|
// Register Types
|
||||||
registry.Register(&entrytypes.Image{})
|
entryRegister := app.NewEntryTypeRegistry()
|
||||||
registry.Register(&entrytypes.Article{})
|
entryRegister.Register(&entrytypes.Image{})
|
||||||
registry.Register(&entrytypes.Page{})
|
entryRegister.Register(&entrytypes.Article{})
|
||||||
registry.Register(&entrytypes.Recipe{})
|
entryRegister.Register(&entrytypes.Page{})
|
||||||
registry.Register(&entrytypes.Note{})
|
entryRegister.Register(&entrytypes.Recipe{})
|
||||||
registry.Register(&entrytypes.Bookmark{})
|
entryRegister.Register(&entrytypes.Note{})
|
||||||
registry.Register(&entrytypes.Reply{})
|
entryRegister.Register(&entrytypes.Bookmark{})
|
||||||
|
entryRegister.Register(&entrytypes.Reply{})
|
||||||
|
|
||||||
entryRepo := infra.NewEntryRepository(db, registry)
|
interactionRegister := app.NewInteractionTypeRegistry()
|
||||||
binRepo := infra.NewBinaryFileRepo(db)
|
interactionRegister.Register(&interactions.Webmention{})
|
||||||
authorRepo := infra.NewDefaultAuthorRepo(db)
|
|
||||||
siteConfigRepo := infra.NewConfigRepo(db)
|
|
||||||
|
|
||||||
entryService := app.NewEntryService(entryRepo)
|
|
||||||
binaryService := app.NewBinaryFileService(binRepo)
|
|
||||||
authorService := app.NewAuthorService(authorRepo, siteConfigRepo)
|
|
||||||
|
|
||||||
configRegister := app.NewConfigRegister()
|
configRegister := app.NewConfigRegister()
|
||||||
|
|
||||||
|
// Create Repositories
|
||||||
|
entryRepo := infra.NewEntryRepository(db, entryRegister)
|
||||||
|
binRepo := infra.NewBinaryFileRepo(db)
|
||||||
|
authorRepo := infra.NewDefaultAuthorRepo(db)
|
||||||
|
siteConfigRepo := infra.NewConfigRepo(db)
|
||||||
|
interactionRepo := infra.NewInteractionRepo(db, interactionRegister)
|
||||||
|
|
||||||
|
// Create Services
|
||||||
|
entryService := app.NewEntryService(entryRepo)
|
||||||
|
binaryService := app.NewBinaryFileService(binRepo)
|
||||||
|
authorService := app.NewAuthorService(authorRepo, siteConfigRepo)
|
||||||
|
webmentionService := app.NewWebmentionService(
|
||||||
|
interactionRepo, entryRepo,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create WebApp
|
||||||
return web.NewWebApp(
|
return web.NewWebApp(
|
||||||
entryService, registry, binaryService,
|
entryService, entryRegister, binaryService,
|
||||||
authorService, siteConfigRepo, configRegister,
|
authorService, siteConfigRepo, configRegister,
|
||||||
|
webmentionService,
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type InteractionContent string
|
||||||
|
|
||||||
|
// Interaction is a generic interface for all interactions with entries
|
||||||
|
// These interactions can be:
|
||||||
|
// - Webmention, Pingback, Trackback
|
||||||
|
// - Likes, Comments on third party sites
|
||||||
|
// - Comments on the site itself
|
||||||
|
type Interaction interface {
|
||||||
|
ID() string
|
||||||
|
EntryID() string
|
||||||
|
Content() InteractionContent
|
||||||
|
CreatedAt() time.Time
|
||||||
|
MetaData() interface{}
|
||||||
|
|
||||||
|
SetID(id string)
|
||||||
|
SetEntryID(entryID string)
|
||||||
|
SetCreatedAt(createdAt time.Time)
|
||||||
|
SetMetaData(metaData interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type InteractionBase struct {
|
||||||
|
id string
|
||||||
|
entryID string
|
||||||
|
createdAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *InteractionBase) ID() string {
|
||||||
|
return i.id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *InteractionBase) EntryID() string {
|
||||||
|
return i.entryID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *InteractionBase) CreatedAt() time.Time {
|
||||||
|
return i.createdAt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *InteractionBase) SetID(id string) {
|
||||||
|
i.id = id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *InteractionBase) SetEntryID(entryID string) {
|
||||||
|
i.entryID = entryID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *InteractionBase) SetCreatedAt(createdAt time.Time) {
|
||||||
|
i.createdAt = createdAt
|
||||||
|
}
|
|
@ -28,6 +28,26 @@ type DefaultEntryRepo struct {
|
||||||
db *sqlx.DB
|
db *sqlx.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewEntryRepository(db Database, register *app.EntryTypeRegistry) repository.EntryRepository {
|
||||||
|
sqlxdb := db.Get()
|
||||||
|
|
||||||
|
// Create tables if not exists
|
||||||
|
sqlxdb.MustExec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS entries (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
type TEXT NOT NULL,
|
||||||
|
published_at DATETIME,
|
||||||
|
author_id TEXT NOT NULL,
|
||||||
|
meta_data TEXT NOT NULL
|
||||||
|
);
|
||||||
|
`)
|
||||||
|
|
||||||
|
return &DefaultEntryRepo{
|
||||||
|
db: sqlxdb,
|
||||||
|
typeRegistry: register,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create implements repository.EntryRepository.
|
// Create implements repository.EntryRepository.
|
||||||
func (r *DefaultEntryRepo) Create(entry model.Entry) error {
|
func (r *DefaultEntryRepo) Create(entry model.Entry) error {
|
||||||
t, err := r.typeRegistry.TypeName(entry)
|
t, err := r.typeRegistry.TypeName(entry)
|
||||||
|
@ -123,26 +143,6 @@ func (r *DefaultEntryRepo) Update(entry model.Entry) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewEntryRepository(db Database, register *app.EntryTypeRegistry) repository.EntryRepository {
|
|
||||||
sqlxdb := db.Get()
|
|
||||||
|
|
||||||
// Create tables if not exists
|
|
||||||
sqlxdb.MustExec(`
|
|
||||||
CREATE TABLE IF NOT EXISTS entries (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
type TEXT NOT NULL,
|
|
||||||
published_at DATETIME,
|
|
||||||
author_id TEXT NOT NULL,
|
|
||||||
meta_data TEXT NOT NULL
|
|
||||||
);
|
|
||||||
`)
|
|
||||||
|
|
||||||
return &DefaultEntryRepo{
|
|
||||||
db: sqlxdb,
|
|
||||||
typeRegistry: register,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultEntryRepo) sqlEntryToEntry(entry sqlEntry) (model.Entry, error) {
|
func (r *DefaultEntryRepo) sqlEntryToEntry(entry sqlEntry) (model.Entry, error) {
|
||||||
e, err := r.typeRegistry.Type(entry.Type)
|
e, err := r.typeRegistry.Type(entry.Type)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
package infra
|
||||||
|
|
||||||
|
import (
|
||||||
|
"owl-blogs/app"
|
||||||
|
"owl-blogs/app/repository"
|
||||||
|
"owl-blogs/domain/model"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type sqlInteraction struct {
|
||||||
|
Id string `db:"id"`
|
||||||
|
Type string `db:"type"`
|
||||||
|
EntryId string `db:"entry_id"`
|
||||||
|
CreatedAt string `db:"created_at"`
|
||||||
|
MetaData string `db:"meta_data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultInteractionRepo struct {
|
||||||
|
typeRegistry *app.InteractionTypeRegistry
|
||||||
|
db *sqlx.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInteractionRepo(db Database, register *app.InteractionTypeRegistry) repository.InteractionRepository {
|
||||||
|
sqlxdb := db.Get()
|
||||||
|
|
||||||
|
// Create tables if not exists
|
||||||
|
sqlxdb.MustExec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS interactions (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
type TEXT NOT NULL,
|
||||||
|
entry_id TEXT NOT NULL,
|
||||||
|
created_at DATETIME NOT NULL,
|
||||||
|
meta_data TEXT NOT NULL
|
||||||
|
);
|
||||||
|
`)
|
||||||
|
|
||||||
|
return &DefaultInteractionRepo{
|
||||||
|
db: sqlxdb,
|
||||||
|
typeRegistry: register,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create implements repository.InteractionRepository.
|
||||||
|
func (*DefaultInteractionRepo) Create(interaction model.Interaction) error {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete implements repository.InteractionRepository.
|
||||||
|
func (*DefaultInteractionRepo) Delete(interaction model.Interaction) error {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindAll implements repository.InteractionRepository.
|
||||||
|
func (*DefaultInteractionRepo) FindAll(entryId string) ([]model.Interaction, error) {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindById implements repository.InteractionRepository.
|
||||||
|
func (*DefaultInteractionRepo) FindById(id string) (model.Interaction, error) {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update implements repository.InteractionRepository.
|
||||||
|
func (*DefaultInteractionRepo) Update(interaction model.Interaction) error {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package interactions
|
||||||
|
|
||||||
|
import "owl-blogs/domain/model"
|
||||||
|
|
||||||
|
type Webmention struct {
|
||||||
|
model.InteractionBase
|
||||||
|
meta WebmentionInteractionMetaData
|
||||||
|
}
|
||||||
|
|
||||||
|
type WebmentionInteractionMetaData struct {
|
||||||
|
Source string
|
||||||
|
Target string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Webmention) Content() model.InteractionContent {
|
||||||
|
return model.InteractionContent(i.meta.Source)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Webmention) MetaData() interface{} {
|
||||||
|
return &i.meta
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Webmention) SetMetaData(metaData interface{}) {
|
||||||
|
i.meta = *metaData.(*WebmentionInteractionMetaData)
|
||||||
|
}
|
|
@ -34,6 +34,7 @@ func NewWebApp(
|
||||||
authorService *app.AuthorService,
|
authorService *app.AuthorService,
|
||||||
configRepo repository.ConfigRepository,
|
configRepo repository.ConfigRepository,
|
||||||
configRegister *app.ConfigRegister,
|
configRegister *app.ConfigRegister,
|
||||||
|
webmentionService *app.WebmentionService,
|
||||||
) *WebApp {
|
) *WebApp {
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
app.Use(middleware.NewUserMiddleware(authorService).Handle)
|
app.Use(middleware.NewUserMiddleware(authorService).Handle)
|
||||||
|
|
Loading…
Reference in New Issue