diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..0d19b3e
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,18 @@
+# See https://pre-commit.com for more information
+# See https://pre-commit.com/hooks.html for more hooks
+repos:
+- repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v2.1.0
+ hooks:
+ - id: trailing-whitespace
+ - id: end-of-file-fixer
+ - id: check-yaml
+ - id: check-merge-conflict
+ - id: check-added-large-files
+- repo: https://github.com/dnephin/pre-commit-golang
+ rev: master
+ hooks:
+ - id: go-vet
+ - id: go-fmt
+ - id: go-lint
+ - id: go-unit-tests
diff --git a/cmd/eksterd/main.go b/cmd/eksterd/main.go
index 8268efa..4f454bc 100644
--- a/cmd/eksterd/main.go
+++ b/cmd/eksterd/main.go
@@ -1,19 +1,5 @@
/*
- Microsub server
- Copyright (C) 2018 Peter Stuifzand
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see .
+Package main contains the main command for the Microsub server.
*/
package main
@@ -77,7 +63,7 @@ func WithAuth(handler http.Handler, b *memoryBackend) http.Handler {
return
}
- if token.Me != b.Me {
+ if token.Me != b.Me { // FIXME: Me should be part of the request
log.Printf("Missing \"me\" in token response: %#v\n", token)
http.Error(w, "Wrong me", 403)
return
diff --git a/cmd/eksterd/memory.go b/cmd/eksterd/memory.go
index e778ccc..bc49687 100644
--- a/cmd/eksterd/memory.go
+++ b/cmd/eksterd/memory.go
@@ -1,19 +1,20 @@
/*
- Microsub server
- Copyright (C) 2018 Peter Stuifzand
+Package main runs the microsub server
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
+Copyright (C) 2018 Peter Stuifzand
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
- You should have received a copy of the GNU General Public License
- along with this program. If not, see .
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
*/
package main
@@ -42,6 +43,7 @@ import (
"willnorris.com/go/microformats"
)
+// DefaultPrio is the priority value for new channels
const DefaultPrio = 9999999
type memoryBackend struct {
@@ -51,10 +53,10 @@ type memoryBackend struct {
Channels map[string]microsub.Channel
Feeds map[string][]microsub.Feed
Settings map[string]channelSetting
- NextUid int
+ NextUID int
- Me string
- TokenEndpoint string
+ Me string // FIXME: should be removed
+ TokenEndpoint string // FIXME: should be removed
AuthEnabled bool
ticker *time.Ticker
@@ -68,6 +70,7 @@ type channelSetting struct {
IncludeRegex string
}
+// Debug interface for easy of use in other packages
type Debug interface {
Debug()
}
@@ -176,7 +179,8 @@ func createMemoryBackend() {
backend.Channels[c.UID] = c
}
- backend.NextUid = 1000000
+ backend.NextUID = 1000000
+ // FIXME: can't be used in Backend
backend.Me = "https://example.com/"
backend.lock.Unlock()
@@ -443,24 +447,24 @@ func (b *memoryBackend) Search(query string) ([]microsub.Feed, error) {
}
defer resp.Body.Close()
- fetchUrl, err := url.Parse(u)
- md := microformats.Parse(resp.Body, fetchUrl)
+ fetchURL, err := url.Parse(u)
+ md := microformats.Parse(resp.Body, fetchURL)
if err != nil {
log.Printf("Error while fetching %s: %v\n", u, err)
continue
}
- feedResp, err := Fetch2(fetchUrl.String())
+ feedResp, err := Fetch2(fetchURL.String())
if err != nil {
- log.Printf("Error in fetch of %s - %v\n", fetchUrl, err)
+ log.Printf("Error in fetch of %s - %v\n", fetchURL, err)
continue
}
defer feedResp.Body.Close()
// TODO: Combine FeedHeader and FeedItems so we can use it here
- parsedFeed, err := fetch.FeedHeader(&fetch2{}, fetchUrl.String(), feedResp.Header.Get("Content-Type"), feedResp.Body)
+ parsedFeed, err := fetch.FeedHeader(&fetch2{}, fetchURL.String(), feedResp.Header.Get("Content-Type"), feedResp.Body)
if err != nil {
- log.Printf("Error in parse of %s - %v\n", fetchUrl, err)
+ log.Printf("Error in parse of %s - %v\n", fetchURL, err)
continue
}
@@ -706,7 +710,7 @@ func Fetch2(fetchURL string) (*http.Response, error) {
client := http.Client{}
resp, err := client.Do(req)
if err != nil {
- return nil, fmt.Errorf("fetch failed: %s", u, err)
+ return nil, fmt.Errorf("fetch failed: %s: %s", u, err)
}
defer resp.Body.Close()
@@ -724,10 +728,11 @@ func Fetch2(fetchURL string) (*http.Response, error) {
}
func (b *memoryBackend) createChannel(name string) microsub.Channel {
- uid := fmt.Sprintf("%012d", b.NextUid)
+ uid := fmt.Sprintf("%012d", b.NextUID)
channel := microsub.Channel{
- UID: uid,
- Name: name,
+ UID: uid,
+ Name: name,
+ Unread: microsub.Unread{microsub.UnreadCount, false, 0},
}
return channel
}
@@ -737,7 +742,7 @@ func (b *memoryBackend) setChannel(channel microsub.Channel) {
defer b.lock.Unlock()
b.Channels[channel.UID] = channel
b.Feeds[channel.UID] = []microsub.Feed{}
- b.NextUid++
+ b.NextUID++
}
func updateChannelInRedis(conn redis.Conn, uid string, prio int) {
diff --git a/cmd/eksterd/timeline.go b/cmd/eksterd/timeline.go
index 2fff6ca..c1c5768 100644
--- a/cmd/eksterd/timeline.go
+++ b/cmd/eksterd/timeline.go
@@ -291,8 +291,10 @@ func (timeline *redisStreamTimeline) Items(before, after string) (microsub.Timel
}
return microsub.Timeline{
- Items: items,
- Paging: microsub.Pagination{},
+ Items: items,
+ Paging: microsub.Pagination{
+ After: items[len(items)-1].ID,
+ },
}, nil
}
diff --git a/pkg/client/requests.go b/pkg/client/requests.go
index 48a918e..6afdcb5 100644
--- a/pkg/client/requests.go
+++ b/pkg/client/requests.go
@@ -13,6 +13,7 @@ import (
"p83.nl/go/ekster/pkg/microsub"
)
+// Client is a HTTP client for Microsub
type Client struct {
Me *url.URL
MicrosubEndpoint *url.URL
@@ -120,6 +121,7 @@ func (c *Client) microsubPostFormRequest(action string, args map[string]string,
return res, err
}
+// ChannelsGetList gets the channels from a Microsub server
func (c *Client) ChannelsGetList() ([]microsub.Channel, error) {
args := make(map[string]string)
res, err := c.microsubGetRequest("channels", args)
@@ -146,6 +148,7 @@ func (c *Client) ChannelsGetList() ([]microsub.Channel, error) {
return channels.Channels, err
}
+// TimelineGet gets a timeline from a Microsub server
func (c *Client) TimelineGet(before, after, channel string) (microsub.Timeline, error) {
args := make(map[string]string)
args["after"] = after
@@ -172,6 +175,7 @@ func (c *Client) TimelineGet(before, after, channel string) (microsub.Timeline,
return timeline, nil
}
+// PreviewURL gets a Timeline for a url from a Microsub server
func (c *Client) PreviewURL(url string) (microsub.Timeline, error) {
args := make(map[string]string)
args["url"] = url
@@ -338,7 +342,3 @@ func (c *Client) MarkRead(channel string, uids []string) error {
res.Body.Close()
return nil
}
-
-func (c *Client) AddEventListener(el microsub.EventListener) error {
- panic("implement me")
-}
diff --git a/pkg/microsub/protocol.go b/pkg/microsub/protocol.go
index f62ec18..7b96e58 100644
--- a/pkg/microsub/protocol.go
+++ b/pkg/microsub/protocol.go
@@ -1,21 +1,3 @@
-/*
- Microsub server
- Copyright (C) 2018 Peter Stuifzand
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see .
-*/
-
// Package microsub describes the protocol methods of the Microsub protocol
package microsub
@@ -34,11 +16,13 @@ import (
block / unblock
*/
+// Constants for Unread
const (
UnreadBool = 0
UnreadCount = 1
)
+// Unread is a special int/bool value for the JSON response
type Unread struct {
Type int
Unread bool
diff --git a/pkg/microsub/protocol_test.go b/pkg/microsub/protocol_test.go
new file mode 100644
index 0000000..1a911e2
--- /dev/null
+++ b/pkg/microsub/protocol_test.go
@@ -0,0 +1,84 @@
+package microsub
+
+import (
+ "encoding/json"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func Test_UnmarshalUnreadBool(t *testing.T) {
+ var x Unread
+ err := json.Unmarshal([]byte("false"), &x)
+ if assert.NoError(t, err) {
+ assert.Equal(t, UnreadBool, x.Type)
+ assert.False(t, x.Unread)
+ }
+}
+
+func Test_UnmarshalUnreadBoolTrue(t *testing.T) {
+ var x Unread
+ err := json.Unmarshal([]byte("true"), &x)
+ if assert.NoError(t, err) {
+ assert.Equal(t, UnreadBool, x.Type)
+ assert.True(t, x.Unread)
+ }
+}
+
+func Test_UnmarshalUnreadIntZero(t *testing.T) {
+ var x Unread
+ err := json.Unmarshal([]byte("0"), &x)
+ if assert.NoError(t, err) {
+ assert.Equal(t, UnreadCount, x.Type)
+ assert.Equal(t, 0, x.UnreadCount)
+ }
+}
+
+func Test_UnmarshalUnreadIntNonZero(t *testing.T) {
+ var x Unread
+ err := json.Unmarshal([]byte("209449"), &x)
+ if assert.NoError(t, err) {
+ assert.Equal(t, UnreadCount, x.Type)
+ assert.Equal(t, 209449, x.UnreadCount)
+ }
+}
+
+func Test_MarshalUnreadEmpty(t *testing.T) {
+ x := Unread{}
+ bytes, err := json.Marshal(x)
+ if assert.NoError(t, err) {
+ assert.Equal(t, "false", string(bytes))
+ }
+}
+
+func Test_MarshalUnreadBoolFalse(t *testing.T) {
+ x := Unread{Type: UnreadBool, Unread: false}
+ bytes, err := json.Marshal(x)
+ if assert.NoError(t, err) {
+ assert.Equal(t, "false", string(bytes))
+ }
+}
+
+func Test_MarshalUnreadBoolTrue(t *testing.T) {
+ x := Unread{Type: UnreadBool, Unread: true}
+ bytes, err := json.Marshal(x)
+ if assert.NoError(t, err) {
+ assert.Equal(t, "true", string(bytes))
+ }
+}
+
+func Test_MarshalUnreadIntZero(t *testing.T) {
+ x := Unread{Type: UnreadCount, UnreadCount: 0}
+ bytes, err := json.Marshal(x)
+ if assert.NoError(t, err) {
+ assert.Equal(t, "0", string(bytes))
+ }
+}
+
+func Test_MarshalUnreadIntNonZero(t *testing.T) {
+ x := Unread{Type: UnreadCount, UnreadCount: 1884844}
+ bytes, err := json.Marshal(x)
+ if assert.NoError(t, err) {
+ assert.Equal(t, "1884844", string(bytes))
+ }
+}
diff --git a/pkg/server/microsub.go b/pkg/server/microsub.go
index 9133f1b..02abd68 100644
--- a/pkg/server/microsub.go
+++ b/pkg/server/microsub.go
@@ -1,19 +1,6 @@
/*
- ekster - microsub server
- Copyright (C) 2018 Peter Stuifzand
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see .
+Package server contains the microsub server itself. It implements http.Handler.
+It follows the spec at https://indieweb.org/Microsub-spec.
*/
package server
@@ -31,6 +18,7 @@ var (
entryRegex = regexp.MustCompile("^entry\\[\\d+\\]$")
)
+// Constants used for the responses
const (
OutputContentType = "application/json; charset=utf-8"
)
@@ -51,11 +39,14 @@ func respondJSON(w http.ResponseWriter, value interface{}) {
}
}
+// NewMicrosubHandler is the main entry point for the microsub server
+// It returns a handler for HTTP and a broker that will send events.
func NewMicrosubHandler(backend microsub.Microsub) (http.Handler, *Broker) {
broker := NewServer()
return µsubHandler{backend, broker}, broker
}
+// Methods required by http.Handler
func (h *microsubHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
diff --git a/pkg/server/microsub_test.go b/pkg/server/microsub_test.go
index 4be0dd5..57bbb41 100644
--- a/pkg/server/microsub_test.go
+++ b/pkg/server/microsub_test.go
@@ -14,7 +14,7 @@ import (
func createServerClient() (*httptest.Server, *client.Client) {
backend := &NullBackend{}
- handler := NewMicrosubHandler(backend)
+ handler, _ := NewMicrosubHandler(backend)
server := httptest.NewServer(handler)
diff --git a/pkg/server/null.go b/pkg/server/null.go
index 9e3f6a3..6e347bd1 100644
--- a/pkg/server/null.go
+++ b/pkg/server/null.go
@@ -25,10 +25,6 @@ import (
type NullBackend struct {
}
-func (b *NullBackend) AddEventListener(el microsub.EventListener) error {
- panic("implement me")
-}
-
// ChannelsGetList gets no channels
func (b *NullBackend) ChannelsGetList() ([]microsub.Channel, error) {
return []microsub.Channel{
@@ -66,26 +62,31 @@ func (b *NullBackend) TimelineGet(before, after, channel string) (microsub.Timel
}, nil
}
+// FollowGetList implements the follow list command
func (b *NullBackend) FollowGetList(uid string) ([]microsub.Feed, error) {
return []microsub.Feed{
{Name: "test", Type: "feed", URL: "https://example.com/"},
}, nil
}
+// FollowURL follows a new url
func (b *NullBackend) FollowURL(uid string, url string) (microsub.Feed, error) {
return microsub.Feed{Type: "feed", URL: url}, nil
}
+// UnfollowURL unfollows a url
func (b *NullBackend) UnfollowURL(uid string, url string) error {
return nil
}
+// Search search for a query and return an example list of feeds
func (b *NullBackend) Search(query string) ([]microsub.Feed, error) {
return []microsub.Feed{
{"feed", "https://example.com/", "Example", "test.jpg", "test", microsub.Card{}},
}, nil
}
+// PreviewURL shows an empty feed
func (b *NullBackend) PreviewURL(url string) (microsub.Timeline, error) {
return microsub.Timeline{
Paging: microsub.Pagination{},
@@ -93,6 +94,7 @@ func (b *NullBackend) PreviewURL(url string) (microsub.Timeline, error) {
}, nil
}
+// MarkRead marks no items as read
func (b *NullBackend) MarkRead(channel string, uids []string) error {
return nil
}