layouting webmention

This commit is contained in:
Niko Abeler 2023-08-08 20:17:04 +02:00
parent 5939bbd09d
commit b1c46a86aa
11 changed files with 292 additions and 86 deletions

View File

@ -1,58 +1,11 @@
package app
import (
"errors"
"owl-blogs/domain/model"
"reflect"
)
type EntryTypeRegistry struct {
types map[string]model.Entry
}
type EntryTypeRegistry = TypeRegistry[model.Entry]
func NewEntryTypeRegistry() *EntryTypeRegistry {
return &EntryTypeRegistry{types: map[string]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
return NewTypeRegistry[model.Entry]()
}

57
app/generic_register.go Normal file
View File

@ -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
}

View File

@ -0,0 +1,11 @@
package app
import (
"owl-blogs/domain/model"
)
type InteractionTypeRegistry = TypeRegistry[model.Interaction]
func NewInteractionTypeRegistry() *InteractionTypeRegistry {
return NewTypeRegistry[model.Interaction]()
}

View File

@ -36,3 +36,11 @@ type ConfigRepository interface {
Get(name string, config 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)
}

18
app/webmention_service.go Normal file
View File

@ -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,
}
}

View File

@ -6,6 +6,7 @@ import (
"owl-blogs/app"
entrytypes "owl-blogs/entry_types"
"owl-blogs/infra"
"owl-blogs/interactions"
"owl-blogs/web"
"github.com/spf13/cobra"
@ -26,29 +27,41 @@ func Execute() {
}
func App(db infra.Database) *web.WebApp {
registry := app.NewEntryTypeRegistry()
registry.Register(&entrytypes.Image{})
registry.Register(&entrytypes.Article{})
registry.Register(&entrytypes.Page{})
registry.Register(&entrytypes.Recipe{})
registry.Register(&entrytypes.Note{})
registry.Register(&entrytypes.Bookmark{})
registry.Register(&entrytypes.Reply{})
// Register Types
entryRegister := app.NewEntryTypeRegistry()
entryRegister.Register(&entrytypes.Image{})
entryRegister.Register(&entrytypes.Article{})
entryRegister.Register(&entrytypes.Page{})
entryRegister.Register(&entrytypes.Recipe{})
entryRegister.Register(&entrytypes.Note{})
entryRegister.Register(&entrytypes.Bookmark{})
entryRegister.Register(&entrytypes.Reply{})
entryRepo := infra.NewEntryRepository(db, registry)
binRepo := infra.NewBinaryFileRepo(db)
authorRepo := infra.NewDefaultAuthorRepo(db)
siteConfigRepo := infra.NewConfigRepo(db)
entryService := app.NewEntryService(entryRepo)
binaryService := app.NewBinaryFileService(binRepo)
authorService := app.NewAuthorService(authorRepo, siteConfigRepo)
interactionRegister := app.NewInteractionTypeRegistry()
interactionRegister.Register(&interactions.Webmention{})
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(
entryService, registry, binaryService,
entryService, entryRegister, binaryService,
authorService, siteConfigRepo, configRegister,
webmentionService,
)
}

View File

@ -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
}

View File

@ -28,6 +28,26 @@ type DefaultEntryRepo struct {
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.
func (r *DefaultEntryRepo) Create(entry model.Entry) error {
t, err := r.typeRegistry.TypeName(entry)
@ -123,26 +143,6 @@ func (r *DefaultEntryRepo) Update(entry model.Entry) error {
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) {
e, err := r.typeRegistry.Type(entry.Type)
if err != nil {

View File

@ -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")
}

View File

@ -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)
}

View File

@ -34,6 +34,7 @@ func NewWebApp(
authorService *app.AuthorService,
configRepo repository.ConfigRepository,
configRegister *app.ConfigRegister,
webmentionService *app.WebmentionService,
) *WebApp {
app := fiber.New()
app.Use(middleware.NewUserMiddleware(authorService).Handle)