WIP receiving webmentionsi. #7

This commit is contained in:
Niko Abeler 2022-08-23 17:59:17 +02:00
parent 179559250a
commit 691158cd0e
6 changed files with 156 additions and 0 deletions

View File

@ -26,6 +26,9 @@ Each directory in the `/users/` directory of a repository is considered a user.
\- media/ \- media/
-- Contains all media files used in the blog post. -- Contains all media files used in the blog post.
-- All files in this folder will be publicly available -- All files in this folder will be publicly available
\- webmention/
\- <hash>.yml
-- Contains data for a received webmention
\- meta/ \- meta/
\- base.html \- base.html
-- The template used to render all sites -- The template used to render all sites

View File

@ -5,6 +5,7 @@ import (
"net/http" "net/http"
"os" "os"
"path" "path"
"strings"
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
) )
@ -56,6 +57,59 @@ func userIndexHandler(repo *owl.Repository) func(http.ResponseWriter, *http.Requ
} }
} }
func userWebmentionHandler(repo *owl.Repository) func(http.ResponseWriter, *http.Request, httprouter.Params) {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
user, err := getUserFromRepo(repo, ps)
if err != nil {
w.WriteHeader(http.StatusNotFound)
w.Write([]byte("User not found"))
return
}
err = r.ParseForm()
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Unable to parse form data"))
return
}
params := r.PostForm
target := params["target"]
source := params["source"]
if len(target) == 0 {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("No target provided"))
return
}
if len(source) == 0 {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("No source provided"))
return
}
parts := strings.Split(target[0], "/")
if len(parts) < 2 {
w.WriteHeader(http.StatusNotFound)
w.Write([]byte("Not found"))
return
}
postId := parts[len(parts)-2]
post, err := user.GetPost(postId)
if err != nil {
w.WriteHeader(http.StatusNotFound)
w.Write([]byte("Post not found"))
return
}
err = post.AddWebmention(source[0])
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("Unable to process webmention"))
return
}
w.WriteHeader(http.StatusAccepted)
w.Write([]byte(""))
}
}
func userRSSHandler(repo *owl.Repository) func(http.ResponseWriter, *http.Request, httprouter.Params) { func userRSSHandler(repo *owl.Repository) func(http.ResponseWriter, *http.Request, httprouter.Params) {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
user, err := getUserFromRepo(repo, ps) user, err := getUserFromRepo(repo, ps)

View File

@ -14,6 +14,7 @@ func Router(repo *owl.Repository) http.Handler {
router.ServeFiles("/static/*filepath", http.Dir(repo.StaticDir())) router.ServeFiles("/static/*filepath", http.Dir(repo.StaticDir()))
router.GET("/", repoIndexHandler(repo)) router.GET("/", repoIndexHandler(repo))
router.GET("/user/:user/", userIndexHandler(repo)) router.GET("/user/:user/", userIndexHandler(repo))
router.POST("/user/:user/webmention/", userWebmentionHandler(repo))
router.GET("/user/:user/index.xml", userRSSHandler(repo)) router.GET("/user/:user/index.xml", userRSSHandler(repo))
router.GET("/user/:user/posts/:post/", postHandler(repo)) router.GET("/user/:user/posts/:post/", postHandler(repo))
router.GET("/user/:user/posts/:post/media/*filepath", postMediaHandler(repo)) router.GET("/user/:user/posts/:post/media/*filepath", postMediaHandler(repo))
@ -25,6 +26,7 @@ func SingleUserRouter(repo *owl.Repository) http.Handler {
router := httprouter.New() router := httprouter.New()
router.ServeFiles("/static/*filepath", http.Dir(repo.StaticDir())) router.ServeFiles("/static/*filepath", http.Dir(repo.StaticDir()))
router.GET("/", userIndexHandler(repo)) router.GET("/", userIndexHandler(repo))
router.POST("/webmention/", userWebmentionHandler(repo))
router.GET("/index.xml", userRSSHandler(repo)) router.GET("/index.xml", userRSSHandler(repo))
router.GET("/posts/:post/", postHandler(repo)) router.GET("/posts/:post/", postHandler(repo))
router.GET("/posts/:post/media/*filepath", postMediaHandler(repo)) router.GET("/posts/:post/media/*filepath", postMediaHandler(repo))

View File

@ -0,0 +1,78 @@
package main_test
import (
main "h4kor/owl-blogs/cmd/owl-web"
"net/http"
"net/http/httptest"
"net/url"
"strconv"
"strings"
"testing"
)
func TestWebmentionHandleAccepts(t *testing.T) {
repo := getTestRepo()
user, _ := repo.CreateUser("test-1")
post, _ := user.CreateNewPost("post-1")
target := post.FullUrl()
source := "https://example.com"
data := url.Values{}
data.Set("target", target)
data.Set("source", source)
// Create Request and Response
req, err := http.NewRequest("POST", user.UrlPath()+"webmention/", strings.NewReader(data.Encode()))
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.Header.Add("Content-Length", strconv.Itoa(len(data.Encode())))
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
router := main.Router(&repo)
router.ServeHTTP(rr, req)
// Check the status code is what we expect.
if status := rr.Code; status != http.StatusAccepted {
t.Errorf("handler returned wrong status code: got %v want %v",
status, http.StatusAccepted)
t.Errorf("Body: %v", rr.Body)
}
}
func TestWebmentionWrittenToPost(t *testing.T) {
repo := getTestRepo()
user, _ := repo.CreateUser("test-1")
post, _ := user.CreateNewPost("post-1")
target := post.FullUrl()
source := "https://example.com"
data := url.Values{}
data.Set("target", target)
data.Set("source", source)
// Create Request and Response
req, err := http.NewRequest("POST", user.UrlPath()+"webmention/", strings.NewReader(data.Encode()))
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.Header.Add("Content-Length", strconv.Itoa(len(data.Encode())))
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
router := main.Router(&repo)
router.ServeHTTP(rr, req)
// Check the status code is what we expect.
if status := rr.Code; status != http.StatusAccepted {
t.Errorf("handler returned wrong status code: got %v want %v",
status, http.StatusAccepted)
return
}
if len(post.Webmentions()) != 1 {
t.Errorf("no webmention written to post")
}
}

18
post.go
View File

@ -2,7 +2,10 @@ package owl
import ( import (
"bytes" "bytes"
"crypto/sha256"
"encoding/base64"
"io/ioutil" "io/ioutil"
"os"
"path" "path"
"github.com/yuin/goldmark" "github.com/yuin/goldmark"
@ -39,6 +42,10 @@ func (post Post) MediaDir() string {
return path.Join(post.Dir(), "media") return path.Join(post.Dir(), "media")
} }
func (post Post) WebmentionDir() string {
return path.Join(post.Dir(), "webmention")
}
func (post Post) UrlPath() string { func (post Post) UrlPath() string {
return post.user.UrlPath() + "posts/" + post.id + "/" return post.user.UrlPath() + "posts/" + post.id + "/"
} }
@ -139,3 +146,14 @@ func (post *Post) LoadMeta() error {
post.meta = meta post.meta = meta
return nil return nil
} }
func (post *Post) AddWebmention(source string) error {
hash := sha256.Sum256([]byte(source))
hashStr := base64.URLEncoding.EncodeToString(hash[:])
data := "source: " + source
return os.WriteFile(path.Join(post.WebmentionDir(), hashStr+".yml"), []byte(data), 0644)
}
func (post *Post) Webmentions() []string {
return listDir(post.WebmentionDir())
}

View File

@ -150,6 +150,7 @@ func (user User) CreateNewPost(title string) (Post, error) {
os.WriteFile(post.ContentFile(), []byte(initial_content), 0644) os.WriteFile(post.ContentFile(), []byte(initial_content), 0644)
// create media dir // create media dir
os.Mkdir(post.MediaDir(), 0755) os.Mkdir(post.MediaDir(), 0755)
os.Mkdir(post.WebmentionDir(), 0755)
return post, nil return post, nil
} }