Compare commits

..

8 Commits

Author SHA1 Message Date
5dcb64cc28
Handle error from ProcessContent in HubBackend
All checks were successful
the build was successful
2018-12-09 17:59:06 +01:00
d93530a237
Remove os.GetEnv call 2018-12-09 17:57:16 +01:00
47661b73f1
Add test for mark_read and unknown POST action 2018-12-09 17:50:29 +01:00
226a35fb43
Add bad request status code for wrong action 2018-12-09 17:46:47 +01:00
7d61179ee1
Add microsub server tests 2018-12-09 17:40:33 +01:00
a2db7069e7
Separate App from main
- extract env vars
- add command line arguments
- move null backend to server package
2018-12-09 17:39:36 +01:00
f21a68a56d
Add -headless option 2018-12-09 16:30:02 +01:00
d997fa8ca9
Fix null result in output 2018-12-09 16:26:44 +01:00
8 changed files with 297 additions and 68 deletions

View File

@ -39,6 +39,7 @@ import (
type mainHandler struct { type mainHandler struct {
Backend *memoryBackend Backend *memoryBackend
BaseURL string
TemplateDir string TemplateDir string
} }
@ -101,13 +102,10 @@ type authRequest struct {
AccessToken string `redis:"access_token"` AccessToken string `redis:"access_token"`
} }
func newMainHandler(backend *memoryBackend) (*mainHandler, error) { func newMainHandler(backend *memoryBackend, baseURL, templateDir string) (*mainHandler, error) {
h := &mainHandler{Backend: backend} h := &mainHandler{Backend: backend}
templateDir := os.Getenv("EKSTER_TEMPLATES") h.BaseURL = baseURL
if templateDir == "" {
return nil, fmt.Errorf("missing env var EKSTER_TEMPLATES")
}
templateDir = strings.TrimRight(templateDir, "/") templateDir = strings.TrimRight(templateDir, "/")
h.TemplateDir = templateDir h.TemplateDir = templateDir
@ -211,7 +209,7 @@ func isLoggedIn(backend *memoryBackend, sess *session) bool {
return false return false
} }
if !authEnabled { if !backend.AuthEnabled {
return true return true
} }
@ -297,7 +295,7 @@ func (h *mainHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var page indexPage var page indexPage
page.Session = sess page.Session = sess
page.Baseurl = strings.TrimRight(os.Getenv("EKSTER_BASEURL"), "/") page.Baseurl = strings.TrimRight(h.BaseURL, "/")
err = h.renderTemplate(w, "index.html", page) err = h.renderTemplate(w, "index.html", page)
if err != nil { if err != nil {
@ -517,7 +515,7 @@ func (h *mainHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
log.Println(authURL) log.Println(authURL)
state := util.RandStringBytes(16) state := util.RandStringBytes(16)
redirectURI := fmt.Sprintf("%s/session/callback", os.Getenv("EKSTER_BASEURL")) redirectURI := fmt.Sprintf("%s/session/callback", h.BaseURL)
sess, err := loadSession(sessionVar, conn) sess, err := loadSession(sessionVar, conn)

View File

@ -48,6 +48,7 @@ type HubBackend interface {
type hubIncomingBackend struct { type hubIncomingBackend struct {
backend *memoryBackend backend *memoryBackend
baseURL string
} }
func (h *hubIncomingBackend) GetSecret(id int64) string { func (h *hubIncomingBackend) GetSecret(id int64) string {
@ -85,7 +86,7 @@ func (h *hubIncomingBackend) CreateFeed(topic string, channel string) (int64, er
log.Printf("WebSub Hub URL found for topic=%s hub=%s\n", topic, hubURL) log.Printf("WebSub Hub URL found for topic=%s hub=%s\n", topic, hubURL)
} }
callbackURL := fmt.Sprintf("%s/incoming/%d", os.Getenv("EKSTER_BASEURL"), id) callbackURL := fmt.Sprintf("%s/incoming/%d", h.baseURL, id)
if err == nil && hubURL != "" { if err == nil && hubURL != "" {
args := redis.Args{}.Add(fmt.Sprintf("feed:%d", id), "hub", hubURL, "callback", callbackURL) args := redis.Args{}.Add(fmt.Sprintf("feed:%d", id), "hub", hubURL, "callback", callbackURL)
@ -112,8 +113,11 @@ func (h *hubIncomingBackend) UpdateFeed(feedID int64, contentType string, body i
return err return err
} }
log.Printf("updating feed %d - %s %s\n", feedID, u, channel) log.Printf("Updating feed %d - %s %s\n", feedID, u, channel)
h.backend.ProcessContent(channel, u, contentType, body) err = h.backend.ProcessContent(channel, u, contentType, body)
if err != nil {
log.Printf("Error while updating content for channel %s: %s", channel, err)
}
return err return err
} }

View File

@ -28,7 +28,6 @@ import (
"github.com/gomodule/redigo/redis" "github.com/gomodule/redigo/redis"
"p83.nl/go/ekster/pkg/auth" "p83.nl/go/ekster/pkg/auth"
"p83.nl/go/ekster/pkg/microsub"
"p83.nl/go/ekster/pkg/server" "p83.nl/go/ekster/pkg/server"
) )
@ -36,18 +35,21 @@ const (
ClientID string = "https://p83.nl/microsub-client" ClientID string = "https://p83.nl/microsub-client"
) )
type AppOptions struct {
Port int
AuthEnabled bool
Headless bool
RedisServer string
BaseURL string
TemplateDir string
}
var ( var (
pool *redis.Pool pool *redis.Pool
port int
authEnabled bool
redisServer = flag.String("redis", "redis:6379", "")
) )
func init() { func init() {
log.SetFlags(log.Lshortfile | log.Ldate | log.Ltime) log.SetFlags(log.Lshortfile | log.Ldate | log.Ltime)
flag.IntVar(&port, "port", 80, "port for serving api")
flag.BoolVar(&authEnabled, "auth", true, "use auth")
} }
func newPool(addr string) *redis.Pool { func newPool(addr string) *redis.Pool {
@ -85,18 +87,93 @@ func WithAuth(handler http.Handler, b *memoryBackend) http.Handler {
}) })
} }
type App struct {
options AppOptions
backend *memoryBackend
hubBackend *hubIncomingBackend
}
func (app *App) Run() {
app.backend.run()
app.hubBackend.run()
log.Printf("Listening on port %d\n", app.options.Port)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", app.options.Port), nil))
}
func NewApp(options AppOptions) *App {
app := &App{
options: options,
}
var backend *memoryBackend
app.backend = loadMemoryBackend()
app.backend.AuthEnabled = options.AuthEnabled
app.hubBackend = &hubIncomingBackend{app.backend, options.BaseURL}
http.Handle("/micropub", &micropubHandler{
Backend: app.backend,
})
handler := server.NewMicrosubHandler(app.backend)
if options.AuthEnabled {
handler = WithAuth(handler, app.backend)
}
http.Handle("/microsub", handler)
http.Handle("/incoming/", &incomingHandler{
Backend: app.hubBackend,
})
if !options.Headless {
handler, err := newMainHandler(backend, options.BaseURL, options.TemplateDir)
if err != nil {
log.Fatal(err)
}
http.Handle("/", handler)
}
return app
}
func main() { func main() {
log.Println("eksterd - microsub server") log.Println("eksterd - microsub server")
var options AppOptions
flag.IntVar(&options.Port, "port", 80, "port for serving api")
flag.BoolVar(&options.AuthEnabled, "auth", true, "use auth")
flag.BoolVar(&options.Headless, "headless", false, "disable frontend")
flag.StringVar(&options.RedisServer, "redis", "redis:6379", "redis server")
flag.StringVar(&options.BaseURL, "baseurl", "", "http server baseurl")
flag.StringVar(&options.TemplateDir, "templates", "./templates", "template directory")
flag.Parse() flag.Parse()
if authEnabled { if options.AuthEnabled {
log.Println("Using auth") log.Println("Using auth")
} else { } else {
log.Println("Authentication disabled") log.Println("Authentication disabled")
} }
if _, e := os.LookupEnv("EKSTER_BASEURL"); !e { if options.BaseURL == "" {
log.Fatal("EKSTER_BASEURL environment variable not found, please set with external url: https://example.com") if envVar, e := os.LookupEnv("EKSTER_BASEURL"); e {
options.BaseURL = envVar
} else {
log.Fatal("EKSTER_BASEURL environment variable not found, please set with external url, -baseurl url option")
}
}
if options.TemplateDir == "" {
if envVar, e := os.LookupEnv("EKSTER_TEMPLATES"); e {
options.TemplateDir = envVar
} else {
log.Fatal("EKSTER_TEMPLATES environment variable not found, use env var or -templates dir option")
}
} }
createBackend := false createBackend := false
@ -108,13 +185,10 @@ func main() {
} }
} }
pool = newPool(*redisServer)
var backend microsub.Microsub
if createBackend { if createBackend {
backend = createMemoryBackend() createMemoryBackend()
// TODO(peter): automatically gather this information from login or otherwise
log.Println(`Config file "backend.json" is created in the current directory.`) log.Println(`Config file "backend.json" is created in the current directory.`)
log.Println(`Update "Me" variable to your website address "https://example.com/"`) log.Println(`Update "Me" variable to your website address "https://example.com/"`)
log.Println(`Update "TokenEndpoint" variable to the address of your token endpoint "https://example.com/token"`) log.Println(`Update "TokenEndpoint" variable to the address of your token endpoint "https://example.com/token"`)
@ -122,33 +196,7 @@ func main() {
return return
} }
backend = loadMemoryBackend() pool = newPool(options.RedisServer)
hubBackend := hubIncomingBackend{backend.(*memoryBackend)} NewApp(options).Run()
http.Handle("/micropub", &micropubHandler{
Backend: backend.(*memoryBackend),
})
handler := server.NewMicrosubHandler(backend)
if authEnabled {
handler = WithAuth(handler, backend.(*memoryBackend))
}
http.Handle("/microsub", handler)
http.Handle("/incoming/", &incomingHandler{
Backend: &hubBackend,
})
handler, err := newMainHandler(backend.(*memoryBackend))
if err != nil {
log.Fatal(err)
}
http.Handle("/", handler)
backend.(*memoryBackend).run()
hubBackend.run()
log.Printf("Listening on port %d\n", port)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil))
} }

