Compare commits

..

No commits in common. "main" and "activity_pub" have entirely different histories.

16 changed files with 26 additions and 264 deletions

View File

@ -1,45 +0,0 @@
on:
release:
types: [created]
permissions:
contents: write
packages: write
jobs:
release-linux-amd64:
name: release linux/amd64
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.22'
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Build
run: go build -v ./...
- name: Test
run: go test -v ./...
- name: E2E Test
run: |
cd e2e_tests
docker compose -f docker-compose.ci.yml up -d
pip install -r requirements.txt
pytest
- name: Build Release
env:
CGO_ENABLED: 1
GOOS: linux
GOARCH: amd64
GH_TOKEN: ${{ github.token }}
run: |
go build -o owl-linux-amd64 ./cmd/owl
gh release upload ${{github.event.release.tag_name}} owl-linux-amd64

View File

@ -1,38 +0,0 @@
# This workflow will build a golang project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go
name: Go
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.22'
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Build
run: go build -v ./...
- name: Test
run: go test -v ./...
- name: E2E Test
run: |
cd e2e_tests
docker compose -f docker-compose.ci.yml up -d
pip install -r requirements.txt
pytest

View File

@ -2,9 +2,17 @@
# Owl Blogs
Owl-blogs is a blogging software focused on simplicity with IndieWeb and Fediverse support.
A simple web server for blogs generated from Markdown files.
# Usage
**_This project is not yet stable. Expect frequent breaking changes! Only use this if you are willing to regularly adjust your project accordingly._**
## Build
```
CGO_ENABLED=1 go build -o owl ./cmd/owl
```
## Run
@ -26,36 +34,4 @@ To retrieve a list of all commands run:
```
owl -h
```
# Development
## Build
```
CGO_ENABLED=1 go build -o owl ./cmd/owl
```
For development with live reload use `air` ([has to be install first](https://github.com/cosmtrek/air))
## Tests
The project has two test suites; "unit tests" written in go and "end-to-end tests" written in python.
### Unit Tests
```
go test ./...
```
### End-to-End tests
- Start the docker compose setup in the `e2e_tests` directory.
- Install the python dependencies into a virtualenv
```
cd e2e_tests
python3 -m venv venv
. venv/bin/activate
pip install -r requirements.txt
```
- Run the e2e_tests with `pytest`
```

View File

@ -498,41 +498,7 @@ func (svc *ActivityPubService) NotifyEntryCreated(entry model.Entry) {
}
func (svc *ActivityPubService) NotifyEntryUpdated(entry model.Entry) {
slog.Info("Processing Entry Create for ActivityPub")
followers, err := svc.AllFollowers()
if err != nil {
slog.Error("Cannot retrieve followers")
}
object, err := svc.entryToObject(entry)
if err != nil {
slog.Error("Cannot convert object", "err", err)
}
update := vocab.UpdateNew(object.ID, object)
update.Actor = object.AttributedTo
update.To = object.To
update.Published = object.Published
data, err := jsonld.WithContext(
jsonld.IRI(vocab.ActivityBaseURI),
jsonld.Context{
jsonld.ContextElement{
Term: "toot",
IRI: jsonld.IRI("http://joinmastodon.org/ns#"),
},
},
).Marshal(update)
if err != nil {
slog.Error("marshalling error", "err", err)
}
for _, follower := range followers {
actor, err := svc.GetActor(follower)
if err != nil {
slog.Error("Unable to retrieve follower actor", "err", err)
}
svc.sendObject(actor, data)
}
}
func (svc *ActivityPubService) NotifyEntryDeleted(entry model.Entry) {
@ -577,13 +543,6 @@ func (svc *ActivityPubService) entryToObject(entry model.Entry) (vocab.Object, e
if imageEntry, ok := entry.(*entrytypes.Image); ok {
return svc.imageToObject(imageEntry), nil
}
if articleEntry, ok := entry.(*entrytypes.Article); ok {
return svc.articleToObject(articleEntry), nil
}
if recipeEntry, ok := entry.(*entrytypes.Recipe); ok {
return svc.recipeToObject(recipeEntry), nil
}
slog.Warn("entry type not yet supported for activity pub")
return vocab.Object{}, errors.New("entry type not supported")
}
@ -636,14 +595,11 @@ func (svc *ActivityPubService) imageToObject(imageEntry *entrytypes.Image) vocab
Type: vocab.DocumentType,
MediaType: vocab.MimeType(binaryFile.Mime()),
URL: vocab.ID(fullImageUrl),
Name: vocab.NaturalLanguageValues{
{Value: vocab.Content(content)},
},
})
image := vocab.Image{
image := vocab.Note{
ID: vocab.ID(imageEntry.FullUrl(siteCfg)),
Type: "Image",
Type: "Note",
To: vocab.ItemCollection{
vocab.PublicNS,
vocab.IRI(svc.FollowersUrl()),
@ -654,7 +610,7 @@ func (svc *ActivityPubService) imageToObject(imageEntry *entrytypes.Image) vocab
{Value: vocab.Content(imageEntry.Title())},
},
Content: vocab.NaturalLanguageValues{
{Value: vocab.Content(imageEntry.Title() + "<br><br>" + string(content))},
{Value: vocab.Content(content)},
},
Attachment: attachments,
// Tag: tags,
@ -662,51 +618,3 @@ func (svc *ActivityPubService) imageToObject(imageEntry *entrytypes.Image) vocab
return image
}
func (svc *ActivityPubService) articleToObject(articleEntry *entrytypes.Article) vocab.Object {
siteCfg, _ := svc.siteConfigServcie.GetSiteConfig()
content := articleEntry.Content()
image := vocab.Article{
ID: vocab.ID(articleEntry.FullUrl(siteCfg)),
Type: "Article",
To: vocab.ItemCollection{
vocab.PublicNS,
vocab.IRI(svc.FollowersUrl()),
},
Published: *articleEntry.PublishedAt(),
AttributedTo: vocab.ID(svc.ActorUrl()),
Name: vocab.NaturalLanguageValues{
{Value: vocab.Content(articleEntry.Title())},
},
Content: vocab.NaturalLanguageValues{
{Value: vocab.Content(string(content))},
},
}
return image
}
func (svc *ActivityPubService) recipeToObject(recipeEntry *entrytypes.Recipe) vocab.Object {
siteCfg, _ := svc.siteConfigServcie.GetSiteConfig()
content := recipeEntry.Content()
image := vocab.Article{
ID: vocab.ID(recipeEntry.FullUrl(siteCfg)),
Type: "Article",
To: vocab.ItemCollection{
vocab.PublicNS,
vocab.IRI(svc.FollowersUrl()),
},
Published: *recipeEntry.PublishedAt(),
AttributedTo: vocab.ID(svc.ActorUrl()),
Name: vocab.NaturalLanguageValues{
{Value: vocab.Content(recipeEntry.Title())},
},
Content: vocab.NaturalLanguageValues{
{Value: vocab.Content(string(content))},
},
}
return image
}

View File

@ -48,13 +48,7 @@ func (s *EntryService) Create(entry model.Entry) error {
if err != nil {
return err
}
// only notify if the publishing date is set
// otherwise this is a draft.
// listeners might publish the entry to other services/platforms
// this should only happen for publshed content
if entry.PublishedAt() != nil && !entry.PublishedAt().IsZero() {
s.Bus.NotifyCreated(entry)
}
s.Bus.NotifyCreated(entry)
return nil
}
@ -63,13 +57,7 @@ func (s *EntryService) Update(entry model.Entry) error {
if err != nil {
return err
}
// only notify if the publishing date is set
// otherwise this is a draft.
// listeners might publish the entry to other services/platforms
// this should only happen for publshed content
if entry.PublishedAt() != nil && !entry.PublishedAt().IsZero() {
s.Bus.NotifyUpdated(entry)
}
s.Bus.NotifyUpdated(entry)
return nil
}
@ -78,9 +66,6 @@ func (s *EntryService) Delete(entry model.Entry) error {
if err != nil {
return err
}
// deletes should always be notfied
// a published entry might be converted to a draft before deletion
// omitting the deletion in this case would prevent deletion on other platforms
s.Bus.NotifyDeleted(entry)
return nil
}

View File

@ -14,9 +14,7 @@ func setupService() *app.EntryService {
register := app.NewEntryTypeRegistry()
register.Register(&test.MockEntry{})
repo := infra.NewEntryRepository(db, register)
cfgRepo := infra.NewConfigRepo(db)
cfgService := app.NewSiteConfigService(cfgRepo)
service := app.NewEntryService(repo, cfgService, app.NewEventBus())
service := app.NewEntryService(repo, nil, app.NewEventBus())
return service
}

View File

@ -25,6 +25,7 @@ func (svc *SiteConfigService) defaultConfig() model.SiteConfig {
return model.SiteConfig{
Title: "My Owl-Blog",
SubTitle: "A freshly created blog",
HeaderColor: "#efc48c",
PrimaryColor: "#d37f12",
AuthorName: "",
Me: []model.MeLinks{},

View File

@ -92,6 +92,7 @@ var importCmd = &cobra.Command{
}
v2Config.Title = v1Config.Title
v2Config.SubTitle = v1Config.SubTitle
v2Config.HeaderColor = v1Config.HeaderColor
v2Config.AuthorName = v1Config.AuthorName
v2Config.Me = mes
v2Config.Lists = lists

View File

@ -22,6 +22,7 @@ type MenuItem struct {
type SiteConfig struct {
Title string
SubTitle string
HeaderColor string
PrimaryColor string
AuthorName string
Me []MeLinks

View File

@ -1,12 +0,0 @@
services:
web:
build:
context: ../
dockerfile: Dockerfile
command: web
ports:
- "3000:3000"
mock_masto:
build: mock_masto
ports:
- 8000:8000

View File

@ -1,17 +1,11 @@
certifi==2024.2.2
cffi==1.16.0
charset-normalizer==3.3.2
cryptography==42.0.7
exceptiongroup==1.2.1
http-message-signatures==0.5.0
http_sfv==0.9.9
idna==3.7
iniconfig==2.0.0
packaging==24.0
pluggy==1.5.0
pycparser==2.22
pytest==8.2.0
requests==2.31.0
tomli==2.0.1
typing_extensions==4.11.0
urllib3==2.2.1

View File

@ -57,7 +57,6 @@ def test_following(client, inbox_url, followers_url, actor_url):
def test_unfollow(client, inbox_url, followers_url, actor_url):
ensure_follow(client, inbox_url, actor_url)
sleep(0.5)
with msg_inc(1):
req = sign(
"POST",

View File

@ -13,12 +13,7 @@
<link rel="alternate" type="application/rss+xml" title="RSS" href="/index.xml">
<link rel='stylesheet' href='/static/owl.css'>
<link rel='stylesheet' href='/static/style.css'>
<style>
:root {
--primary: {{.SiteConfig.PrimaryColor}};
}
</style>
<link rel='stylesheet' href='/static/style.css'>
{{ .SiteConfig.HtmlHeadExtra }}
</head>
<body>
@ -72,10 +67,6 @@
<div>
{{ .SiteConfig.FooterExtra}}
</div>
<div style="margin-top:var(--s2);">
powered by <i><a href="https://github.com/H4kor/owl-blogs" target="_blank">owl-blogs</a></i>
</a>
</footer>
</body>
</html>

View File

@ -14,6 +14,9 @@
<label for="SubTitle">SubTitle</label>
<input type="text" name="SubTitle" id="SubTitle" value="{{.SubTitle}}"/>
<label for="HeaderColor">HeaderColor</label>
<input type="color" name="HeaderColor" id="HeaderColor" value="{{.HeaderColor}}"/>
<label for="PrimaryColor">PrimaryColor</label>
<input type="color" name="PrimaryColor" id="PrimaryColor" value="{{.PrimaryColor}}"/>

View File

@ -39,8 +39,7 @@ func NewWebApp(
apService *app.ActivityPubService,
) *WebApp {
fiberApp := fiber.New(fiber.Config{
BodyLimit: 50 * 1024 * 1024, // 50MB in bytes
DisableStartupMessage: true,
BodyLimit: 50 * 1024 * 1024, // 50MB in bytes
})
fiberApp.Use(middleware.NewUserMiddleware(authorService).Handle)

View File

@ -38,6 +38,7 @@ func (h *SiteConfigHandler) HandlePost(c *fiber.Ctx) error {
siteConfig.Title = c.FormValue("Title")
siteConfig.SubTitle = c.FormValue("SubTitle")
siteConfig.HeaderColor = c.FormValue("HeaderColor")
siteConfig.PrimaryColor = c.FormValue("PrimaryColor")
siteConfig.AuthorName = c.FormValue("AuthorName")
siteConfig.AvatarUrl = c.FormValue("AvatarUrl")