🧑💻 Created entry handler with basic ActivityPub support
This commit is contained in:
parent
fd8140a86c
commit
0c38b587b4
8
go.mod
8
go.mod
|
@ -14,8 +14,16 @@ require (
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect
|
||||||
|
github.com/go-ap/errors v0.0.0-20231003111023-183eef4b31b7 // indirect
|
||||||
|
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73 // indirect
|
||||||
|
github.com/valyala/fastjson v1.6.4 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/BurntSushi/toml v1.3.2 // indirect
|
github.com/BurntSushi/toml v1.3.2 // indirect
|
||||||
|
github.com/go-ap/activitypub v0.0.0-20240211124657-820024a66b78
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
10
go.sum
10
go.sum
|
@ -1,3 +1,5 @@
|
||||||
|
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:cliQ4HHsCo6xi2oWZYKWW4bly/Ory9FuTpFPRxj/mAg=
|
||||||
|
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
||||||
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
|
@ -7,6 +9,12 @@ github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu
|
||||||
github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
github.com/caarlos0/env/v10 v10.0.0 h1:yIHUBZGsyqCnpTkbjk8asUlx6RFhhEs+h7TOBdgdzXA=
|
github.com/caarlos0/env/v10 v10.0.0 h1:yIHUBZGsyqCnpTkbjk8asUlx6RFhhEs+h7TOBdgdzXA=
|
||||||
github.com/caarlos0/env/v10 v10.0.0/go.mod h1:ZfulV76NvVPw3tm591U4SwL3Xx9ldzBP9aGxzeN7G18=
|
github.com/caarlos0/env/v10 v10.0.0/go.mod h1:ZfulV76NvVPw3tm591U4SwL3Xx9ldzBP9aGxzeN7G18=
|
||||||
|
github.com/go-ap/activitypub v0.0.0-20240211124657-820024a66b78 h1:M3QPsOk2J/AyyIODQQf2Jm9vsp6Jor0NQWyIBzI3oSM=
|
||||||
|
github.com/go-ap/activitypub v0.0.0-20240211124657-820024a66b78/go.mod h1:cJ9Ye0ZNSMN7RzZDBRY3E+8M3Bpf/R1JX22Ir9yX6WI=
|
||||||
|
github.com/go-ap/errors v0.0.0-20231003111023-183eef4b31b7 h1:I2nuhyVI/48VXoRCCZR2hYBgnSXa+EuDJf/VyX06TC0=
|
||||||
|
github.com/go-ap/errors v0.0.0-20231003111023-183eef4b31b7/go.mod h1:5x8a6P/dhmMGFxWLcyYlyOuJ2lRNaHGhRv+yu8BaTSI=
|
||||||
|
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73 h1:GMKIYXyXPGIp+hYiWOhfqK4A023HdgisDT4YGgf99mw=
|
||||||
|
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73/go.mod h1:jyveZeGw5LaADntW+UEsMjl3IlIwk+DxlYNsbofQkGA=
|
||||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
@ -15,6 +23,8 @@ github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47e
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
github.com/valyala/fasthttp v1.30.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus=
|
github.com/valyala/fasthttp v1.30.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus=
|
||||||
|
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
|
||||||
|
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
||||||
github.com/valyala/quicktemplate v1.7.0 h1:LUPTJmlVcb46OOUY3IeD9DojFpAVbsG+5WFTcjMJzCM=
|
github.com/valyala/quicktemplate v1.7.0 h1:LUPTJmlVcb46OOUY3IeD9DojFpAVbsG+5WFTcjMJzCM=
|
||||||
github.com/valyala/quicktemplate v1.7.0/go.mod h1:sqKJnoaOF88V07vkO+9FL8fb9uZg/VPSJnLYn+LmLk8=
|
github.com/valyala/quicktemplate v1.7.0/go.mod h1:sqKJnoaOF88V07vkO+9FL8fb9uZg/VPSJnLYn+LmLk8=
|
||||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
package domain
|
package domain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
type Entry struct {
|
type Entry struct {
|
||||||
Language Language
|
Language Language
|
||||||
Params map[string]any
|
Params map[string]any
|
||||||
|
@ -18,3 +23,18 @@ func (e Entry) IsHome() bool {
|
||||||
func (e Entry) IsTranslated() bool {
|
func (e Entry) IsTranslated() bool {
|
||||||
return 1 < len(e.Translations)
|
return 1 < len(e.Translations)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEntry(tb testing.TB) *Entry {
|
||||||
|
tb.Helper()
|
||||||
|
|
||||||
|
return &Entry{
|
||||||
|
Language: NewLanguage("en"),
|
||||||
|
Params: make(map[string]any),
|
||||||
|
File: NewPath(path.Join("sample-page.en.md")),
|
||||||
|
Description: "do not use in production",
|
||||||
|
Title: "Sample Page",
|
||||||
|
Content: []byte("Hello, world!"),
|
||||||
|
Resources: make([]*Resource, 0),
|
||||||
|
Translations: make([]*Entry, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package domain
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
@ -19,16 +20,16 @@ type Path struct {
|
||||||
uniqueId string
|
uniqueId string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPath(path string) Path {
|
func NewPath(p string) Path {
|
||||||
out := Path{
|
out := Path{
|
||||||
Language: LanguageUnd,
|
Language: LanguageUnd,
|
||||||
baseFileName: "",
|
baseFileName: "",
|
||||||
contentBaseName: "",
|
contentBaseName: "",
|
||||||
dir: filepath.Dir(path),
|
dir: filepath.Dir(p),
|
||||||
ext: strings.TrimPrefix(filepath.Ext(path), "."),
|
ext: strings.TrimPrefix(filepath.Ext(p), "."),
|
||||||
filename: path,
|
filename: p,
|
||||||
logicalName: filepath.Base(path),
|
logicalName: filepath.Base(p),
|
||||||
path: path,
|
path: "",
|
||||||
translationBaseName: "",
|
translationBaseName: "",
|
||||||
uniqueId: "",
|
uniqueId: "",
|
||||||
}
|
}
|
||||||
|
@ -54,6 +55,8 @@ func NewPath(path string) Path {
|
||||||
out.contentBaseName = filepath.Base(out.dir)
|
out.contentBaseName = filepath.Base(out.dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
out.path = path.Join(out.Language.code, out.dir, out.contentBaseName)
|
||||||
|
|
||||||
hash := md5.New()
|
hash := md5.New()
|
||||||
_, _ = hash.Write([]byte(out.path))
|
_, _ = hash.Write([]byte(out.path))
|
||||||
out.uniqueId = string(hash.Sum(nil))
|
out.uniqueId = string(hash.Sum(nil))
|
||||||
|
@ -99,6 +102,7 @@ func (p Path) Ext() string {
|
||||||
return p.ext
|
return p.ext
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filename returns the file path relative to $HOME_CONTENT_DIR.
|
||||||
func (p Path) Filename() string {
|
func (p Path) Filename() string {
|
||||||
return p.filename
|
return p.filename
|
||||||
}
|
}
|
||||||
|
@ -113,6 +117,7 @@ func (p Path) LogicalName() string {
|
||||||
return p.logicalName
|
return p.logicalName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Path returns the relative URL path to the file.
|
||||||
func (p Path) Path() string {
|
func (p Path) Path() string {
|
||||||
return p.path
|
return p.path
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,153 @@
|
||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"mime"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-ap/activitypub"
|
||||||
|
|
||||||
|
"source.toby3d.me/toby3d/home/internal/common"
|
||||||
|
"source.toby3d.me/toby3d/home/internal/domain"
|
||||||
|
"source.toby3d.me/toby3d/home/internal/entry"
|
||||||
|
"source.toby3d.me/toby3d/home/internal/site"
|
||||||
|
"source.toby3d.me/toby3d/home/internal/theme"
|
||||||
|
"source.toby3d.me/toby3d/home/internal/urlutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Handler struct {
|
||||||
|
entries entry.UseCase
|
||||||
|
sites site.UseCase
|
||||||
|
themes theme.UseCase
|
||||||
|
}
|
||||||
|
|
||||||
|
Profile struct {
|
||||||
|
*activitypub.Person
|
||||||
|
|
||||||
|
// Mastodon related
|
||||||
|
|
||||||
|
// Pinned posts. See [Featured collection].
|
||||||
|
//
|
||||||
|
// [Featured collection]: https://docs.joinmastodon.org/spec/activitypub/#featured
|
||||||
|
Featured []interface{} `json:"featured"`
|
||||||
|
|
||||||
|
// Required for Move activity.
|
||||||
|
AlsoKnownAs []string `json:"alsoKnownAs"`
|
||||||
|
|
||||||
|
// Will be shown as a locked account.
|
||||||
|
ManuallyApprovesFollowers bool `json:"manuallyApprovesFollowers"`
|
||||||
|
|
||||||
|
// Will be shown in the profile directory.
|
||||||
|
// See [Discoverability flag].
|
||||||
|
//
|
||||||
|
// [Discoverability flag]: https://docs.joinmastodon.org/spec/activitypub/#discoverable
|
||||||
|
Discoverable bool `json:"discoverable"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewHandler(sites site.UseCase, entries entry.UseCase, themes theme.UseCase) *Handler {
|
||||||
|
return &Handler{
|
||||||
|
sites: sites,
|
||||||
|
entries: entries,
|
||||||
|
themes: themes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
head, tail := urlutil.ShiftPath(r.URL.Path)
|
||||||
|
|
||||||
|
lang := domain.NewLanguage(head)
|
||||||
|
|
||||||
|
s, err := h.sites.Do(r.Context(), lang)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
contentType, params, _ := mime.ParseMediaType(r.Header.Get(common.HeaderAccept))
|
||||||
|
|
||||||
|
switch contentType {
|
||||||
|
case common.MIMEApplicationLdJSON: // NOTE(toby3d): show entry as ActivityPub object.
|
||||||
|
if profile, ok := params["profile"]; ok && profile != "https://www.w3.org/ns/activitystreams" {
|
||||||
|
http.Error(w, "got '"+profile+"' profile value, want 'https://www.w3.org/ns/activitystreams'",
|
||||||
|
http.StatusBadRequest)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set(common.HeaderContentType, common.MIMEApplicationActivityJSONCharsetUTF8)
|
||||||
|
encoder := json.NewEncoder(w)
|
||||||
|
|
||||||
|
if head == "" { // NOTE(toby3d): base URL point to owner Profile.
|
||||||
|
langRef := activitypub.LangRef(lang.Lang())
|
||||||
|
person := activitypub.PersonNew(activitypub.IRI(s.BaseURL.String()))
|
||||||
|
person.URL = person.ID
|
||||||
|
person.Name.Add(activitypub.LangRefValueNew(langRef, "Maxim Lebedev"))
|
||||||
|
person.Summary.Add(activitypub.LangRefValueNew(langRef, "Creative dude from russia"))
|
||||||
|
person.PreferredUsername.Add(activitypub.LangRefValueNew(langRef, "toby3d"))
|
||||||
|
person.Published = time.Date(2009, time.February, 0, 0, 0, 0, 0, time.UTC)
|
||||||
|
|
||||||
|
_ = encoder.Encode(person)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
e, err := h.entries.Do(r.Context(), lang, tail)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, entry.ErrNotExist) {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
} else {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := activitypub.ObjectNew(activitypub.NoteType)
|
||||||
|
resp.ID = activitypub.ID(s.BaseURL.JoinPath(e.File.Path()).String())
|
||||||
|
resp.Content.Add(activitypub.LangRefValueNew(activitypub.NilLangRef, string(e.Content)))
|
||||||
|
|
||||||
|
_ = encoder.Encode(resp)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(toby3d): search entry for requested URL and language
|
||||||
|
// code in subdir.
|
||||||
|
e, err := h.entries.Do(r.Context(), lang, tail)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, entry.ErrNotExist) {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
} else {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(toby3d): wrap founded entry into theme template and
|
||||||
|
// answer to client.
|
||||||
|
contentLanguage := make([]string, len(e.Translations))
|
||||||
|
for i := range e.Translations {
|
||||||
|
contentLanguage[i] = e.Translations[i].Language.Code()
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set(common.HeaderContentLanguage, strings.Join(contentLanguage, ", "))
|
||||||
|
|
||||||
|
template, err := h.themes.Do(r.Context(), s, e)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set(common.HeaderContentType, common.MIMETextHTMLCharsetUTF8)
|
||||||
|
if err = template(w); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package http_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"source.toby3d.me/toby3d/home/internal/common"
|
||||||
|
"source.toby3d.me/toby3d/home/internal/domain"
|
||||||
|
delivery "source.toby3d.me/toby3d/home/internal/entry/delivery/http"
|
||||||
|
entryucase "source.toby3d.me/toby3d/home/internal/entry/usecase"
|
||||||
|
siteucase "source.toby3d.me/toby3d/home/internal/site/usecase"
|
||||||
|
"source.toby3d.me/toby3d/home/internal/testutil"
|
||||||
|
themeucase "source.toby3d.me/toby3d/home/internal/theme/usecase"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHandler_ServeHTTP(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testEntry := domain.TestEntry(t)
|
||||||
|
|
||||||
|
for name, path := range map[string]string{
|
||||||
|
"person": "/",
|
||||||
|
"note": testEntry.File.Path(),
|
||||||
|
} {
|
||||||
|
name, path := name, path
|
||||||
|
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "https://example.com/"+path, nil)
|
||||||
|
req.Header.Set(common.HeaderAccept, common.MIMEApplicationLdJSONProfile)
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
delivery.NewHandler(
|
||||||
|
siteucase.NewStubSiteUseCase(domain.TestSite(t), nil),
|
||||||
|
entryucase.NewStubEntryUseCase(domain.TestEntry(t), nil),
|
||||||
|
themeucase.NewDummyThemeUseCase(),
|
||||||
|
).ServeHTTP(w, req)
|
||||||
|
testutil.GoldenEqual(t, w.Result().Body)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
{"id":"https://example.com/en/sample-page","type":"Note","content":"Hello, world!"}
|
1
internal/entry/delivery/http/testdata/TestHandler_ServeHTTP/person.golden
vendored
Executable file
1
internal/entry/delivery/http/testdata/TestHandler_ServeHTTP/person.golden
vendored
Executable file
|
@ -0,0 +1 @@
|
||||||
|
{"id":"https://example.com/","type":"Person","name":"Maxim Lebedev","summary":"Creative dude from russia","url":"https://example.com/","published":"2009-01-31T00:00:00Z","preferredUsername":"toby3d"}
|
|
@ -12,10 +12,17 @@ import (
|
||||||
"source.toby3d.me/toby3d/home/internal/urlutil"
|
"source.toby3d.me/toby3d/home/internal/urlutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
type entryUseCase struct {
|
type (
|
||||||
entries entry.Repository
|
entryUseCase struct {
|
||||||
resources resource.Repository
|
entries entry.Repository
|
||||||
}
|
resources resource.Repository
|
||||||
|
}
|
||||||
|
|
||||||
|
stubEntryUseCase struct {
|
||||||
|
entry *domain.Entry
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
func NewEntryUseCase(entries entry.Repository, resources resource.Repository) entry.UseCase {
|
func NewEntryUseCase(entries entry.Repository, resources resource.Repository) entry.UseCase {
|
||||||
return &entryUseCase{
|
return &entryUseCase{
|
||||||
|
@ -78,3 +85,14 @@ func (ucase *entryUseCase) Do(ctx context.Context, lang domain.Language, p strin
|
||||||
|
|
||||||
return nil, fmt.Errorf("cannot find page on path '%s': %w", p, entry.ErrNotExist)
|
return nil, fmt.Errorf("cannot find page on path '%s': %w", p, entry.ErrNotExist)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewStubEntryUseCase(entry *domain.Entry, err error) entry.UseCase {
|
||||||
|
return &stubEntryUseCase{
|
||||||
|
entry: entry,
|
||||||
|
err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ucase *stubEntryUseCase) Do(_ context.Context, _ domain.Language, _ string) (*domain.Entry, error) {
|
||||||
|
return ucase.entry, ucase.err
|
||||||
|
}
|
||||||
|
|
|
@ -11,10 +11,14 @@ import (
|
||||||
"source.toby3d.me/toby3d/home/internal/theme"
|
"source.toby3d.me/toby3d/home/internal/theme"
|
||||||
)
|
)
|
||||||
|
|
||||||
type themeUseCase struct {
|
type (
|
||||||
partials fs.FS
|
themeUseCase struct {
|
||||||
themes theme.Repository
|
partials fs.FS
|
||||||
}
|
themes theme.Repository
|
||||||
|
}
|
||||||
|
|
||||||
|
dummyThemeUseCase struct{}
|
||||||
|
)
|
||||||
|
|
||||||
func NewThemeUseCase(partials fs.FS, themes theme.Repository) theme.UseCase {
|
func NewThemeUseCase(partials fs.FS, themes theme.Repository) theme.UseCase {
|
||||||
return &themeUseCase{
|
return &themeUseCase{
|
||||||
|
@ -36,3 +40,11 @@ func (ucase *themeUseCase) Do(ctx context.Context, s *domain.Site, e *domain.Ent
|
||||||
})
|
})
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewDummyThemeUseCase() theme.UseCase {
|
||||||
|
return dummyThemeUseCase{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dummyThemeUseCase) Do(_ context.Context, _ *domain.Site, _ *domain.Entry) (theme.Writer, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue