Break functionality out of Serve() func
- Adds a new interface, Apper, that enables loading and persisting instance-level data in new ways - Converts some initialization funcs to methods - Exports funcs and methods needed for intialization - In general, moves a ton of stuff around Overall, this should maintain all existing functionality, but with the ability to now better manage a WF instance. Ref T613
This commit is contained in:
parent
ed4aacd1ac
commit
034db22f8c
294
app.go
294
app.go
|
@ -14,6 +14,7 @@ import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
@ -76,10 +77,109 @@ type App struct {
|
||||||
timeline *localTimeline
|
timeline *localTimeline
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DB returns the App's datastore
|
||||||
|
func (app *App) DB() *datastore {
|
||||||
|
return app.db
|
||||||
|
}
|
||||||
|
|
||||||
|
// Router returns the App's router
|
||||||
|
func (app *App) Router() *mux.Router {
|
||||||
|
return app.router
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config returns the App's current configuration.
|
||||||
|
func (app *App) Config() *config.Config {
|
||||||
|
return app.cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetConfig updates the App's Config to the given value.
|
||||||
|
func (app *App) SetConfig(cfg *config.Config) {
|
||||||
|
app.cfg = cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetKeys updates the App's Keychain to the given value.
|
||||||
func (app *App) SetKeys(k *key.Keychain) {
|
func (app *App) SetKeys(k *key.Keychain) {
|
||||||
app.keys = k
|
app.keys = k
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apper is the interface for getting data into and out of a WriteFreely
|
||||||
|
// instance (or "App").
|
||||||
|
//
|
||||||
|
// App returns the App for the current instance.
|
||||||
|
//
|
||||||
|
// LoadConfig reads an app configuration into the App, returning any error
|
||||||
|
// encountered.
|
||||||
|
//
|
||||||
|
// SaveConfig persists the current App configuration.
|
||||||
|
//
|
||||||
|
// LoadKeys reads the App's encryption keys and loads them into its
|
||||||
|
// key.Keychain.
|
||||||
|
type Apper interface {
|
||||||
|
App() *App
|
||||||
|
|
||||||
|
LoadConfig() error
|
||||||
|
SaveConfig(*config.Config) error
|
||||||
|
|
||||||
|
LoadKeys() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// App returns the App
|
||||||
|
func (app *App) App() *App {
|
||||||
|
return app
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadConfig loads and parses a config file.
|
||||||
|
func (app *App) LoadConfig() error {
|
||||||
|
log.Info("Loading %s configuration...", app.cfgFile)
|
||||||
|
cfg, err := config.Load(app.cfgFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Unable to load configuration: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
app.cfg = cfg
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveConfig saves the given Config to disk -- namely, to the App's cfgFile.
|
||||||
|
func (app *App) SaveConfig(c *config.Config) error {
|
||||||
|
return config.Save(c, app.cfgFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadKeys reads all needed keys from disk into the App. In order to use the
|
||||||
|
// configured `Server.KeysParentDir`, you must call initKeyPaths(App) before
|
||||||
|
// this.
|
||||||
|
func (app *App) LoadKeys() error {
|
||||||
|
var err error
|
||||||
|
app.keys = &key.Keychain{}
|
||||||
|
|
||||||
|
if debugging {
|
||||||
|
log.Info(" %s", emailKeyPath)
|
||||||
|
}
|
||||||
|
app.keys.EmailKey, err = ioutil.ReadFile(emailKeyPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if debugging {
|
||||||
|
log.Info(" %s", cookieAuthKeyPath)
|
||||||
|
}
|
||||||
|
app.keys.CookieAuthKey, err = ioutil.ReadFile(cookieAuthKeyPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if debugging {
|
||||||
|
log.Info(" %s", cookieKeyPath)
|
||||||
|
}
|
||||||
|
app.keys.CookieKey, err = ioutil.ReadFile(cookieKeyPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// handleViewHome shows page at root path. Will be the Pad if logged in and the
|
// handleViewHome shows page at root path. Will be the Pad if logged in and the
|
||||||
// catch-all landing page otherwise.
|
// catch-all landing page otherwise.
|
||||||
func handleViewHome(app *App, w http.ResponseWriter, r *http.Request) error {
|
func handleViewHome(app *App, w http.ResponseWriter, r *http.Request) error {
|
||||||
|
@ -198,81 +298,50 @@ func pageForReq(app *App, r *http.Request) page.StaticPage {
|
||||||
|
|
||||||
var fileRegex = regexp.MustCompile("/([^/]*\\.[^/]*)$")
|
var fileRegex = regexp.MustCompile("/([^/]*\\.[^/]*)$")
|
||||||
|
|
||||||
func Serve(app *App, debug bool) {
|
// Initialize loads the app configuration and initializes templates, keys,
|
||||||
|
// session, route handlers, and the database connection.
|
||||||
|
func Initialize(apper Apper, debug bool) (*App, error) {
|
||||||
debugging = debug
|
debugging = debug
|
||||||
|
|
||||||
log.Info("Initializing...")
|
apper.LoadConfig()
|
||||||
|
|
||||||
loadConfig(app)
|
// Load templates
|
||||||
|
err := InitTemplates(apper.App().Config())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("load templates: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load keys and set up session
|
||||||
|
initKeyPaths(apper.App()) // TODO: find a better way to do this, since it's unneeded in all Apper implementations
|
||||||
|
err = InitKeys(apper)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("init keys: %s", err)
|
||||||
|
}
|
||||||
|
apper.App().InitSession()
|
||||||
|
|
||||||
|
apper.App().InitDecoder()
|
||||||
|
|
||||||
|
err = ConnectToDatabase(apper.App())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("connect to DB: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle local timeline, if enabled
|
||||||
|
if apper.App().cfg.App.LocalTimeline {
|
||||||
|
log.Info("Initializing local timeline...")
|
||||||
|
initLocalTimeline(apper.App())
|
||||||
|
}
|
||||||
|
|
||||||
|
return apper.App(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Serve(app *App, r *mux.Router) {
|
||||||
|
log.Info("Going to serve...")
|
||||||
|
|
||||||
hostName = app.cfg.App.Host
|
hostName = app.cfg.App.Host
|
||||||
isSingleUser = app.cfg.App.SingleUser
|
isSingleUser = app.cfg.App.SingleUser
|
||||||
app.cfg.Server.Dev = debugging
|
app.cfg.Server.Dev = debugging
|
||||||
|
|
||||||
err := initTemplates(app.cfg)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("load templates: %s", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load keys
|
|
||||||
log.Info("Loading encryption keys...")
|
|
||||||
initKeyPaths(app)
|
|
||||||
err = initKeys(app)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("\n%s\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize modules
|
|
||||||
app.sessionStore = initSession(app)
|
|
||||||
app.formDecoder = schema.NewDecoder()
|
|
||||||
app.formDecoder.RegisterConverter(converter.NullJSONString{}, converter.ConvertJSONNullString)
|
|
||||||
app.formDecoder.RegisterConverter(converter.NullJSONBool{}, converter.ConvertJSONNullBool)
|
|
||||||
app.formDecoder.RegisterConverter(sql.NullString{}, converter.ConvertSQLNullString)
|
|
||||||
app.formDecoder.RegisterConverter(sql.NullBool{}, converter.ConvertSQLNullBool)
|
|
||||||
app.formDecoder.RegisterConverter(sql.NullInt64{}, converter.ConvertSQLNullInt64)
|
|
||||||
app.formDecoder.RegisterConverter(sql.NullFloat64{}, converter.ConvertSQLNullFloat64)
|
|
||||||
|
|
||||||
// Check database configuration
|
|
||||||
if app.cfg.Database.Type == driverMySQL && (app.cfg.Database.User == "" || app.cfg.Database.Password == "") {
|
|
||||||
log.Error("Database user or password not set.")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
if app.cfg.Database.Host == "" {
|
|
||||||
app.cfg.Database.Host = "localhost"
|
|
||||||
}
|
|
||||||
if app.cfg.Database.Database == "" {
|
|
||||||
app.cfg.Database.Database = "writefreely"
|
|
||||||
}
|
|
||||||
|
|
||||||
connectToDatabase(app)
|
|
||||||
defer shutdown(app)
|
|
||||||
|
|
||||||
// Test database connection
|
|
||||||
err = app.db.Ping()
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Database ping failed: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
r := mux.NewRouter()
|
|
||||||
handler := NewHandler(app)
|
|
||||||
handler.SetErrorPages(&ErrorPages{
|
|
||||||
NotFound: pages["404-general.tmpl"],
|
|
||||||
Gone: pages["410.tmpl"],
|
|
||||||
InternalServerError: pages["500.tmpl"],
|
|
||||||
Blank: pages["blank.tmpl"],
|
|
||||||
})
|
|
||||||
|
|
||||||
// Handle app routes
|
|
||||||
initRoutes(handler, r, app.cfg, app.db)
|
|
||||||
|
|
||||||
// Handle local timeline, if enabled
|
|
||||||
if app.cfg.App.LocalTimeline {
|
|
||||||
log.Info("Initializing local timeline...")
|
|
||||||
initLocalTimeline(app)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Handle shutdown
|
// Handle shutdown
|
||||||
c := make(chan os.Signal, 2)
|
c := make(chan os.Signal, 2)
|
||||||
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
||||||
|
@ -284,13 +353,12 @@ func Serve(app *App, debug bool) {
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
http.Handle("/", r)
|
|
||||||
|
|
||||||
// Start web application server
|
// Start web application server
|
||||||
var bindAddress = app.cfg.Server.Bind
|
var bindAddress = app.cfg.Server.Bind
|
||||||
if bindAddress == "" {
|
if bindAddress == "" {
|
||||||
bindAddress = "localhost"
|
bindAddress = "localhost"
|
||||||
}
|
}
|
||||||
|
var err error
|
||||||
if app.cfg.IsSecureStandalone() {
|
if app.cfg.IsSecureStandalone() {
|
||||||
log.Info("Serving redirects on http://%s:80", bindAddress)
|
log.Info("Serving redirects on http://%s:80", bindAddress)
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -304,11 +372,11 @@ func Serve(app *App, debug bool) {
|
||||||
log.Info("Serving on https://%s:443", bindAddress)
|
log.Info("Serving on https://%s:443", bindAddress)
|
||||||
log.Info("---")
|
log.Info("---")
|
||||||
err = http.ListenAndServeTLS(
|
err = http.ListenAndServeTLS(
|
||||||
fmt.Sprintf("%s:443", bindAddress), app.cfg.Server.TLSCertPath, app.cfg.Server.TLSKeyPath, nil)
|
fmt.Sprintf("%s:443", bindAddress), app.cfg.Server.TLSCertPath, app.cfg.Server.TLSKeyPath, r)
|
||||||
} else {
|
} else {
|
||||||
log.Info("Serving on http://%s:%d\n", bindAddress, app.cfg.Server.Port)
|
log.Info("Serving on http://%s:%d\n", bindAddress, app.cfg.Server.Port)
|
||||||
log.Info("---")
|
log.Info("---")
|
||||||
err = http.ListenAndServe(fmt.Sprintf("%s:%d", bindAddress, app.cfg.Server.Port), nil)
|
err = http.ListenAndServe(fmt.Sprintf("%s:%d", bindAddress, app.cfg.Server.Port), r)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Unable to start: %v", err)
|
log.Error("Unable to start: %v", err)
|
||||||
|
@ -316,6 +384,44 @@ func Serve(app *App, debug bool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (app *App) InitDecoder() {
|
||||||
|
// TODO: do this at the package level, instead of the App level
|
||||||
|
// Initialize modules
|
||||||
|
app.formDecoder = schema.NewDecoder()
|
||||||
|
app.formDecoder.RegisterConverter(converter.NullJSONString{}, converter.ConvertJSONNullString)
|
||||||
|
app.formDecoder.RegisterConverter(converter.NullJSONBool{}, converter.ConvertJSONNullBool)
|
||||||
|
app.formDecoder.RegisterConverter(sql.NullString{}, converter.ConvertSQLNullString)
|
||||||
|
app.formDecoder.RegisterConverter(sql.NullBool{}, converter.ConvertSQLNullBool)
|
||||||
|
app.formDecoder.RegisterConverter(sql.NullInt64{}, converter.ConvertSQLNullInt64)
|
||||||
|
app.formDecoder.RegisterConverter(sql.NullFloat64{}, converter.ConvertSQLNullFloat64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnectToDatabase validates and connects to the configured database, then
|
||||||
|
// tests the connection.
|
||||||
|
func ConnectToDatabase(app *App) error {
|
||||||
|
// Check database configuration
|
||||||
|
if app.cfg.Database.Type == driverMySQL && (app.cfg.Database.User == "" || app.cfg.Database.Password == "") {
|
||||||
|
return fmt.Errorf("Database user or password not set.")
|
||||||
|
}
|
||||||
|
if app.cfg.Database.Host == "" {
|
||||||
|
app.cfg.Database.Host = "localhost"
|
||||||
|
}
|
||||||
|
if app.cfg.Database.Database == "" {
|
||||||
|
app.cfg.Database.Database = "writefreely"
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: check err
|
||||||
|
connectToDatabase(app)
|
||||||
|
|
||||||
|
// Test database connection
|
||||||
|
err := app.db.Ping()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Database ping failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// OutputVersion prints out the version of the application.
|
// OutputVersion prints out the version of the application.
|
||||||
func OutputVersion() {
|
func OutputVersion() {
|
||||||
fmt.Println(serverSoftware + " " + softwareVer)
|
fmt.Println(serverSoftware + " " + softwareVer)
|
||||||
|
@ -378,10 +484,10 @@ func DoConfig(app *App) {
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenerateKeys creates app encryption keys and saves them into the configured KeysParentDir.
|
// GenerateKeyFiles creates app encryption keys and saves them into the configured KeysParentDir.
|
||||||
func GenerateKeys(app *App) error {
|
func GenerateKeyFiles(app *App) error {
|
||||||
// Read keys path from config
|
// Read keys path from config
|
||||||
loadConfig(app)
|
app.LoadConfig()
|
||||||
|
|
||||||
// Create keys dir if it doesn't exist yet
|
// Create keys dir if it doesn't exist yet
|
||||||
fullKeysDir := filepath.Join(app.cfg.Server.KeysParentDir, keysDir)
|
fullKeysDir := filepath.Join(app.cfg.Server.KeysParentDir, keysDir)
|
||||||
|
@ -412,11 +518,11 @@ func GenerateKeys(app *App) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateSchema creates all database tables needed for the application.
|
// CreateSchema creates all database tables needed for the application.
|
||||||
func CreateSchema(app *App) error {
|
func CreateSchema(apper Apper) error {
|
||||||
loadConfig(app)
|
apper.LoadConfig()
|
||||||
connectToDatabase(app)
|
connectToDatabase(apper.App())
|
||||||
defer shutdown(app)
|
defer shutdown(apper.App())
|
||||||
err := adminInitDatabase(app)
|
err := adminInitDatabase(apper.App())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -425,7 +531,7 @@ func CreateSchema(app *App) error {
|
||||||
|
|
||||||
// Migrate runs all necessary database migrations.
|
// Migrate runs all necessary database migrations.
|
||||||
func Migrate(app *App) error {
|
func Migrate(app *App) error {
|
||||||
loadConfig(app)
|
app.LoadConfig()
|
||||||
connectToDatabase(app)
|
connectToDatabase(app)
|
||||||
defer shutdown(app)
|
defer shutdown(app)
|
||||||
|
|
||||||
|
@ -439,7 +545,7 @@ func Migrate(app *App) error {
|
||||||
// ResetPassword runs the interactive password reset process.
|
// ResetPassword runs the interactive password reset process.
|
||||||
func ResetPassword(app *App, username string) error {
|
func ResetPassword(app *App, username string) error {
|
||||||
// Connect to the database
|
// Connect to the database
|
||||||
loadConfig(app)
|
app.LoadConfig()
|
||||||
connectToDatabase(app)
|
connectToDatabase(app)
|
||||||
defer shutdown(app)
|
defer shutdown(app)
|
||||||
|
|
||||||
|
@ -475,16 +581,6 @@ func ResetPassword(app *App, username string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadConfig(app *App) {
|
|
||||||
log.Info("Loading %s configuration...", app.cfgFile)
|
|
||||||
cfg, err := config.Load(app.cfgFile)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Unable to load configuration: %v", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
app.cfg = cfg
|
|
||||||
}
|
|
||||||
|
|
||||||
func connectToDatabase(app *App) {
|
func connectToDatabase(app *App) {
|
||||||
log.Info("Connecting to %s database...", app.cfg.Database.Type)
|
log.Info("Connecting to %s database...", app.cfg.Database.Type)
|
||||||
|
|
||||||
|
@ -521,14 +617,14 @@ func shutdown(app *App) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateUser creates a new admin or normal user from the given credentials.
|
// CreateUser creates a new admin or normal user from the given credentials.
|
||||||
func CreateUser(app *App, username, password string, isAdmin bool) error {
|
func CreateUser(apper Apper, username, password string, isAdmin bool) error {
|
||||||
// Create an admin user with --create-admin
|
// Create an admin user with --create-admin
|
||||||
loadConfig(app)
|
apper.LoadConfig()
|
||||||
connectToDatabase(app)
|
connectToDatabase(apper.App())
|
||||||
defer shutdown(app)
|
defer shutdown(apper.App())
|
||||||
|
|
||||||
// Ensure an admin / first user doesn't already exist
|
// Ensure an admin / first user doesn't already exist
|
||||||
firstUser, _ := app.db.GetUserByID(1)
|
firstUser, _ := apper.App().db.GetUserByID(1)
|
||||||
if isAdmin {
|
if isAdmin {
|
||||||
// Abort if trying to create admin user, but one already exists
|
// Abort if trying to create admin user, but one already exists
|
||||||
if firstUser != nil {
|
if firstUser != nil {
|
||||||
|
@ -551,8 +647,8 @@ func CreateUser(app *App, username, password string, isAdmin bool) error {
|
||||||
usernameDesc += " (originally: " + desiredUsername + ")"
|
usernameDesc += " (originally: " + desiredUsername + ")"
|
||||||
}
|
}
|
||||||
|
|
||||||
if !author.IsValidUsername(app.cfg, username) {
|
if !author.IsValidUsername(apper.App().cfg, username) {
|
||||||
return fmt.Errorf("Username %s is invalid, reserved, or shorter than configured minimum length (%d characters).", usernameDesc, app.cfg.App.MinUsernameLen)
|
return fmt.Errorf("Username %s is invalid, reserved, or shorter than configured minimum length (%d characters).", usernameDesc, apper.App().cfg.App.MinUsernameLen)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash the password
|
// Hash the password
|
||||||
|
@ -572,7 +668,7 @@ func CreateUser(app *App, username, password string, isAdmin bool) error {
|
||||||
userType = "admin"
|
userType = "admin"
|
||||||
}
|
}
|
||||||
log.Info("Creating %s %s...", userType, usernameDesc)
|
log.Info("Creating %s %s...", userType, usernameDesc)
|
||||||
err = app.db.CreateUser(u, desiredUsername)
|
err = apper.App().db.CreateUser(u, desiredUsername)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Unable to create user: %s", err)
|
return fmt.Errorf("Unable to create user: %s", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
"github.com/writeas/web-core/log"
|
"github.com/writeas/web-core/log"
|
||||||
"github.com/writeas/writefreely"
|
"github.com/writeas/writefreely"
|
||||||
"os"
|
"os"
|
||||||
|
@ -54,7 +55,7 @@ func main() {
|
||||||
writefreely.DoConfig(app)
|
writefreely.DoConfig(app)
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
} else if *genKeys {
|
} else if *genKeys {
|
||||||
err := writefreely.GenerateKeys(app)
|
err := writefreely.GenerateKeyFiles(app)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err.Error())
|
log.Error(err.Error())
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -107,7 +108,21 @@ func main() {
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
writefreely.Serve(app, *debugPtr)
|
// Initialize the application
|
||||||
|
var err error
|
||||||
|
app, err = writefreely.Initialize(app, *debugPtr)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("%s", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set app routes
|
||||||
|
r := mux.NewRouter()
|
||||||
|
app.InitRoutes(r)
|
||||||
|
app.InitStaticRoutes(r)
|
||||||
|
|
||||||
|
// Serve the application
|
||||||
|
writefreely.Serve(app, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func userPass(credStr string, isAdmin bool) (user string, pass string, err error) {
|
func userPass(credStr string, isAdmin bool) (user string, pass string, err error) {
|
||||||
|
|
13
handle.go
13
handle.go
|
@ -74,6 +74,19 @@ func NewHandler(app *App) *Handler {
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewWFHandler returns a new Handler instance, using WriteFreely template files.
|
||||||
|
// You MUST call writefreely.InitTemplates() before this.
|
||||||
|
func NewWFHandler(app *App) *Handler {
|
||||||
|
h := NewHandler(app)
|
||||||
|
h.SetErrorPages(&ErrorPages{
|
||||||
|
NotFound: pages["404-general.tmpl"],
|
||||||
|
Gone: pages["410.tmpl"],
|
||||||
|
InternalServerError: pages["500.tmpl"],
|
||||||
|
Blank: pages["blank.tmpl"],
|
||||||
|
})
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
// SetErrorPages sets the given set of ErrorPages as templates for any errors
|
// SetErrorPages sets the given set of ErrorPages as templates for any errors
|
||||||
// that come up.
|
// that come up.
|
||||||
func (h *Handler) SetErrorPages(e *ErrorPages) {
|
func (h *Handler) SetErrorPages(e *ErrorPages) {
|
||||||
|
|
40
keys.go
40
keys.go
|
@ -28,6 +28,15 @@ var (
|
||||||
cookieKeyPath = filepath.Join(keysDir, "cookies_enc.aes256")
|
cookieKeyPath = filepath.Join(keysDir, "cookies_enc.aes256")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// InitKeys loads encryption keys into memory via the given Apper interface
|
||||||
|
func InitKeys(apper Apper) error {
|
||||||
|
log.Info("Loading encryption keys...")
|
||||||
|
err := apper.LoadKeys()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func initKeyPaths(app *App) {
|
func initKeyPaths(app *App) {
|
||||||
emailKeyPath = filepath.Join(app.cfg.Server.KeysParentDir, emailKeyPath)
|
emailKeyPath = filepath.Join(app.cfg.Server.KeysParentDir, emailKeyPath)
|
||||||
|
@ -35,37 +44,6 @@ func initKeyPaths(app *App) {
|
||||||
cookieKeyPath = filepath.Join(app.cfg.Server.KeysParentDir, cookieKeyPath)
|
cookieKeyPath = filepath.Join(app.cfg.Server.KeysParentDir, cookieKeyPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initKeys(app *App) error {
|
|
||||||
var err error
|
|
||||||
app.keys = &key.Keychain{}
|
|
||||||
|
|
||||||
if debugging {
|
|
||||||
log.Info(" %s", emailKeyPath)
|
|
||||||
}
|
|
||||||
app.keys.EmailKey, err = ioutil.ReadFile(emailKeyPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if debugging {
|
|
||||||
log.Info(" %s", cookieAuthKeyPath)
|
|
||||||
}
|
|
||||||
app.keys.CookieAuthKey, err = ioutil.ReadFile(cookieAuthKeyPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if debugging {
|
|
||||||
log.Info(" %s", cookieKeyPath)
|
|
||||||
}
|
|
||||||
app.keys.CookieKey, err = ioutil.ReadFile(cookieKeyPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// generateKey generates a key at the given path used for the encryption of
|
// generateKey generates a key at the given path used for the encryption of
|
||||||
// certain user data. Because user data becomes unrecoverable without these
|
// certain user data. Because user data becomes unrecoverable without these
|
||||||
// keys, this won't overwrite any existing key, and instead outputs a message.
|
// keys, this won't overwrite any existing key, and instead outputs a message.
|
||||||
|
|
27
routes.go
27
routes.go
|
@ -14,7 +14,6 @@ import (
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/writeas/go-webfinger"
|
"github.com/writeas/go-webfinger"
|
||||||
"github.com/writeas/web-core/log"
|
"github.com/writeas/web-core/log"
|
||||||
"github.com/writeas/writefreely/config"
|
|
||||||
"github.com/writefreely/go-nodeinfo"
|
"github.com/writefreely/go-nodeinfo"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -31,9 +30,14 @@ func (app *App) InitStaticRoutes(r *mux.Router) {
|
||||||
r.PathPrefix("/").Handler(fs)
|
r.PathPrefix("/").Handler(fs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initRoutes(handler *Handler, r *mux.Router, cfg *config.Config, db *datastore) {
|
// InitRoutes adds dynamic routes for the given mux.Router.
|
||||||
hostSubroute := cfg.App.Host[strings.Index(cfg.App.Host, "://")+3:]
|
func (app *App) InitRoutes(r *mux.Router) *mux.Router {
|
||||||
if cfg.App.SingleUser {
|
// Create handler
|
||||||
|
handler := NewWFHandler(app)
|
||||||
|
|
||||||
|
// Set up routes
|
||||||
|
hostSubroute := app.cfg.App.Host[strings.Index(app.cfg.App.Host, "://")+3:]
|
||||||
|
if app.cfg.App.SingleUser {
|
||||||
hostSubroute = "{domain}"
|
hostSubroute = "{domain}"
|
||||||
} else {
|
} else {
|
||||||
if strings.HasPrefix(hostSubroute, "localhost") {
|
if strings.HasPrefix(hostSubroute, "localhost") {
|
||||||
|
@ -41,7 +45,7 @@ func initRoutes(handler *Handler, r *mux.Router, cfg *config.Config, db *datasto
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.App.SingleUser {
|
if app.cfg.App.SingleUser {
|
||||||
log.Info("Adding %s routes (single user)...", hostSubroute)
|
log.Info("Adding %s routes (single user)...", hostSubroute)
|
||||||
} else {
|
} else {
|
||||||
log.Info("Adding %s routes (multi-user)...", hostSubroute)
|
log.Info("Adding %s routes (multi-user)...", hostSubroute)
|
||||||
|
@ -51,7 +55,7 @@ func initRoutes(handler *Handler, r *mux.Router, cfg *config.Config, db *datasto
|
||||||
write := r.PathPrefix("/").Subrouter()
|
write := r.PathPrefix("/").Subrouter()
|
||||||
|
|
||||||
// Federation endpoint configurations
|
// Federation endpoint configurations
|
||||||
wf := webfinger.Default(wfResolver{db, cfg})
|
wf := webfinger.Default(wfResolver{app.db, app.cfg})
|
||||||
wf.NoTLSHandler = nil
|
wf.NoTLSHandler = nil
|
||||||
|
|
||||||
// Federation endpoints
|
// Federation endpoints
|
||||||
|
@ -60,15 +64,15 @@ func initRoutes(handler *Handler, r *mux.Router, cfg *config.Config, db *datasto
|
||||||
// webfinger
|
// webfinger
|
||||||
write.HandleFunc(webfinger.WebFingerPath, handler.LogHandlerFunc(http.HandlerFunc(wf.Webfinger)))
|
write.HandleFunc(webfinger.WebFingerPath, handler.LogHandlerFunc(http.HandlerFunc(wf.Webfinger)))
|
||||||
// nodeinfo
|
// nodeinfo
|
||||||
niCfg := nodeInfoConfig(db, cfg)
|
niCfg := nodeInfoConfig(app.db, app.cfg)
|
||||||
ni := nodeinfo.NewService(*niCfg, nodeInfoResolver{cfg, db})
|
ni := nodeinfo.NewService(*niCfg, nodeInfoResolver{app.cfg, app.db})
|
||||||
write.HandleFunc(nodeinfo.NodeInfoPath, handler.LogHandlerFunc(http.HandlerFunc(ni.NodeInfoDiscover)))
|
write.HandleFunc(nodeinfo.NodeInfoPath, handler.LogHandlerFunc(http.HandlerFunc(ni.NodeInfoDiscover)))
|
||||||
write.HandleFunc(niCfg.InfoURL, handler.LogHandlerFunc(http.HandlerFunc(ni.NodeInfo)))
|
write.HandleFunc(niCfg.InfoURL, handler.LogHandlerFunc(http.HandlerFunc(ni.NodeInfo)))
|
||||||
|
|
||||||
// Set up dyamic page handlers
|
// Set up dyamic page handlers
|
||||||
// Handle auth
|
// Handle auth
|
||||||
auth := write.PathPrefix("/api/auth/").Subrouter()
|
auth := write.PathPrefix("/api/auth/").Subrouter()
|
||||||
if cfg.App.OpenRegistration {
|
if app.cfg.App.OpenRegistration {
|
||||||
auth.HandleFunc("/signup", handler.All(apiSignup)).Methods("POST")
|
auth.HandleFunc("/signup", handler.All(apiSignup)).Methods("POST")
|
||||||
}
|
}
|
||||||
auth.HandleFunc("/login", handler.All(login)).Methods("POST")
|
auth.HandleFunc("/login", handler.All(login)).Methods("POST")
|
||||||
|
@ -155,7 +159,7 @@ func initRoutes(handler *Handler, r *mux.Router, cfg *config.Config, db *datasto
|
||||||
RouteRead(handler, readPerm, write.PathPrefix("/read").Subrouter())
|
RouteRead(handler, readPerm, write.PathPrefix("/read").Subrouter())
|
||||||
|
|
||||||
draftEditPrefix := ""
|
draftEditPrefix := ""
|
||||||
if cfg.App.SingleUser {
|
if app.cfg.App.SingleUser {
|
||||||
draftEditPrefix = "/d"
|
draftEditPrefix = "/d"
|
||||||
write.HandleFunc("/me/new", handler.Web(handleViewPad, UserLevelOptional)).Methods("GET")
|
write.HandleFunc("/me/new", handler.Web(handleViewPad, UserLevelOptional)).Methods("GET")
|
||||||
} else {
|
} else {
|
||||||
|
@ -166,7 +170,7 @@ func initRoutes(handler *Handler, r *mux.Router, cfg *config.Config, db *datasto
|
||||||
write.HandleFunc(draftEditPrefix+"/{action}/edit", handler.Web(handleViewPad, UserLevelOptional)).Methods("GET")
|
write.HandleFunc(draftEditPrefix+"/{action}/edit", handler.Web(handleViewPad, UserLevelOptional)).Methods("GET")
|
||||||
write.HandleFunc(draftEditPrefix+"/{action}/meta", handler.Web(handleViewMeta, UserLevelOptional)).Methods("GET")
|
write.HandleFunc(draftEditPrefix+"/{action}/meta", handler.Web(handleViewMeta, UserLevelOptional)).Methods("GET")
|
||||||
// Collections
|
// Collections
|
||||||
if cfg.App.SingleUser {
|
if app.cfg.App.SingleUser {
|
||||||
RouteCollections(handler, write.PathPrefix("/").Subrouter())
|
RouteCollections(handler, write.PathPrefix("/").Subrouter())
|
||||||
} else {
|
} else {
|
||||||
write.HandleFunc("/{prefix:[@~$!\\-+]}{collection}", handler.Web(handleViewCollection, UserLevelOptional))
|
write.HandleFunc("/{prefix:[@~$!\\-+]}{collection}", handler.Web(handleViewCollection, UserLevelOptional))
|
||||||
|
@ -176,6 +180,7 @@ func initRoutes(handler *Handler, r *mux.Router, cfg *config.Config, db *datasto
|
||||||
}
|
}
|
||||||
write.HandleFunc(draftEditPrefix+"/{post}", handler.Web(handleViewPost, UserLevelOptional))
|
write.HandleFunc(draftEditPrefix+"/{post}", handler.Web(handleViewPost, UserLevelOptional))
|
||||||
write.HandleFunc("/", handler.Web(handleViewHome, UserLevelOptional))
|
write.HandleFunc("/", handler.Web(handleViewHome, UserLevelOptional))
|
||||||
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func RouteCollections(handler *Handler, r *mux.Router) {
|
func RouteCollections(handler *Handler, r *mux.Router) {
|
||||||
|
|
|
@ -27,9 +27,9 @@ const (
|
||||||
blogPassCookieName = "ub"
|
blogPassCookieName = "ub"
|
||||||
)
|
)
|
||||||
|
|
||||||
// initSession creates the cookie store. It depends on the keychain already
|
// InitSession creates the cookie store. It depends on the keychain already
|
||||||
// being loaded.
|
// being loaded.
|
||||||
func initSession(app *App) *sessions.CookieStore {
|
func (app *App) InitSession() {
|
||||||
// Register complex data types we'll be storing in cookies
|
// Register complex data types we'll be storing in cookies
|
||||||
gob.Register(&User{})
|
gob.Register(&User{})
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ func initSession(app *App) *sessions.CookieStore {
|
||||||
HttpOnly: true,
|
HttpOnly: true,
|
||||||
Secure: strings.HasPrefix(app.cfg.App.Host, "https://"),
|
Secure: strings.HasPrefix(app.cfg.App.Host, "https://"),
|
||||||
}
|
}
|
||||||
return store
|
app.sessionStore = store
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSessionFlashes(app *App, w http.ResponseWriter, r *http.Request, session *sessions.Session) ([]string, error) {
|
func getSessionFlashes(app *App, w http.ResponseWriter, r *http.Request, session *sessions.Session) ([]string, error) {
|
||||||
|
|
|
@ -98,7 +98,8 @@ func initUserPage(parentDir, path, key string) {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
func initTemplates(cfg *config.Config) error {
|
// InitTemplates loads all template files from the configured parent dir.
|
||||||
|
func InitTemplates(cfg *config.Config) error {
|
||||||
log.Info("Loading templates...")
|
log.Info("Loading templates...")
|
||||||
tmplFiles, err := ioutil.ReadDir(filepath.Join(cfg.Server.TemplatesParentDir, templatesDir))
|
tmplFiles, err := ioutil.ReadDir(filepath.Join(cfg.Server.TemplatesParentDir, templatesDir))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
Loading…
Reference in New Issue