diff --git a/.air.toml b/.air.toml index 37bc493..891a1c4 100644 --- a/.air.toml +++ b/.air.toml @@ -1,11 +1,11 @@ root = "." testdata_dir = "testdata" -tmp_dir = "tmp" +tmp_dir = "/tmp" [build] args_bin = ["web"] - bin = "./tmp/main" - cmd = "go build -o ./tmp/main owl-blogs/cmd/owl" + bin = "/tmp/main" + cmd = "go build -buildvcs=false -o /tmp/main owl-blogs/cmd/owl" delay = 1000 exclude_dir = ["assets", "tmp", "vendor", "testdata"] exclude_file = [] diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..8313a0c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +e2e_tests/ +tmp/ +*.db \ No newline at end of file diff --git a/.gitignore b/.gitignore index fd7e798..13804c9 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,7 @@ users/ *.db -tmp/ \ No newline at end of file +tmp/ + +venv/ +*.pyc \ No newline at end of file diff --git a/app/activity_pub_service.go b/app/activity_pub_service.go index 5ae367f..4ef9634 100644 --- a/app/activity_pub_service.go +++ b/app/activity_pub_service.go @@ -1,6 +1,7 @@ package app import ( + "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/pem" @@ -12,6 +13,7 @@ import ( "owl-blogs/config" "owl-blogs/domain/model" "owl-blogs/render" + "reflect" "time" vocab "github.com/go-ap/activitypub" @@ -56,6 +58,46 @@ func NewActivityPubService(followersRepo repository.FollowerRepository, configRe } } +func (svc *ActivityPubService) defaultConfig() ActivityPubConfig { + privKey, _ := rsa.GenerateKey(rand.Reader, 2048) + pubKey := privKey.Public().(*rsa.PublicKey) + + pubKeyPem := pem.EncodeToMemory( + &pem.Block{ + Type: "RSA PUBLIC KEY", + Bytes: x509.MarshalPKCS1PublicKey(pubKey), + }, + ) + + privKeyPrm := pem.EncodeToMemory( + &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(privKey), + }, + ) + + return ActivityPubConfig{ + PreferredUsername: "blog", + PublicKeyPem: string(pubKeyPem), + PrivateKeyPem: string(privKeyPrm), + } +} + +func (svc *ActivityPubService) GetApConfig() (ActivityPubConfig, error) { + apConfig := ActivityPubConfig{} + err := svc.configRepo.Get(config.ACT_PUB_CONF_NAME, &apConfig) + if err != nil { + println("ERROR IN ACTIVITY PUB CONFIG") + return ActivityPubConfig{}, err + } + if reflect.ValueOf(apConfig).IsZero() { + cfg := svc.defaultConfig() + svc.configRepo.Update(config.ACT_PUB_CONF_NAME, cfg) + return cfg, nil + } + return apConfig, nil +} + func (s *ActivityPubService) AddFollower(follower string) error { return s.followersRepo.Add(follower) } diff --git a/e2e_tests/conftest.py b/e2e_tests/conftest.py new file mode 100644 index 0000000..7714dc1 --- /dev/null +++ b/e2e_tests/conftest.py @@ -0,0 +1,49 @@ +import pytest +from requests import Session +from urllib.parse import urljoin +from tests.fixtures import ACCT_NAME + + +class LiveServerSession(Session): + def __init__(self, base_url=None): + super().__init__() + self.base_url = base_url + + def request(self, method, url, *args, **kwargs): + joined_url = urljoin(self.base_url, url) + return super().request(method, joined_url, *args, **kwargs) + + +@pytest.fixture +def client(): + return LiveServerSession("http://localhost:3000") + + +@pytest.fixture +def actor_url(client): + resp = client.get(f"/.well-known/webfinger?resource={ACCT_NAME}") + data = resp.json() + self_link = [x for x in data["links"] if x["rel"] == "self"][0] + return self_link["href"] + + +@pytest.fixture +def actor(client): + resp = client.get(actor_url, headers={"Content-Type": "application/activity+json"}) + assert resp.status_code == 200 + return resp.json() + + +@pytest.fixture +def inbox(actor): + return actor["inbox"] + + +@pytest.fixture +def outbox(actor): + return actor["outbox"] + + +@pytest.fixture +def followers(actor): + return actor["followers"] diff --git a/e2e_tests/docker-compose.yml b/e2e_tests/docker-compose.yml new file mode 100644 index 0000000..991de34 --- /dev/null +++ b/e2e_tests/docker-compose.yml @@ -0,0 +1,24 @@ +services: + web: + build: + context: ../ + dockerfile: Dockerfile.test + volumes: + - ../app:/go/owl/app + - ../assets:/go/owl/assets + - ../cmd:/go/owl/cmd + - ../config:/go/owl/config + - ../domain:/go/owl/domain + - ../entry_types:/go/owl/entry_types + - ../importer:/go/owl/importer + - ../infra:/go/owl/infra + - ../interactions:/go/owl/interactions + - ../plugings:/go/owl/plugings + - ../render:/go/owl/render + - ../web:/go/owl/web + ports: + - "3000:3000" + mock_masto: + build: mock_masto + ports: + - 8000:8000 diff --git a/e2e_tests/mock_masto/Dockerfile b/e2e_tests/mock_masto/Dockerfile new file mode 100644 index 0000000..900aa26 --- /dev/null +++ b/e2e_tests/mock_masto/Dockerfile @@ -0,0 +1,6 @@ +FROM python:3.11 + +COPY . . +RUN pip install -r requirements.txt + +CMD [ "python", "main.py" ] \ No newline at end of file diff --git a/e2e_tests/mock_masto/main.py b/e2e_tests/mock_masto/main.py new file mode 100644 index 0000000..5132479 --- /dev/null +++ b/e2e_tests/mock_masto/main.py @@ -0,0 +1,208 @@ +import json +from flask import Flask, request + +app = Flask(__name__) + + +PRIV_KEY_PEM = """-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCni8P4bvkC/3Sx +NTrDw1qw0vWtJKZMsyJ3Mcs4+1apoVqOQhujUqGqFSiRT7Vmc7OEhB0vikdiTkCk +1PcoTd/rOa/0WbG8385JcLzaJfTIG+rrRtHwZ1TwxwBju43jpGEZjpbA0dqoXMkr +J1MyD7aPLoAiVe0ikw2czSZumv4ncemOtk0VG3b2TnIxo3CMKtUOWu8xT08MMIuo +3cZRnpI6Xr/ULzvjv8e3EjIpwRJqMPECtGsfdcjFmR0yFIrjrlmkZTiW31z/Dk7i +xRGD0ADy3WEQ3lA4l3mNZeyG4S0Wi4iYe9/wegESMZcakLoME7ks+KNS388Mdbcd +DKy9NmWvAgMBAAECggEABLQAA0hHhdWv6+Lc9xkpFuTvxTV4fuyvCf4u1eGlnstg +ZF/nW1/6w8XQ8WCgbJ4mKuZz1J14FYKxfoRaj8S9MA2Ff+wd+M77gRpAuDWajRzO +LQk8OW2yd7POXKkAzvln9F9eofkCFKR4zSpPGTenCJaQkuYrQEOKfUf7oofdRzQi +w9kmp3wAxM/EseHZpknYDCgDQV7MDQAaMD7kbynL2WfXPxebktwpRlKUwgtGrevj +gagQL8J/GX6wO3ymw9sln4BhlI2+3LuiMXQdQc1tamkXFCguCuOZCu/2VRdCHmiS +nnpu+FMspBHbvxO+RXo3Cu/S6jjJgoQxD2WZTE0gqQKBgQDM6AQdqBYjISdkI9Gl +6ZLLjwZRJSYpopujtX7pun61l9kUwQevaR2Z39rMWxX62DD6arazi/ygIUBw6Kgp +s/qBEb29ec+0cESdC8aJYb3dGvDzh/8C05p7ozxj8JZQcxq5W5jql/BELlSsUONO +jfqQv8RGZNSkD9uy6TxOr4eWIwKBgQDRUuO/XRDLt8Mp10mTshxTznSQ3gAJYKeG +0WfEC3kPEukHBQb8huqFcQDiQ71oBWuEdOQWgT3aBS6L+nIMyZMT5u+BejQm7/E5 +pMM+z0VRpfFSsIrCvU8yKam0aemQGlKQAfhTct1gCg+wKnYsSQMlNHKWEfDbw9I/ +cns/IN+dBQKBgQC6/Of0oFVDTZgC3GUPAO3C8QwUtM/0or1hUdk1Nck3shCZzeVT +f5tRtmSWpHCUbwGTJBsCEjdBcda6srXzCJkLe8Moy6ZtxR34KqzM5fM7eMB1nJ9s +Vunc9gPAN+cUF1ZF3H7ZZjoOHjGK5m3oW8xSl41np9Acv5P/2rP8Ilaa/QKBgQDJ +YwISfitGk8mEW8hB/L4cMykapztJyl/i6Vz31EHoKr1fL4sFMZg4QfwjtCBqD6zd +hshajoU/WHTr30wS2WxTXX9YBoZeX8KpPsdJioiagRioAYm+yfuDu2m2VZ+MMIb2 +Xa7YOk6Zs5RcXL3M5YHNLaSAlUoxZTjGKhJBLhN1MQKBgQCbo3ngBl7Qjjx4WJ93 +2WEEKvSDCv69eecNQDuKWKEiFqBN23LheNrN8DXMWFTtE4miY106dzQ0dUMh418x +K98rXSX3VvY4w48AznvPMKVLqesFjcvwnBdvk/NqXod20CMSpOEVj6W/nGoTBQt2 +0PuW3IUym9KvO0WX9E+1Qw8mbw== +-----END PRIVATE KEY-----""" + +PUB_KEY_PEM = """-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp4vD+G75Av90sTU6w8Na +sNL1rSSmTLMidzHLOPtWqaFajkIbo1KhqhUokU+1ZnOzhIQdL4pHYk5ApNT3KE3f +6zmv9FmxvN/OSXC82iX0yBvq60bR8GdU8McAY7uN46RhGY6WwNHaqFzJKydTMg+2 +jy6AIlXtIpMNnM0mbpr+J3HpjrZNFRt29k5yMaNwjCrVDlrvMU9PDDCLqN3GUZ6S +Ol6/1C8747/HtxIyKcESajDxArRrH3XIxZkdMhSK465ZpGU4lt9c/w5O4sURg9AA +8t1hEN5QOJd5jWXshuEtFouImHvf8HoBEjGXGpC6DBO5LPijUt/PDHW3HQysvTZl +rwIDAQAB +-----END PUBLIC KEY-----""" + +INBOX = [] + + +@app.route("/.well-known/webfinger") +def webfinger(): + return json.dumps( + { + "subject": "acct:h4kor@mock_masto", + "aliases": [ + "http://mock_masto/@h4kor", + "http://mock_masto/users/h4kor", + ], + "links": [ + { + "rel": "http://webfinger.net/rel/profile-page", + "type": "text/html", + "href": "http://mock_masto/@h4kor", + }, + { + "rel": "self", + "type": "application/activity+json", + "href": "http://mock_masto/users/h4kor", + }, + { + "rel": "http://ostatus.org/schema/1.0/subscribe", + "template": "http://mock_masto/authorize_interaction?uri={uri}", + }, + { + "rel": "http://webfinger.net/rel/avatar", + "type": "image/png", + "href": "http://assets.mock_masto/accounts/avatars/000/082/056/original/a4be9944e3b03229.png", + }, + ], + } + ) + + +@app.route("/users/h4kor") +def actor(): + return json.dumps( + { + "@context": [ + "http://www.w3.org/ns/activitystreams", + "http://w3id.org/security/v1", + { + "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", + "toot": "http://joinmastodon.org/ns#", + "featured": {"@id": "toot:featured", "@type": "@id"}, + "featuredTags": {"@id": "toot:featuredTags", "@type": "@id"}, + "alsoKnownAs": {"@id": "as:alsoKnownAs", "@type": "@id"}, + "movedTo": {"@id": "as:movedTo", "@type": "@id"}, + "schema": "http://schema.org#", + "PropertyValue": "schema:PropertyValue", + "value": "schema:value", + "discoverable": "toot:discoverable", + "Device": "toot:Device", + "Ed25519Signature": "toot:Ed25519Signature", + "Ed25519Key": "toot:Ed25519Key", + "Curve25519Key": "toot:Curve25519Key", + "EncryptedMessage": "toot:EncryptedMessage", + "publicKeyBase64": "toot:publicKeyBase64", + "deviceId": "toot:deviceId", + "claim": {"@type": "@id", "@id": "toot:claim"}, + "fingerprintKey": {"@type": "@id", "@id": "toot:fingerprintKey"}, + "identityKey": {"@type": "@id", "@id": "toot:identityKey"}, + "devices": {"@type": "@id", "@id": "toot:devices"}, + "messageFranking": "toot:messageFranking", + "messageType": "toot:messageType", + "cipherText": "toot:cipherText", + "suspended": "toot:suspended", + "memorial": "toot:memorial", + "indexable": "toot:indexable", + "Hashtag": "as:Hashtag", + "focalPoint": {"@container": "@list", "@id": "toot:focalPoint"}, + }, + ], + "id": "http://mock_masto/users/h4kor", + "type": "Person", + "following": "http://mock_masto/users/h4kor/following", + "followers": "http://mock_masto/users/h4kor/followers", + "inbox": "http://mock_masto/users/h4kor/inbox", + "outbox": "http://mock_masto/users/h4kor/outbox", + "featured": "http://mock_masto/users/h4kor/collections/featured", + "featuredTags": "http://mock_masto/users/h4kor/collections/tags", + "preferredUsername": "h4kor", + "name": "Niko", + "summary": '
Teaching computers to do things with arguable efficiency.
he/him
#vegan #cooking #programming #politics #climate
', + "url": "http://mock_masto/@h4kor", + "manuallyApprovesFollowers": False, + "discoverable": True, + "indexable": False, + "published": "2018-08-16T00:00:00Z", + "memorial": False, + "devices": "http://mock_masto/users/h4kor/collections/devices", + "publicKey": { + "id": "http://mock_masto/users/h4kor#main-key", + "owner": "http://mock_masto/users/h4kor", + "publicKeyPem": PUB_KEY_PEM, + }, + "tag": [ + { + "type": "Hashtag", + "href": "http://mock_masto/tags/politics", + "name": "#politics", + }, + { + "type": "Hashtag", + "href": "http://mock_masto/tags/climate", + "name": "#climate", + }, + { + "type": "Hashtag", + "href": "http://mock_masto/tags/vegan", + "name": "#vegan", + }, + { + "type": "Hashtag", + "href": "http://mock_masto/tags/programming", + "name": "#programming", + }, + { + "type": "Hashtag", + "href": "http://mock_masto/tags/cooking", + "name": "#cooking", + }, + ], + "attachment": [ + { + "type": "PropertyValue", + "name": "Me", + "value": 'http://rerere.org', + }, + { + "type": "PropertyValue", + "name": "Blog", + "value": 'http://blog.libove.org', + }, + {"type": "PropertyValue", "name": "Location", "value": "Münster"}, + { + "type": "PropertyValue", + "name": "Current Project", + "value": 'http://git.libove.org/h4kor/owl-blogs', + }, + ], + "endpoints": {"sharedInbox": "http://mock_masto/inbox"}, + "icon": { + "type": "Image", + "mediaType": "image/png", + "url": "http://assets.mock_masto/accounts/avatars/000/082/056/original/a4be9944e3b03229.png", + }, + } + ) + + +@app.route("/users/h4kor/inbox") +def inbox(): + if request.method == "POST": + INBOX.append(request.get_json()) + return "" + + +if __name__ == "__main__": + app.run(debug=True, host="0.0.0.0", port="8000") diff --git a/e2e_tests/mock_masto/requirements.txt b/e2e_tests/mock_masto/requirements.txt new file mode 100644 index 0000000..a993b8d --- /dev/null +++ b/e2e_tests/mock_masto/requirements.txt @@ -0,0 +1 @@ +Flask==3.0.3 \ No newline at end of file diff --git a/e2e_tests/requirements.txt b/e2e_tests/requirements.txt new file mode 100644 index 0000000..df3c493 --- /dev/null +++ b/e2e_tests/requirements.txt @@ -0,0 +1,11 @@ +certifi==2024.2.2 +charset-normalizer==3.3.2 +exceptiongroup==1.2.1 +idna==3.7 +iniconfig==2.0.0 +packaging==24.0 +pluggy==1.5.0 +pytest==8.2.0 +requests==2.31.0 +tomli==2.0.1 +urllib3==2.2.1 diff --git a/e2e_tests/tests/__init__.py b/e2e_tests/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/e2e_tests/tests/fixtures.py b/e2e_tests/tests/fixtures.py new file mode 100644 index 0000000..d788cb4 --- /dev/null +++ b/e2e_tests/tests/fixtures.py @@ -0,0 +1 @@ +ACCT_NAME = "acct:blog@localhost:3000" diff --git a/e2e_tests/tests/test_activity_pub.py b/e2e_tests/tests/test_activity_pub.py new file mode 100644 index 0000000..6f9f746 --- /dev/null +++ b/e2e_tests/tests/test_activity_pub.py @@ -0,0 +1,24 @@ +import pytest + + +def test_actor(client, actor_url): + resp = client.get(actor_url, headers={"Content-Type": "application/activity+json"}) + assert resp.status_code == 200 + data = resp.json() + assert "id" in data + assert "type" in data + assert "inbox" in data + assert "outbox" in data + assert "followers" in data + assert "preferredUsername" in data + assert "publicKey" in data + assert len(data["publicKey"]) + + pubKey = data["publicKey"] + assert "id" in pubKey + assert "owner" in pubKey + assert "publicKeyPem" in pubKey + + assert pubKey["owner"] == data["id"] + assert pubKey["id"] != data["id"] + assert "-----BEGIN RSA PUBLIC KEY-----" in pubKey["publicKeyPem"] diff --git a/e2e_tests/tests/test_webfinger.py b/e2e_tests/tests/test_webfinger.py new file mode 100644 index 0000000..1119507 --- /dev/null +++ b/e2e_tests/tests/test_webfinger.py @@ -0,0 +1,27 @@ +import pytest +from .fixtures import ACCT_NAME + + +@pytest.mark.parametrize( + ["query", "status"], + [ + ["", 404], + ["?foo=bar", 404], + ["?resource=lol@bar.com", 404], + [f"?resource={ACCT_NAME}", 200], + ], +) +def test_webfinger_status(client, query, status): + resp = client.get("/.well-known/webfinger" + query) + assert resp.status_code == status + + +def test_webfinger(client): + resp = client.get(f"/.well-known/webfinger?resource={ACCT_NAME}") + assert resp.status_code == 200 + data = resp.json() + assert data["subject"] == ACCT_NAME + assert len(data["links"]) > 0 + self_link = [x for x in data["links"] if x["rel"] == "self"][0] + assert self_link["type"] == "application/activity+json" + assert "href" in self_link diff --git a/web/activity_pub_handler.go b/web/activity_pub_handler.go index 66a8809..5a2a236 100644 --- a/web/activity_pub_handler.go +++ b/web/activity_pub_handler.go @@ -6,9 +6,6 @@ import ( "net/http" "net/url" "owl-blogs/app" - "owl-blogs/app/repository" - "owl-blogs/config" - "owl-blogs/domain/model" vocab "github.com/go-ap/activitypub" "github.com/go-ap/jsonld" @@ -18,9 +15,9 @@ import ( ) type ActivityPubServer struct { - configRepo repository.ConfigRepository - apService *app.ActivityPubService - entryService *app.EntryService + siteConfigService *app.SiteConfigService + apService *app.ActivityPubService + entryService *app.EntryService } type WebfingerResponse struct { @@ -35,19 +32,17 @@ type WebfingerLink struct { Href string `json:"href"` } -func NewActivityPubServer(configRepo repository.ConfigRepository, entryService *app.EntryService, apService *app.ActivityPubService) *ActivityPubServer { +func NewActivityPubServer(siteConfigService *app.SiteConfigService, entryService *app.EntryService, apService *app.ActivityPubService) *ActivityPubServer { return &ActivityPubServer{ - configRepo: configRepo, - entryService: entryService, - apService: apService, + siteConfigService: siteConfigService, + entryService: entryService, + apService: apService, } } func (s *ActivityPubServer) HandleWebfinger(ctx *fiber.Ctx) error { - siteConfig := model.SiteConfig{} - apConfig := app.ActivityPubConfig{} - s.configRepo.Get(config.ACT_PUB_CONF_NAME, &apConfig) - s.configRepo.Get(config.SITE_CONFIG, &siteConfig) + siteConfig, _ := s.siteConfigService.GetSiteConfig() + apConfig, _ := s.apService.GetApConfig() domain, err := url.Parse(siteConfig.FullUrl) if err != nil { @@ -55,7 +50,9 @@ func (s *ActivityPubServer) HandleWebfinger(ctx *fiber.Ctx) error { } subject := ctx.Query("resource", "") - if subject != "acct:"+apConfig.PreferredUsername+"@"+domain.Host { + blogSubject := "acct:" + apConfig.PreferredUsername + "@" + domain.Host + slog.Info("webfinger request", "for", subject, "required", blogSubject) + if subject != blogSubject { return ctx.Status(404).JSON(nil) } @@ -83,10 +80,8 @@ func (s *ActivityPubServer) Router(router fiber.Router) { } func (s *ActivityPubServer) HandleActor(ctx *fiber.Ctx) error { - siteConfig := model.SiteConfig{} - apConfig := app.ActivityPubConfig{} - s.configRepo.Get(config.ACT_PUB_CONF_NAME, &apConfig) - s.configRepo.Get(config.SITE_CONFIG, &siteConfig) + siteConfig, _ := s.siteConfigService.GetSiteConfig() + apConfig, _ := s.apService.GetApConfig() actor := vocab.PersonNew(vocab.IRI(siteConfig.FullUrl + "/activitypub/actor")) actor.PreferredUsername = vocab.NaturalLanguageValues{{Value: vocab.Content(apConfig.PreferredUsername)}} @@ -110,10 +105,8 @@ func (s *ActivityPubServer) HandleActor(ctx *fiber.Ctx) error { } func (s *ActivityPubServer) HandleOutbox(ctx *fiber.Ctx) error { - siteConfig := model.SiteConfig{} - apConfig := app.ActivityPubConfig{} - s.configRepo.Get(config.ACT_PUB_CONF_NAME, &apConfig) - s.configRepo.Get(config.SITE_CONFIG, &siteConfig) + siteConfig, _ := s.siteConfigService.GetSiteConfig() + // apConfig, _ := s.apService.GetApConfig() entries, err := s.entryService.FindAllByType(nil, true, false) if err != nil { @@ -165,10 +158,8 @@ func (s *ActivityPubServer) processUndo(act *vocab.Activity) error { } func (s *ActivityPubServer) HandleInbox(ctx *fiber.Ctx) error { - siteConfig := model.SiteConfig{} - apConfig := app.ActivityPubConfig{} - s.configRepo.Get(config.ACT_PUB_CONF_NAME, &apConfig) - s.configRepo.Get(config.SITE_CONFIG, &siteConfig) + // siteConfig, _ := s.siteConfigService.GetSiteConfig() + // apConfig, _ := s.apService.GetApConfig() body := ctx.Request().Body() data, err := vocab.UnmarshalJSON(body) @@ -199,10 +190,8 @@ func (s *ActivityPubServer) HandleInbox(ctx *fiber.Ctx) error { } func (s *ActivityPubServer) HandleFollowers(ctx *fiber.Ctx) error { - siteConfig := model.SiteConfig{} - apConfig := app.ActivityPubConfig{} - s.configRepo.Get(config.ACT_PUB_CONF_NAME, &apConfig) - s.configRepo.Get(config.SITE_CONFIG, &siteConfig) + siteConfig, _ := s.siteConfigService.GetSiteConfig() + // apConfig, _ := s.apService.GetApConfig() fs, err := s.apService.AllFollowers() if err != nil { diff --git a/web/app.go b/web/app.go index 96e9e49..6d673f0 100644 --- a/web/app.go +++ b/web/app.go @@ -135,7 +135,7 @@ func NewWebApp( fiberApp.Get("/sitemap.xml", NewSiteMapHandler(entryService, siteConfigService).Handle) // ActivityPub - activityPubServer := NewActivityPubServer(configRepo, entryService, apService) + activityPubServer := NewActivityPubServer(siteConfigService, entryService, apService) configRegister.Register(config.ACT_PUB_CONF_NAME, &app.ActivityPubConfig{}) fiberApp.Get("/.well-known/webfinger", activityPubServer.HandleWebfinger) fiberApp.Route("/activitypub", activityPubServer.Router)