View File

@ -54,6 +54,7 @@ type memoryBackend struct {
Me string Me string
TokenEndpoint string TokenEndpoint string
AuthEnabled bool
ticker *time.Ticker ticker *time.Ticker
quit chan struct{} quit chan struct{}
@ -141,7 +142,7 @@ func (b *memoryBackend) save() {
jw.Encode(b) jw.Encode(b)
} }
func loadMemoryBackend() microsub.Microsub { func loadMemoryBackend() *memoryBackend {
backend := &memoryBackend{} backend := &memoryBackend{}
err := backend.load() err := backend.load()
if err != nil { if err != nil {
@ -153,7 +154,7 @@ func loadMemoryBackend() microsub.Microsub {
return backend return backend
} }
func createMemoryBackend() microsub.Microsub { func createMemoryBackend() {
backend := memoryBackend{} backend := memoryBackend{}
backend.lock.Lock() backend.lock.Lock()
@ -174,7 +175,6 @@ func createMemoryBackend() microsub.Microsub {
backend.lock.Unlock() backend.lock.Unlock()
backend.save() backend.save()
return &backend
} }
// ChannelsGetList gets channels // ChannelsGetList gets channels
@ -475,7 +475,8 @@ func getPossibleURLs(query string) []string {
func (b *memoryBackend) Search(query string) ([]microsub.Feed, error) { func (b *memoryBackend) Search(query string) ([]microsub.Feed, error) {
urls := getPossibleURLs(query) urls := getPossibleURLs(query)
var feeds []microsub.Feed // needs to be like this, because we get a null result otherwise in the json output
feeds := []microsub.Feed{}
for _, u := range urls { for _, u := range urls {
log.Println(u) log.Println(u)

View File

@ -324,13 +324,14 @@ func (c *Client) Search(query string) ([]microsub.Feed, error) {
func (c *Client) MarkRead(channel string, uids []string) error { func (c *Client) MarkRead(channel string, uids []string) error {
args := make(map[string]string) args := make(map[string]string)
args["channel"] = channel args["channel"] = channel
args["method"] = "mark_read"
data := url.Values{} data := url.Values{}
for _, uid := range uids { for _, uid := range uids {
data.Add("entry[]", uid) data.Add("entry[]", uid)
} }
res, err := c.microsubPostFormRequest("mark_read", args, data) res, err := c.microsubPostFormRequest("timeline", args, data)
if err != nil { if err != nil {
return err return err
} }

View File

@ -108,7 +108,7 @@ func (h *microsubHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
cons := newConsumer(conn) cons := newConsumer(conn)
h.backend.AddEventListener(cons) h.backend.AddEventListener(cons)
} else { } else {
http.Error(w, fmt.Sprintf("unknown action %s\n", action), 500) http.Error(w, fmt.Sprintf("unknown action %s\n", action), 400)
return return
} }
return return
@ -210,9 +210,8 @@ func (h *microsubHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
respondJSON(w, []string{}) respondJSON(w, []string{})
} else { } else {
http.Error(w, fmt.Sprintf("unknown action %s\n", action), 500) http.Error(w, fmt.Sprintf("unknown action %s\n", action), 400)
} }
return return
} }
return return

170
pkg/server/microsub_test.go Normal file
View File

@ -0,0 +1,170 @@
package server
import (
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/stretchr/testify/assert"
"p83.nl/go/ekster/pkg/client"
)
func createServerClient() (*httptest.Server, *client.Client) {
backend := &NullBackend{}
handler := NewMicrosubHandler(backend)
server := httptest.NewServer(handler)
c := client.Client{
Logging: false,
Token: "1234",
}
c.Me, _ = url.Parse("https://example.com/")
c.MicrosubEndpoint, _ = url.Parse(server.URL + "/microsub")
return server, &c
}
func TestServer_ChannelsGetList(t *testing.T) {
server, c := createServerClient()
defer server.Close()
channels, err := c.ChannelsGetList()
if assert.NoError(t, err) {
assert.Equal(t, 2, len(channels), "should return 2 channels")
assert.Equal(t, "notifications", channels[0].Name)
assert.Equal(t, "0001", channels[0].UID)
assert.Equal(t, 0, channels[0].Unread)
assert.Equal(t, "default", channels[1].Name)
assert.Equal(t, "0000", channels[1].UID)
assert.Equal(t, 0, channels[1].Unread)
}
}
func TestServer_ChannelsCreate(t *testing.T) {
server, c := createServerClient()
defer server.Close()
channel, err := c.ChannelsCreate("test")
if assert.NoError(t, err) {
assert.Equal(t, "test", channel.Name)
}
}
func TestServer_ChannelsDelete(t *testing.T) {
server, c := createServerClient()
defer server.Close()
err := c.ChannelsDelete("0001")
assert.NoError(t, err)
}
func TestServer_ChannelsUpdate(t *testing.T) {
server, c := createServerClient()
defer server.Close()
_, err := c.ChannelsUpdate("0001", "newname")
assert.NoError(t, err)
}
func TestServer_TimelineGet(t *testing.T) {
server, c := createServerClient()
defer server.Close()
timeline, err := c.TimelineGet("", "", "0001")
if assert.NoError(t, err) {
assert.Equal(t, 0, len(timeline.Items))
}
}
func TestServer_FollowGetList(t *testing.T) {
server, c := createServerClient()
defer server.Close()
feeds, err := c.FollowGetList("0001")
if assert.NoError(t, err) {
assert.Equal(t, 1, len(feeds))
assert.Equal(t, "test", feeds[0].Name)
assert.Equal(t, "feed", feeds[0].Type)
assert.Equal(t, "https://example.com/", feeds[0].URL)
}
}
func TestServer_FollowURL(t *testing.T) {
server, c := createServerClient()
defer server.Close()
feed, err := c.FollowURL("0001", "https://example.com/")
if assert.NoError(t, err) {
assert.Equal(t, "feed", feed.Type)
assert.Equal(t, "https://example.com/", feed.URL)
}
}
func TestServer_UnFollowURL(t *testing.T) {
server, c := createServerClient()
defer server.Close()
err := c.UnfollowURL("0001", "https://example.com/")
assert.NoError(t, err)
}
func TestServer_PreviewURL(t *testing.T) {
server, c := createServerClient()
defer server.Close()
timeline, err := c.PreviewURL("https://example.com/")
if assert.NoError(t, err) {
assert.Equal(t, 0, len(timeline.Items))
}
}
func TestServer_Search(t *testing.T) {
server, c := createServerClient()
defer server.Close()
feeds, err := c.Search("https://example.com/")
if assert.NoError(t, err) {
assert.Equal(t, 1, len(feeds))
assert.Equal(t, "feed", feeds[0].Type)
assert.Equal(t, "https://example.com/", feeds[0].URL)
assert.Equal(t, "Example", feeds[0].Name)
assert.Equal(t, "test.jpg", feeds[0].Photo)
assert.Equal(t, "test", feeds[0].Description)
}
}
func TestServer_MarkRead(t *testing.T) {
server, c := createServerClient()
defer server.Close()
err := c.MarkRead("0001", []string{"test"})
assert.NoError(t, err)
}
func TestServer_GetUnknownAction(t *testing.T) {
server, c := createServerClient()
defer server.Close()
u := c.MicrosubEndpoint
q := u.Query()
q.Add("action", "missing")
u.RawQuery = q.Encode()
resp, err := http.Get(u.String())
if assert.NoError(t, err) {
assert.Equal(t, 400, resp.StatusCode)
}
}
func TestServer_PostUnknownAction(t *testing.T) {
server, c := createServerClient()
defer server.Close()
u := c.MicrosubEndpoint
q := u.Query()
q.Add("action", "missing")
u.RawQuery = q.Encode()
resp, err := http.Post(u.String(), "application/json", nil)
if assert.NoError(t, err) {
assert.Equal(t, 400, resp.StatusCode)
}
}

View File

@ -15,7 +15,7 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package main package server
import ( import (
"p83.nl/go/ekster/pkg/microsub" "p83.nl/go/ekster/pkg/microsub"
@ -25,11 +25,15 @@ import (
type NullBackend struct { type NullBackend struct {
} }
func (b *NullBackend) AddEventListener(el microsub.EventListener) error {
panic("implement me")
}
// ChannelsGetList gets no channels // ChannelsGetList gets no channels
func (b *NullBackend) ChannelsGetList() ([]microsub.Channel, error) { func (b *NullBackend) ChannelsGetList() ([]microsub.Channel, error) {
return []microsub.Channel{ return []microsub.Channel{
microsub.Channel{UID: "0001", Name: "notifications", Unread: 0}, {UID: "0001", Name: "notifications", Unread: 0},
microsub.Channel{UID: "0000", Name: "default", Unread: 0}, {UID: "0000", Name: "default", Unread: 0},
}, nil }, nil
} }
@ -63,7 +67,9 @@ func (b *NullBackend) TimelineGet(before, after, channel string) (microsub.Timel
} }
func (b *NullBackend) FollowGetList(uid string) ([]microsub.Feed, error) { func (b *NullBackend) FollowGetList(uid string) ([]microsub.Feed, error) {
return []microsub.Feed{}, nil return []microsub.Feed{
{Name: "test", Type: "feed", URL: "https://example.com/"},
}, nil
} }
func (b *NullBackend) FollowURL(uid string, url string) (microsub.Feed, error) { func (b *NullBackend) FollowURL(uid string, url string) (microsub.Feed, error) {
@ -75,7 +81,9 @@ func (b *NullBackend) UnfollowURL(uid string, url string) error {
} }
func (b *NullBackend) Search(query string) ([]microsub.Feed, error) { func (b *NullBackend) Search(query string) ([]microsub.Feed, error) {
return []microsub.Feed{}, nil return []microsub.Feed{
{"feed", "https://example.com/", "Example", "test.jpg", "test", microsub.Card{}},
}, nil
} }
func (b *NullBackend) PreviewURL(url string) (microsub.Timeline, error) { func (b *NullBackend) PreviewURL(url string) (microsub.Timeline, error) {