Some checks failed
continuous-integration/drone/push Build is failing
Adds Blevesearch to the server. Every item that is processed by the server is added to the index and can be returned from the ItemSearch request.
241 lines
6.1 KiB
Go
241 lines
6.1 KiB
Go
// 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 <http://www.gnu.org/licenses/>.
|
|
|
|
/*
|
|
Eksterd is a microsub server that is extendable.
|
|
*/
|
|
package main
|
|
|
|
import (
|
|
"database/sql"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/gomodule/redigo/redis"
|
|
"github.com/pkg/errors"
|
|
"p83.nl/go/ekster/pkg/auth"
|
|
|
|
"p83.nl/go/ekster/pkg/server"
|
|
)
|
|
|
|
// AppOptions are options for the app
|
|
type AppOptions struct {
|
|
Port int
|
|
AuthEnabled bool
|
|
Headless bool
|
|
RedisServer string
|
|
BaseURL string
|
|
TemplateDir string
|
|
pool *redis.Pool
|
|
database *sql.DB
|
|
}
|
|
|
|
func init() {
|
|
log.SetFlags(log.Lshortfile | log.Ldate | log.Ltime)
|
|
}
|
|
|
|
func newPool(addr string) *redis.Pool {
|
|
return &redis.Pool{
|
|
MaxIdle: 3,
|
|
IdleTimeout: 240 * time.Second,
|
|
Dial: func() (redis.Conn, error) { return redis.Dial("tcp", addr) },
|
|
}
|
|
}
|
|
|
|
// WithAuth adds authorization to a http.Handler
|
|
func WithAuth(handler http.Handler, b *memoryBackend) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method == http.MethodOptions {
|
|
handler.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
|
|
authorization := ""
|
|
|
|
values := r.URL.Query()
|
|
|
|
if r.Method == http.MethodGet && values.Get("action") == "events" && values.Get("access_token") != "" {
|
|
authorization = "Bearer " + values.Get("access_token")
|
|
} else {
|
|
authorization = r.Header.Get("Authorization")
|
|
}
|
|
|
|
var token auth.TokenResponse
|
|
|
|
authorized, err := b.AuthTokenAccepted(authorization, &token)
|
|
if err != nil {
|
|
log.Printf("token not accepted: %v", err)
|
|
}
|
|
if !authorized {
|
|
log.Printf("Token could not be validated")
|
|
http.Error(w, "Can't validate token", 403)
|
|
return
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
handler.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
|
|
// App is the main app structure
|
|
type App struct {
|
|
options AppOptions
|
|
backend *memoryBackend
|
|
hubBackend *hubIncomingBackend
|
|
}
|
|
|
|
// Run runs the app
|
|
func (app *App) Run() error {
|
|
err := initSearch()
|
|
if err != nil {
|
|
return fmt.Errorf("while starting app: %v", err)
|
|
}
|
|
app.backend.run()
|
|
app.hubBackend.run()
|
|
|
|
log.Printf("Listening on port %d\n", app.options.Port)
|
|
return http.ListenAndServe(fmt.Sprintf(":%d", app.options.Port), nil)
|
|
}
|
|
|
|
// NewApp initializes the App
|
|
func NewApp(options AppOptions) (*App, error) {
|
|
app := &App{
|
|
options: options,
|
|
}
|
|
|
|
backend, err := loadMemoryBackend(options.pool, options.database)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
app.backend = backend
|
|
app.backend.AuthEnabled = options.AuthEnabled
|
|
app.backend.baseURL = options.BaseURL
|
|
app.backend.hubIncomingBackend.pool = options.pool
|
|
app.backend.hubIncomingBackend.baseURL = options.BaseURL
|
|
|
|
app.hubBackend = &hubIncomingBackend{backend: app.backend, baseURL: options.BaseURL, pool: options.pool}
|
|
|
|
http.Handle("/micropub", µpubHandler{
|
|
Backend: app.backend,
|
|
pool: options.pool,
|
|
})
|
|
|
|
handler, broker := server.NewMicrosubHandler(app.backend)
|
|
if options.AuthEnabled {
|
|
handler = WithAuth(handler, app.backend)
|
|
}
|
|
|
|
app.backend.broker = broker
|
|
|
|
http.Handle("/microsub", handler)
|
|
|
|
http.Handle("/incoming/", &incomingHandler{
|
|
Backend: app.hubBackend,
|
|
})
|
|
|
|
if !options.Headless {
|
|
handler, err := newMainHandler(app.backend, options.BaseURL, options.TemplateDir, options.pool)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "could not create main handler")
|
|
}
|
|
http.Handle("/", handler)
|
|
}
|
|
|
|
return app, nil
|
|
}
|
|
|
|
func main() {
|
|
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()
|
|
|
|
if options.AuthEnabled {
|
|
log.Println("Using auth")
|
|
} else {
|
|
log.Println("Authentication disabled")
|
|
}
|
|
|
|
if options.BaseURL == "" {
|
|
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
|
|
args := flag.Args()
|
|
|
|
if len(args) >= 1 {
|
|
if args[0] == "new" {
|
|
createBackend = true
|
|
}
|
|
}
|
|
|
|
if createBackend {
|
|
err := createMemoryBackend()
|
|
if err != nil {
|
|
log.Fatalf("Error while saving backend.json: %s", err)
|
|
}
|
|
|
|
// TODO(peter): automatically gather this information from login or otherwise
|
|
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 "TokenEndpoint" variable to the address of your token endpoint "https://example.com/token"`)
|
|
|
|
return
|
|
}
|
|
|
|
pool := newPool(options.RedisServer)
|
|
options.pool = pool
|
|
db, err := sql.Open("postgres", "host=database user=postgres password=simple dbname=ekster sslmode=disable")
|
|
if err != nil {
|
|
log.Fatalf("database open failed: %s", err)
|
|
}
|
|
options.database = db
|
|
|
|
app, err := NewApp(options)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
log.Fatal(app.Run())
|
|
|
|
db.Close()
|
|
}
|