diff --git a/cmd/owl/web/handler.go b/cmd/owl/web/handler.go index 4d20ac6..a54a1c6 100644 --- a/cmd/owl/web/handler.go +++ b/cmd/owl/web/handler.go @@ -116,7 +116,7 @@ func userWebmentionHandler(repo *owl.Repository) func(http.ResponseWriter, *http w.Write([]byte("Post not found")) return } - err = post.AddWebmention(source[0]) + err = post.AddIncomingWebmention(source[0]) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("Unable to process webmention")) diff --git a/cmd/owl/web/webmention_test.go b/cmd/owl/web/webmention_test.go index 5d69061..96324d4 100644 --- a/cmd/owl/web/webmention_test.go +++ b/cmd/owl/web/webmention_test.go @@ -75,7 +75,7 @@ func TestWebmentionWrittenToPost(t *testing.T) { // Check the status code is what we expect. assertStatus(t, rr, http.StatusAccepted) - if len(post.Webmentions()) != 1 { + if len(post.IncomingWebmentions()) != 1 { t.Errorf("no webmention written to post") } } diff --git a/post.go b/post.go index 2136697..dfdfb06 100644 --- a/post.go +++ b/post.go @@ -2,9 +2,7 @@ package owl import ( "bytes" - "crypto/sha256" - "encoding/base64" - "fmt" + "errors" "io/ioutil" "net/url" "os" @@ -34,8 +32,9 @@ type PostMeta struct { Draft bool `yaml:"draft"` } -type PostStatus struct { - Webmentions []WebmentionOut +type PostWebmetions struct { + Incoming []WebmentionIn `ymal:"incoming"` + Outgoing []WebmentionOut `ymal:"outgoing"` } func (post Post) Id() string { @@ -46,18 +45,14 @@ func (post Post) Dir() string { return path.Join(post.user.Dir(), "public", post.id) } -func (post Post) StatusFile() string { - return path.Join(post.Dir(), "status.yml") +func (post Post) WebmentionsFile() string { + return path.Join(post.Dir(), "webmentions.yml") } func (post Post) MediaDir() string { return path.Join(post.Dir(), "media") } -func (post Post) WebmentionDir() string { - return path.Join(post.Dir(), "webmention") -} - func (post Post) UrlPath() string { return post.user.UrlPath() + "posts/" + post.id + "/" } @@ -91,35 +86,35 @@ func (post Post) Content() []byte { return data } -func (post Post) Status() PostStatus { +func (post Post) Webmentions() PostWebmetions { // read status file // return parsed webmentions - fileName := post.StatusFile() + fileName := post.WebmentionsFile() if !fileExists(fileName) { - return PostStatus{} + return PostWebmetions{} } data, err := os.ReadFile(fileName) if err != nil { - return PostStatus{} + return PostWebmetions{} } - status := PostStatus{} - err = yaml.Unmarshal(data, &status) + webmentions := PostWebmetions{} + err = yaml.Unmarshal(data, &webmentions) if err != nil { - return PostStatus{} + return PostWebmetions{} } - return status + return webmentions } -func (post Post) PersistStatus(status PostStatus) error { - data, err := yaml.Marshal(status) +func (post Post) PersistWebmentions(webmentions PostWebmetions) error { + data, err := yaml.Marshal(webmentions) if err != nil { return err } - err = os.WriteFile(post.StatusFile(), data, 0644) + err = os.WriteFile(post.WebmentionsFile(), data, 0644) if err != nil { return err } @@ -195,103 +190,85 @@ func (post *Post) LoadMeta() error { return nil } -func (post *Post) WebmentionFile(source string) string { - - hash := sha256.Sum256([]byte(source)) - hashStr := base64.URLEncoding.EncodeToString(hash[:]) - return path.Join(post.WebmentionDir(), hashStr+".yml") -} - -func (post *Post) PersistWebmention(webmention WebmentionIn) error { - // ensure dir exists - os.MkdirAll(post.WebmentionDir(), 0755) - - // write to file - fileName := post.WebmentionFile(webmention.Source) - data, err := yaml.Marshal(webmention) - if err != nil { - return err - } - return os.WriteFile(fileName, data, 0644) -} - -func (post *Post) Webmention(source string) (WebmentionIn, error) { - // ensure dir exists - os.MkdirAll(post.WebmentionDir(), 0755) - - // Check if file exists - fileName := post.WebmentionFile(source) - if !fileExists(fileName) { - // return error if file doesn't exist - return WebmentionIn{}, fmt.Errorf("Webmention file not found: %s", source) - } - - data, err := os.ReadFile(fileName) - if err != nil { - return WebmentionIn{}, err - } - - mention := WebmentionIn{} - err = yaml.Unmarshal(data, &mention) - if err != nil { - return WebmentionIn{}, err - } - - return mention, nil -} - -func (post *Post) AddWebmention(source string) error { - // Check if file already exists - _, err := post.Webmention(source) - if err != nil { - webmention := WebmentionIn{ - Source: source, - } - defer post.EnrichWebmention(source) - return post.PersistWebmention(webmention) - } - return nil -} - -func (post *Post) AddOutgoingWebmention(target string) error { - status := post.Status() - - // Check if file already exists - _, err := post.Webmention(target) - if err != nil { - webmention := WebmentionOut{ - Target: target, - } - // if target is not in status, add it - for _, t := range status.Webmentions { - if t.Target == webmention.Target { - return nil - } - } - status.Webmentions = append(status.Webmentions, webmention) - } - - return post.PersistStatus(status) -} - -func (post *Post) UpdateOutgoingWebmention(webmention *WebmentionOut) error { - status := post.Status() +func (post *Post) PersistIncomingWebmention(webmention WebmentionIn) error { + wms := post.Webmentions() // if target is not in status, add it replaced := false - for i, t := range status.Webmentions { - if t.Target == webmention.Target { - status.Webmentions[i] = *webmention + for i, t := range wms.Incoming { + if t.Source == webmention.Source { + wms.Incoming[i] = webmention replaced = true break } } if !replaced { - status.Webmentions = append(status.Webmentions, *webmention) + wms.Incoming = append(wms.Incoming, webmention) } - return post.PersistStatus(status) + return post.PersistWebmentions(wms) +} + +func (post *Post) Webmention(source string) (WebmentionIn, error) { + wms := post.Webmentions() + for _, wm := range wms.Incoming { + if wm.Source == source { + return wm, nil + } + } + return WebmentionIn{}, errors.New("not found") +} + +func (post *Post) AddIncomingWebmention(source string) error { + // Check if file already exists + _, err := post.Webmention(source) + if err != nil { + wms := post.Webmentions() + wms.Incoming = append(wms.Incoming, WebmentionIn{ + Source: source, + }) + defer post.EnrichWebmention(source) + return post.PersistWebmentions(wms) + } + return nil +} + +func (post *Post) AddOutgoingWebmention(target string) error { + wms := post.Webmentions() + + // Check if file already exists + for _, wm := range wms.Outgoing { + if wm.Target == target { + return nil + } + } + + webmention := WebmentionOut{ + Target: target, + } + wms.Outgoing = append(wms.Outgoing, webmention) + return post.PersistWebmentions(wms) +} + +func (post *Post) UpdateOutgoingWebmention(webmention *WebmentionOut) error { + wms := post.Webmentions() + + // if target is not in status, add it + replaced := false + for i, t := range wms.Outgoing { + if t.Target == webmention.Target { + wms.Outgoing[i] = *webmention + replaced = true + break + } + } + + if !replaced { + wms.Outgoing = append(wms.Outgoing, *webmention) + } + + return post.PersistWebmentions(wms) } func (post *Post) EnrichWebmention(source string) error { @@ -304,35 +281,18 @@ func (post *Post) EnrichWebmention(source string) error { entry, err := post.user.repo.Parser.ParseHEntry(resp) if err == nil { webmention.Title = entry.Title - return post.PersistWebmention(webmention) + return post.PersistIncomingWebmention(webmention) } } return err } -func (post *Post) Webmentions() []WebmentionIn { - // ensure dir exists - os.MkdirAll(post.WebmentionDir(), 0755) - files := listDir(post.WebmentionDir()) - webmentions := []WebmentionIn{} - for _, file := range files { - data, err := os.ReadFile(path.Join(post.WebmentionDir(), file)) - if err != nil { - continue - } - mention := WebmentionIn{} - err = yaml.Unmarshal(data, &mention) - if err != nil { - continue - } - webmentions = append(webmentions, mention) - } - - return webmentions +func (post *Post) IncomingWebmentions() []WebmentionIn { + return post.Webmentions().Incoming } func (post *Post) ApprovedWebmentions() []WebmentionIn { - webmentions := post.Webmentions() + webmentions := post.IncomingWebmentions() approved := []WebmentionIn{} for _, webmention := range webmentions { if webmention.ApprovalStatus == "approved" { @@ -348,9 +308,7 @@ func (post *Post) ApprovedWebmentions() []WebmentionIn { } func (post *Post) OutgoingWebmentions() []WebmentionOut { - status := post.Status() - return status.Webmentions - + return post.Webmentions().Outgoing } // ScanForLinks scans the post content for links and adds them to the diff --git a/post_test.go b/post_test.go index d26272c..5f52f7e 100644 --- a/post_test.go +++ b/post_test.go @@ -167,18 +167,18 @@ func TestLoadMeta(t *testing.T) { /// Webmention /// -func TestPersistWebmention(t *testing.T) { +func TestPersistIncomingWebmention(t *testing.T) { repo := getTestRepo(owl.RepoConfig{}) user, _ := repo.CreateUser("testuser") post, _ := user.CreateNewPost("testpost") webmention := owl.WebmentionIn{ Source: "http://example.com/source", } - err := post.PersistWebmention(webmention) + err := post.PersistIncomingWebmention(webmention) if err != nil { t.Errorf("Got error: %v", err) } - mentions := post.Webmentions() + mentions := post.IncomingWebmentions() if len(mentions) != 1 { t.Errorf("Expected 1 webmention, got %d", len(mentions)) } @@ -188,74 +188,64 @@ func TestPersistWebmention(t *testing.T) { } } -func TestAddWebmentionCreatesFile(t *testing.T) { +func TestAddIncomingWebmentionCreatesFile(t *testing.T) { repo := getTestRepo(owl.RepoConfig{}) repo.HttpClient = &MockHttpClient{} repo.Parser = &MockHtmlParser{} user, _ := repo.CreateUser("testuser") post, _ := user.CreateNewPost("testpost") - err := post.AddWebmention("https://example.com") + err := post.AddIncomingWebmention("https://example.com") if err != nil { t.Errorf("Got Error: %v", err) } - mentions := post.Webmentions() + mentions := post.IncomingWebmentions() if len(mentions) != 1 { t.Errorf("Expected 1 webmention, got %d", len(mentions)) } } -func TestAddWebmentionNotOverwritingFile(t *testing.T) { +func TestAddIncomingWebmentionNotOverwritingWebmention(t *testing.T) { repo := getTestRepo(owl.RepoConfig{}) repo.HttpClient = &MockHttpClient{} repo.Parser = &MockHtmlParser{} user, _ := repo.CreateUser("testuser") post, _ := user.CreateNewPost("testpost") - post.AddWebmention("https://example.com") - dir, _ := os.Open(post.WebmentionDir()) - defer dir.Close() - files, _ := dir.Readdirnames(-1) + post.PersistIncomingWebmention(owl.WebmentionIn{ + Source: "https://example.com", + ApprovalStatus: "approved", + }) - if len(files) != 1 { - t.Error("No file created for webmention") + post.AddIncomingWebmention("https://example.com") + + mentions := post.IncomingWebmentions() + if len(mentions) != 1 { + t.Errorf("Expected 1 webmention, got %d", len(mentions)) } - content := "url: https://example.com\n" - content += "verified: true" - os.WriteFile(path.Join(post.WebmentionDir(), files[0]), []byte(content), 0644) - - post.AddWebmention("https://example.com") - - fileContent, _ := os.ReadFile(path.Join(post.WebmentionDir(), files[0])) - if string(fileContent) != content { - t.Error("File content was modified.") - t.Errorf("Got: %v", fileContent) - t.Errorf("Expected: %v", content) + if mentions[0].ApprovalStatus != "approved" { + t.Errorf("Expected approval status: %s, got %s", "approved", mentions[0].ApprovalStatus) } } -func TestAddWebmentionAddsParsedTitle(t *testing.T) { +func TestAddIncomingWebmentionAddsParsedTitle(t *testing.T) { repo := getTestRepo(owl.RepoConfig{}) repo.HttpClient = &MockHttpClient{} repo.Parser = &MockHtmlParser{} user, _ := repo.CreateUser("testuser") post, _ := user.CreateNewPost("testpost") - post.AddWebmention("https://example.com") - dir, _ := os.Open(post.WebmentionDir()) - defer dir.Close() - files, _ := dir.Readdirnames(-1) + post.AddIncomingWebmention("https://example.com") - if len(files) != 1 { - t.Error("No file created for webmention") + mentions := post.IncomingWebmentions() + if len(mentions) != 1 { + t.Errorf("Expected 1 webmention, got %d", len(mentions)) } - fileContent, _ := os.ReadFile(path.Join(post.WebmentionDir(), files[0])) - if !strings.Contains(string(fileContent), "Mock Title") { - t.Error("File not containing the title.") - t.Errorf("Got: %v", string(fileContent)) + if mentions[0].Title != "Mock Title" { + t.Errorf("Expected title: %s, got %s", "Mock Title", mentions[0].Title) } } @@ -268,25 +258,25 @@ func TestApprovedWebmentions(t *testing.T) { ApprovalStatus: "approved", RetrievedAt: time.Now(), } - post.PersistWebmention(webmention) + post.PersistIncomingWebmention(webmention) webmention = owl.WebmentionIn{ Source: "http://example.com/source2", ApprovalStatus: "", RetrievedAt: time.Now().Add(time.Hour * -1), } - post.PersistWebmention(webmention) + post.PersistIncomingWebmention(webmention) webmention = owl.WebmentionIn{ Source: "http://example.com/source3", ApprovalStatus: "approved", RetrievedAt: time.Now().Add(time.Hour * -2), } - post.PersistWebmention(webmention) + post.PersistIncomingWebmention(webmention) webmention = owl.WebmentionIn{ Source: "http://example.com/source4", ApprovalStatus: "rejected", RetrievedAt: time.Now().Add(time.Hour * -3), } - post.PersistWebmention(webmention) + post.PersistIncomingWebmention(webmention) webmentions := post.ApprovedWebmentions() if len(webmentions) != 2 { diff --git a/renderer_test.go b/renderer_test.go index 5f80e6e..369032f 100644 --- a/renderer_test.go +++ b/renderer_test.go @@ -187,13 +187,13 @@ func TestRenderPostAddsLinksToApprovedWebmention(t *testing.T) { ApprovalStatus: "approved", RetrievedAt: time.Now().Add(time.Hour * -2), } - post.PersistWebmention(webmention) + post.PersistIncomingWebmention(webmention) webmention = owl.WebmentionIn{ Source: "http://example.com/source4", ApprovalStatus: "rejected", RetrievedAt: time.Now().Add(time.Hour * -3), } - post.PersistWebmention(webmention) + post.PersistIncomingWebmention(webmention) result, _ := owl.RenderPost(&post) if !strings.Contains(result, "http://example.com/source3") { diff --git a/user.go b/user.go index b9eeaa4..e7a1de7 100644 --- a/user.go +++ b/user.go @@ -156,7 +156,6 @@ func (user User) CreateNewPost(title string) (Post, error) { os.WriteFile(post.ContentFile(), []byte(initial_content), 0644) // create media dir os.Mkdir(post.MediaDir(), 0755) - os.Mkdir(post.WebmentionDir(), 0755) return post, nil }