diff --git a/app/webmention_service.go b/app/webmention_service.go
index 41986b5..1732d4f 100644
--- a/app/webmention_service.go
+++ b/app/webmention_service.go
@@ -109,7 +109,7 @@ func (s *WebmentionService) GetExistingWebmention(entryId string, source string,
}
for _, interaction := range inters {
if webm, ok := interaction.(*interactions.Webmention); ok {
- m := webm.MetaData().(interactions.WebmentionInteractionMetaData)
+ m := webm.MetaData().(interactions.WebmentionMetaData)
if m.Source == source && m.Target == target {
return webm, nil
}
@@ -130,6 +130,10 @@ func (s *WebmentionService) ProcessWebmention(source string, target string) erro
}
entryId := UrlToEntryId(target)
+ println(entryId)
+ println(entryId)
+ println(entryId)
+ println(entryId)
_, err = s.EntryRepository.FindById(entryId)
if err != nil {
return err
@@ -140,7 +144,7 @@ func (s *WebmentionService) ProcessWebmention(source string, target string) erro
return err
}
if webmention != nil {
- data := interactions.WebmentionInteractionMetaData{
+ data := interactions.WebmentionMetaData{
Source: source,
Target: target,
Title: hEntry.Title,
@@ -152,12 +156,12 @@ func (s *WebmentionService) ProcessWebmention(source string, target string) erro
return err
} else {
webmention = &interactions.Webmention{}
- data := interactions.WebmentionInteractionMetaData{
+ data := interactions.WebmentionMetaData{
Source: source,
Target: target,
Title: hEntry.Title,
}
- webmention.SetMetaData(data)
+ webmention.SetMetaData(&data)
webmention.SetEntryID(entryId)
webmention.SetCreatedAt(time.Now())
err = s.InteractionRepository.Create(webmention)
diff --git a/app/webmention_test.go b/app/webmention_test.go
new file mode 100644
index 0000000..29d85bb
--- /dev/null
+++ b/app/webmention_test.go
@@ -0,0 +1,222 @@
+package app_test
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "owl-blogs/app"
+ "owl-blogs/infra"
+ "owl-blogs/interactions"
+ "owl-blogs/test"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+// func constructResponse(html []byte) *http.Response {
+// url, _ := url.Parse("http://example.com/foo/bar")
+// return &http.Response{
+// Request: &http.Request{
+// URL: url,
+// },
+// Body: io.NopCloser(bytes.NewReader([]byte(html))),
+// }
+// }
+
+type MockHttpClient struct {
+ PageContent string
+}
+
+// Post implements owlhttp.HttpClient.
+func (MockHttpClient) Post(url string, contentType string, body io.Reader) (resp *http.Response, err error) {
+ panic("unimplemented")
+}
+
+// PostForm implements owlhttp.HttpClient.
+func (MockHttpClient) PostForm(url string, data url.Values) (resp *http.Response, err error) {
+ panic("unimplemented")
+}
+
+func (c *MockHttpClient) Get(url string) (*http.Response, error) {
+ return &http.Response{
+ Body: io.NopCloser(bytes.NewReader([]byte(c.PageContent))),
+ }, nil
+}
+
+func getWebmentionService() *app.WebmentionService {
+ db := test.NewMockDb()
+ entryRegister := app.NewEntryTypeRegistry()
+ entryRegister.Register(&test.MockEntry{})
+ entryRepo := infra.NewEntryRepository(db, entryRegister)
+
+ interactionRegister := app.NewInteractionTypeRegistry()
+ interactionRegister.Register(&interactions.Webmention{})
+
+ interactionRepo := infra.NewInteractionRepo(db, interactionRegister)
+
+ http := infra.OwlHttpClient{}
+ return app.NewWebmentionService(
+ interactionRepo, entryRepo, &http,
+ )
+}
+
+//
+// https://www.w3.org/TR/webmention/#h-webmention-verification
+//
+
+func TestParseValidHEntry(t *testing.T) {
+ service := getWebmentionService()
+ html := []byte("
")
+ entry, err := service.ParseHEntry(&http.Response{Body: io.NopCloser(bytes.NewReader(html))})
+
+ require.NoError(t, err)
+ require.Equal(t, entry.Title, "Foo")
+}
+
+func TestParseValidHEntryWithoutTitle(t *testing.T) {
+ service := getWebmentionService()
+ html := []byte("Foo
")
+ entry, err := service.ParseHEntry(&http.Response{Body: io.NopCloser(bytes.NewReader(html))})
+
+ require.NoError(t, err)
+ require.Equal(t, entry.Title, "")
+}
+
+func TestCreateNewWebmention(t *testing.T) {
+ service := getWebmentionService()
+ service.Http = &MockHttpClient{
+ PageContent: "",
+ }
+ entry := test.MockEntry{}
+ service.EntryRepository.Create(&entry)
+
+ err := service.ProcessWebmention(
+ "http://example.com/foo",
+ fmt.Sprintf("https.//example.com/posts/%s/", entry.ID()),
+ )
+ require.NoError(t, err)
+
+ inters, err := service.InteractionRepository.FindAll(entry.ID())
+ require.NoError(t, err)
+ require.Equal(t, len(inters), 1)
+ webm := inters[0].(*interactions.Webmention)
+ meta := webm.MetaData().(*interactions.WebmentionMetaData)
+ require.Equal(t, meta.Source, "http://example.com/foo")
+ require.Equal(t, meta.Target, fmt.Sprintf("https.//example.com/posts/%s/", entry.ID()))
+ require.Equal(t, meta.Title, "Foo")
+}
+
+// func TestGetWebmentionEndpointLink(t *testing.T) {
+// service := getWebmentionService()
+// html := []byte("")
+// endpoint, err := service.GetWebmentionEndpoint(constructResponse(html))
+
+// require.NoError(t, err)
+
+// require.Equal(t, endpoint, "http://example.com/webmention")
+// }
+
+// func TestGetWebmentionEndpointLinkA(t *testing.T) {
+// service := getWebmentionService()
+// html := []byte("")
+// endpoint, err := service.GetWebmentionEndpoint(constructResponse(html))
+
+// require.NoError(t, err)
+// require.Equal(t, endpoint, "http://example.com/webmention")
+// }
+
+// func TestGetWebmentionEndpointLinkAFakeWebmention(t *testing.T) {
+// service := getWebmentionService()
+// html := []byte("")
+// endpoint, err := service.GetWebmentionEndpoint(constructResponse(html))
+
+// require.NoError(t, err)
+// require.Equal(t, endpoint, "http://example.com/webmention")
+// }
+
+// func TestGetWebmentionEndpointLinkHeader(t *testing.T) {
+// service := getWebmentionService()
+// html := []byte("")
+// resp := constructResponse(html)
+// resp.Header = http.Header{"Link": []string{"; rel=\"webmention\""}}
+// endpoint, err := service.GetWebmentionEndpoint(resp)
+
+// require.NoError(t, err)
+// require.Equal(t, endpoint, "http://example.com/webmention")
+// }
+
+// func TestGetWebmentionEndpointLinkHeaderCommas(t *testing.T) {
+// service := getWebmentionService()
+// html := []byte("")
+// resp := constructResponse(html)
+// resp.Header = http.Header{
+// "Link": []string{"; rel=\"other\", ; rel=\"webmention\""},
+// }
+// endpoint, err := service.GetWebmentionEndpoint(resp)
+
+// require.NoError(t, err)
+// require.Equal(t, endpoint, "https://webmention.rocks/test/19/webmention")
+// }
+
+// func TestGetWebmentionEndpointRelativeLink(t *testing.T) {
+// service := getWebmentionService()
+// html := []byte("")
+// endpoint, err := service.GetWebmentionEndpoint(constructResponse(html))
+
+// require.NoError(t, err)
+// require.Equal(t, endpoint, "http://example.com/webmention")
+// }
+
+// func TestGetWebmentionEndpointRelativeLinkInHeader(t *testing.T) {
+// service := getWebmentionService()
+// html := []byte("")
+// resp := constructResponse(html)
+// resp.Header = http.Header{"Link": []string{"; rel=\"webmention\""}}
+// endpoint, err := service.GetWebmentionEndpoint(resp)
+
+// require.NoError(t, err)
+// require.Equal(t, endpoint, "http://example.com/webmention")
+// }
+
+// func TestRealWorldWebmention(t *testing.T) {
+// service := getWebmentionService()
+// links := []string{
+// "https://webmention.rocks/test/1",
+// "https://webmention.rocks/test/2",
+// "https://webmention.rocks/test/3",
+// "https://webmention.rocks/test/4",
+// "https://webmention.rocks/test/5",
+// "https://webmention.rocks/test/6",
+// "https://webmention.rocks/test/7",
+// "https://webmention.rocks/test/8",
+// "https://webmention.rocks/test/9",
+// // "https://webmention.rocks/test/10", // not supported
+// "https://webmention.rocks/test/11",
+// "https://webmention.rocks/test/12",
+// "https://webmention.rocks/test/13",
+// "https://webmention.rocks/test/14",
+// "https://webmention.rocks/test/15",
+// "https://webmention.rocks/test/16",
+// "https://webmention.rocks/test/17",
+// "https://webmention.rocks/test/18",
+// "https://webmention.rocks/test/19",
+// "https://webmention.rocks/test/20",
+// "https://webmention.rocks/test/21",
+// "https://webmention.rocks/test/22",
+// "https://webmention.rocks/test/23/page",
+// }
+
+// for _, link := range links {
+//
+// client := &owl.OwlHttpClient{}
+// html, _ := client.Get(link)
+// _, err := service.GetWebmentionEndpoint(html)
+
+// if err != nil {
+// t.Errorf("Unable to find webmention: %v for link %v", err, link)
+// }
+// }
+
+// }
diff --git a/infra/interaction_repository.go b/infra/interaction_repository.go
index 68f4144..c5b4f83 100644
--- a/infra/interaction_repository.go
+++ b/infra/interaction_repository.go
@@ -1,19 +1,24 @@
package infra
import (
+ "encoding/json"
+ "errors"
"owl-blogs/app"
"owl-blogs/app/repository"
"owl-blogs/domain/model"
+ "reflect"
+ "time"
+ "github.com/google/uuid"
"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"`
+ Id string `db:"id"`
+ Type string `db:"type"`
+ EntryId string `db:"entry_id"`
+ CreatedAt time.Time `db:"created_at"`
+ MetaData *string `db:"meta_data"`
}
type DefaultInteractionRepo struct {
@@ -42,8 +47,34 @@ func NewInteractionRepo(db Database, register *app.InteractionTypeRegistry) repo
}
// Create implements repository.InteractionRepository.
-func (*DefaultInteractionRepo) Create(interaction model.Interaction) error {
- panic("unimplemented")
+func (repo *DefaultInteractionRepo) Create(interaction model.Interaction) error {
+ t, err := repo.typeRegistry.TypeName(interaction)
+ if err != nil {
+ return errors.New("interaction type not registered")
+ }
+
+ if interaction.ID() == "" {
+ interaction.SetID(uuid.New().String())
+ }
+
+ var metaDataJson []byte
+ if interaction.MetaData() != nil {
+ metaDataJson, _ = json.Marshal(interaction.MetaData())
+ }
+ metaDataStr := string(metaDataJson)
+
+ _, err = repo.db.NamedExec(`
+ INSERT INTO interactions (id, type, entry_id, created_at, meta_data)
+ VALUES (:id, :type, :entry_id, :created_at, :meta_data)
+ `, sqlInteraction{
+ Id: interaction.ID(),
+ Type: t,
+ EntryId: interaction.EntryID(),
+ CreatedAt: interaction.CreatedAt(),
+ MetaData: &metaDataStr,
+ })
+
+ return err
}
// Delete implements repository.InteractionRepository.
@@ -52,8 +83,23 @@ func (*DefaultInteractionRepo) Delete(interaction model.Interaction) error {
}
// FindAll implements repository.InteractionRepository.
-func (*DefaultInteractionRepo) FindAll(entryId string) ([]model.Interaction, error) {
- panic("unimplemented")
+func (repo *DefaultInteractionRepo) FindAll(entryId string) ([]model.Interaction, error) {
+ data := []sqlInteraction{}
+ err := repo.db.Select(&data, "SELECT * FROM interactions WHERE entry_id = ?", entryId)
+ if err != nil {
+ return nil, err
+ }
+
+ interactions := []model.Interaction{}
+ for _, d := range data {
+ i, err := repo.sqlInteractionToInteraction(d)
+ if err != nil {
+ return nil, err
+ }
+ interactions = append(interactions, i)
+ }
+
+ return interactions, nil
}
// FindById implements repository.InteractionRepository.
@@ -65,3 +111,19 @@ func (*DefaultInteractionRepo) FindById(id string) (model.Interaction, error) {
func (*DefaultInteractionRepo) Update(interaction model.Interaction) error {
panic("unimplemented")
}
+
+func (repo *DefaultInteractionRepo) sqlInteractionToInteraction(interaction sqlInteraction) (model.Interaction, error) {
+ i, err := repo.typeRegistry.Type(interaction.Type)
+ if err != nil {
+ return nil, errors.New("interaction type not registered")
+ }
+ metaData := reflect.New(reflect.TypeOf(i.MetaData()).Elem()).Interface()
+ json.Unmarshal([]byte(*interaction.MetaData), metaData)
+ i.SetID(interaction.Id)
+ i.SetEntryID(interaction.EntryId)
+ i.SetCreatedAt(interaction.CreatedAt)
+ i.SetMetaData(metaData)
+
+ return i, nil
+
+}
diff --git a/interactions/webmention.go b/interactions/webmention.go
index 22247a9..09d8fc0 100644
--- a/interactions/webmention.go
+++ b/interactions/webmention.go
@@ -4,10 +4,10 @@ import "owl-blogs/domain/model"
type Webmention struct {
model.InteractionBase
- meta WebmentionInteractionMetaData
+ meta WebmentionMetaData
}
-type WebmentionInteractionMetaData struct {
+type WebmentionMetaData struct {
Source string
Target string
Title string
@@ -22,5 +22,5 @@ func (i *Webmention) MetaData() interface{} {
}
func (i *Webmention) SetMetaData(metaData interface{}) {
- i.meta = *metaData.(*WebmentionInteractionMetaData)
+ i.meta = *metaData.(*WebmentionMetaData)
}