ekster/cmd/eksterd/main.go
Peter Stuifzand f14e6d8249
Some checks failed
continuous-integration/drone/push Build is failing
Add full text search to server
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.
2021-05-30 22:01:34 +02:00

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", &micropubHandler{
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()
}