From be60edd13598dd7f91fad5d9842fcb06b3fa7e7c Mon Sep 17 00:00:00 2001 From: Joe Constant Date: Wed, 6 Dec 2023 04:27:12 +0000 Subject: [PATCH 1/4] Add activity routes --- go.mod | 5 ++- go.sum | 2 + internal/activitypub/activity.go | 33 ++++++++++++++++ internal/activitypub/context.go | 4 +- internal/activitypub/post.go | 16 ++++---- internal/activitypub/user.go | 2 +- internal/http/routes/activity.go | 66 ++++++++++++++++++++++++++++++++ internal/http/routes/post.go | 1 + internal/http/routes/routes.go | 1 + 9 files changed, 117 insertions(+), 13 deletions(-) create mode 100644 internal/activitypub/activity.go create mode 100644 internal/http/routes/activity.go diff --git a/go.mod b/go.mod index 50d440e..4a0be8d 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,7 @@ module participating-online/sublinks-federation go 1.21.4 -require github.com/gorilla/mux v1.8.1 +require ( + github.com/gorilla/mux v1.8.1 + golang.org/x/text v0.14.0 +) diff --git a/go.sum b/go.sum index 7128337..eb8c2be 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,4 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= diff --git a/internal/activitypub/activity.go b/internal/activitypub/activity.go new file mode 100644 index 0000000..3e655e9 --- /dev/null +++ b/internal/activitypub/activity.go @@ -0,0 +1,33 @@ +package activitypub + +type Activity struct { + Context Context `json:"@context"` + Actor string `json:"actor"` + To []string `json:"to"` + Object interface{} `json:"object"` + Cc []string `json:"cc"` + Audience string `json:"audience"` + Type string `json:"type"` + Id string `json:"id"` +} + +func NewActivity( + id string, + actType string, + actor string, + to []string, + cc []string, + audience string, + obj interface{}, +) Activity { + return Activity{ + Context: *GetContext(), + Id: id, + Type: actType, + Actor: actor, + To: to, + Cc: cc, + Audience: audience, + Object: obj, + } +} diff --git a/internal/activitypub/context.go b/internal/activitypub/context.go index fe4d401..60bccb8 100644 --- a/internal/activitypub/context.go +++ b/internal/activitypub/context.go @@ -26,8 +26,8 @@ type moderator struct { Id string `json:"@id"` } -func GetContext() Context { - return Context{ +func GetContext() *Context { + return &Context{ "https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1", lemmyContextData{ diff --git a/internal/activitypub/post.go b/internal/activitypub/post.go index 2d7f57e..3e80b07 100644 --- a/internal/activitypub/post.go +++ b/internal/activitypub/post.go @@ -12,28 +12,27 @@ type Language struct { } type Post struct { - Context Context `json:"@context"` + Context *Context `json:"@context,omitempty"` Id string `json:"id"` Type string `json:"type"` AttributedTo string `json:"attributedTo"` To []string `json:"to"` - Cc []string `json:"cc"` + Cc []string `json:"cc,omitempty"` Audience string `json:"audience"` Name string `json:"name"` Content string `json:"content"` MediaType string `json:"mediaType"` Source Source `json:"source"` - Attachment []Link `json:"attachment"` - Image []Link `json:"image"` + Attachment []Link `json:"attachment,omitempty"` + Image []Link `json:"image,omitempty"` Sensitive bool `json:"sensitive"` CommentsEnabled bool `json:"commentsEnabled"` Language Language `json:"language"` Published time.Time `json:"published"` } -func NewPost(postUrl string, fromUser string, communityUrl string, postTitle string, postBody string, nsfw bool, published time.Time) Post { - post := Post{ - Context: GetContext(), +func NewPost(postUrl string, fromUser string, communityUrl string, postTitle string, postBody string, nsfw bool, published time.Time) *Post { + return &Post{ Id: postUrl, Type: "Page", AttributedTo: fromUser, @@ -57,10 +56,9 @@ func NewPost(postUrl string, fromUser string, communityUrl string, postTitle str }, Published: published, } - return post } -func ConvertPostToApub(p *lemmy.PostResponse) Post { +func ConvertPostToApub(p *lemmy.PostResponse) *Post { return NewPost( p.PostView.Post.ApId, fmt.Sprintf("https://demo.sublinks.org/u/%s", p.PostView.Creator.Name), diff --git a/internal/activitypub/user.go b/internal/activitypub/user.go index 1433e73..18d184d 100644 --- a/internal/activitypub/user.go +++ b/internal/activitypub/user.go @@ -17,7 +17,7 @@ type Endpoints struct { } type User struct { - Context Context `json:"@context"` + Context *Context `json:"@context"` Id string `json:"id"` PreferredUsername string `json:"preferredUsername"` Inbox string `json:"inbox"` diff --git a/internal/http/routes/activity.go b/internal/http/routes/activity.go new file mode 100644 index 0000000..97b99ab --- /dev/null +++ b/internal/http/routes/activity.go @@ -0,0 +1,66 @@ +package routes + +import ( + "context" + "encoding/json" + "log" + "net/http" + "participating-online/sublinks-federation/internal/activitypub" + "participating-online/sublinks-federation/internal/lemmy" + + "fmt" + + "golang.org/x/text/cases" + "golang.org/x/text/language" + + "github.com/gorilla/mux" +) + +func SetupActivityRoutes(r *mux.Router) { + r.HandleFunc("/activities/{action}/{id}", getActivityHandler).Methods("GET") +} + +func getActivityHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + var content []byte + switch vars["action"] { + case "post": + obj, err := GetPostActivityObject(vars["id"]) + if err != nil { + log.Println("Error reading object", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + content, _ = json.MarshalIndent( + activitypub.NewActivity( + r.RequestURI, + cases.Title(language.English).String(vars["action"]), + obj.AttributedTo, + obj.To, + obj.Cc, + obj.Audience, + obj, + ), "", " ") + + break + default: + error.Error(fmt.Errorf("action %s not found", vars["action"])) + w.WriteHeader(http.StatusNotFound) + return + } + + w.WriteHeader(http.StatusOK) + w.Header().Add("content-type", "application/activity+json") + w.Write(content) +} + +func GetPostActivityObject(id string) (*activitypub.Post, error) { + ctx := context.Background() + c := lemmy.GetLemmyClient(ctx) + post, err := c.GetPost(ctx, id) + if err != nil { + log.Println("Error reading post", err) + return nil, err + } + return activitypub.ConvertPostToApub(post), nil +} diff --git a/internal/http/routes/post.go b/internal/http/routes/post.go index 82c5ca0..4410f9b 100644 --- a/internal/http/routes/post.go +++ b/internal/http/routes/post.go @@ -25,6 +25,7 @@ func getPostHandler(w http.ResponseWriter, r *http.Request) { return } postLd := activitypub.ConvertPostToApub(post) + postLd.Context = activitypub.GetContext() w.WriteHeader(http.StatusOK) w.Header().Add("content-type", "application/activity+json") content, _ := json.MarshalIndent(postLd, "", " ") diff --git a/internal/http/routes/routes.go b/internal/http/routes/routes.go index 0b84013..6c2404a 100644 --- a/internal/http/routes/routes.go +++ b/internal/http/routes/routes.go @@ -7,5 +7,6 @@ func SetupRoutes() *mux.Router { SetupUserRoutes(r) SetupPostRoutes(r) SetupApubRoutes(r) + SetupActivityRoutes(r) return r } From 35e9a9a5397bd53b9ac9c710485b40fb6b8a9c9d Mon Sep 17 00:00:00 2001 From: Joe Constant Date: Wed, 6 Dec 2023 04:27:43 +0000 Subject: [PATCH 2/4] Add 'go test' task Added a new task 'go test' to run tests with the 'go test' command. --- .codesandbox/tasks.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.codesandbox/tasks.json b/.codesandbox/tasks.json index c9e1f24..51c5a96 100644 --- a/.codesandbox/tasks.json +++ b/.codesandbox/tasks.json @@ -16,6 +16,16 @@ "branch": true, "resume": true } + }, + "go test": { + "name": "Test", + "command": "go test -v ./...", + "runAtStart": true, + "restartOn": { + "files": ["*"], + "branch": true, + "resume": true + } } } } From 9400de724520742c73a5bb04fe1ef1f4b6d81cdc Mon Sep 17 00:00:00 2001 From: Joe Constant Date: Wed, 6 Dec 2023 04:49:22 +0000 Subject: [PATCH 3/4] Change case 'post' to 'create' --- internal/http/routes/activity.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/http/routes/activity.go b/internal/http/routes/activity.go index 97b99ab..5f156da 100644 --- a/internal/http/routes/activity.go +++ b/internal/http/routes/activity.go @@ -24,7 +24,7 @@ func getActivityHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) var content []byte switch vars["action"] { - case "post": + case "create": obj, err := GetPostActivityObject(vars["id"]) if err != nil { log.Println("Error reading object", err) From 5cb67bb01e337bc659f99299b2306b0303265bc0 Mon Sep 17 00:00:00 2001 From: Joe Constant Date: Wed, 6 Dec 2023 04:49:58 +0000 Subject: [PATCH 4/4] Add Contributing Guidelines Guidelines were proudly stolen from the frontend repo and modified to better fit this one --- CONTRIBUTING.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..6345acc --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,16 @@ +# Contributing +Aka. Developer Guidelines + +## Intro + +We're currently _very_ early in the development stages of the Sublinks federation service. Therefore there aren't too many guidelines yet. However, as you can see below there are some that we would appreciate you to consider before and while you contribute to this project. + +As with most things you'll read about in this document, even these guidelines are up for discussion. If you believe anything should be changed please feel free to create a pull request and let us know what and why. Same thing applies if you want to add or stricten/loosen a guideline. + +## Tests + +Support for tests has been set up. Please submit unit and/or end-to-end tests with all PRs. 100% coverage (at least with unit tests) is a falacy. Ideally we want to strive for "good enough" coverage instead. + +## Pull Requests + +The pull request flow in this project isn't anything special. We require a pull request to be created before anything is merged into `main`. At least one person must approve the pull request.