2018-02-12 00:24:43 +00:00
|
|
|
// Package blog is a personal website and blogging app.
|
|
|
|
package blog
|
2017-10-08 04:48:58 +00:00
|
|
|
|
|
|
|
import (
|
2017-12-23 21:22:51 +00:00
|
|
|
"fmt"
|
2017-10-08 04:48:58 +00:00
|
|
|
"net/http"
|
2018-05-12 04:29:18 +00:00
|
|
|
"os"
|
2017-11-03 16:52:40 +00:00
|
|
|
"path/filepath"
|
2017-10-08 04:48:58 +00:00
|
|
|
|
|
|
|
"github.com/gorilla/mux"
|
2018-05-12 03:15:16 +00:00
|
|
|
"github.com/jinzhu/gorm"
|
2018-12-24 19:47:25 +00:00
|
|
|
"github.com/kirsle/blog/jsondb"
|
|
|
|
"github.com/kirsle/blog/jsondb/caches"
|
|
|
|
"github.com/kirsle/blog/jsondb/caches/null"
|
|
|
|
"github.com/kirsle/blog/jsondb/caches/redis"
|
|
|
|
"github.com/kirsle/blog/models/comments"
|
|
|
|
"github.com/kirsle/blog/models/posts"
|
|
|
|
"github.com/kirsle/blog/models/settings"
|
|
|
|
"github.com/kirsle/blog/models/users"
|
2018-12-24 00:21:50 +00:00
|
|
|
"github.com/kirsle/blog/src/controllers/admin"
|
|
|
|
"github.com/kirsle/blog/src/controllers/authctl"
|
|
|
|
commentctl "github.com/kirsle/blog/src/controllers/comments"
|
|
|
|
"github.com/kirsle/blog/src/controllers/contact"
|
|
|
|
postctl "github.com/kirsle/blog/src/controllers/posts"
|
|
|
|
questionsctl "github.com/kirsle/blog/src/controllers/questions"
|
|
|
|
"github.com/kirsle/blog/src/controllers/setup"
|
|
|
|
"github.com/kirsle/blog/src/log"
|
|
|
|
"github.com/kirsle/blog/src/markdown"
|
|
|
|
"github.com/kirsle/blog/src/middleware"
|
|
|
|
"github.com/kirsle/blog/src/middleware/auth"
|
2018-12-24 19:47:25 +00:00
|
|
|
"github.com/kirsle/blog/src/models"
|
2024-02-29 05:33:05 +00:00
|
|
|
"github.com/kirsle/blog/src/ratelimit"
|
2018-12-24 00:21:50 +00:00
|
|
|
"github.com/kirsle/blog/src/render"
|
|
|
|
"github.com/kirsle/blog/src/responses"
|
|
|
|
"github.com/kirsle/blog/src/sessions"
|
2017-11-24 19:56:32 +00:00
|
|
|
"github.com/shurcooL/github_flavored_markdown/gfmstyle"
|
2017-10-08 04:48:58 +00:00
|
|
|
"github.com/urfave/negroni"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Blog is the root application object that maintains the app configuration
|
|
|
|
// and helper objects.
|
|
|
|
type Blog struct {
|
2017-10-31 16:42:15 +00:00
|
|
|
Debug bool
|
|
|
|
|
2017-10-08 04:48:58 +00:00
|
|
|
// DocumentRoot is the core static files root; UserRoot masks over it.
|
|
|
|
DocumentRoot string
|
|
|
|
UserRoot string
|
|
|
|
|
2018-05-12 03:15:16 +00:00
|
|
|
db *gorm.DB
|
|
|
|
jsonDB *jsondb.DB
|
|
|
|
Cache caches.Cacher
|
2017-11-03 16:52:40 +00:00
|
|
|
|
2017-10-08 04:48:58 +00:00
|
|
|
// Web app objects.
|
2018-02-10 19:14:42 +00:00
|
|
|
n *negroni.Negroni // Negroni middleware manager
|
|
|
|
r *mux.Router // Router
|
2017-10-08 04:48:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// New initializes the Blog application.
|
|
|
|
func New(documentRoot, userRoot string) *Blog {
|
2018-05-12 04:29:18 +00:00
|
|
|
// Initialize the SQLite database.
|
|
|
|
if _, err := os.Stat(filepath.Join(userRoot, ".private")); os.IsNotExist(err) {
|
|
|
|
os.MkdirAll(filepath.Join(userRoot, ".private"), 0755)
|
|
|
|
}
|
2018-05-12 03:15:16 +00:00
|
|
|
db, err := gorm.Open("sqlite3", filepath.Join(userRoot, ".private", "database.sqlite"))
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
2018-02-10 18:08:45 +00:00
|
|
|
return &Blog{
|
2017-10-08 04:48:58 +00:00
|
|
|
DocumentRoot: documentRoot,
|
|
|
|
UserRoot: userRoot,
|
2018-05-12 03:15:16 +00:00
|
|
|
db: db,
|
|
|
|
jsonDB: jsondb.New(filepath.Join(userRoot, ".private")),
|
2017-12-23 21:22:51 +00:00
|
|
|
Cache: null.New(),
|
2017-11-08 03:48:22 +00:00
|
|
|
}
|
2018-02-10 18:08:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Run quickly configures and starts the HTTP server.
|
|
|
|
func (b *Blog) Run(address string) {
|
|
|
|
b.Configure()
|
|
|
|
b.SetupHTTP()
|
|
|
|
b.ListenAndServe(address)
|
|
|
|
}
|
2017-11-07 17:53:02 +00:00
|
|
|
|
2018-02-10 18:08:45 +00:00
|
|
|
// Configure initializes (or reloads) the blog's configuration, and binds the
|
|
|
|
// settings in sub-packages.
|
|
|
|
func (b *Blog) Configure() {
|
2018-05-12 03:15:16 +00:00
|
|
|
if b.Debug {
|
|
|
|
b.db.LogMode(true)
|
|
|
|
}
|
2017-11-08 03:48:22 +00:00
|
|
|
// Load the site config, or start with defaults if not found.
|
2018-05-12 03:15:16 +00:00
|
|
|
settings.DB = b.jsonDB
|
2017-11-08 03:48:22 +00:00
|
|
|
config, err := settings.Load()
|
|
|
|
if err != nil {
|
|
|
|
config = settings.Defaults()
|
2017-10-08 04:48:58 +00:00
|
|
|
}
|
2017-11-03 16:52:40 +00:00
|
|
|
|
2018-02-10 18:08:45 +00:00
|
|
|
// Bind configs in sub-packages.
|
|
|
|
render.UserRoot = &b.UserRoot
|
|
|
|
render.DocumentRoot = &b.DocumentRoot
|
|
|
|
|
2017-11-08 03:48:22 +00:00
|
|
|
// Initialize the session cookie store.
|
2018-02-10 19:14:42 +00:00
|
|
|
sessions.SetSecretKey([]byte(config.Security.SecretKey))
|
2017-11-08 03:48:22 +00:00
|
|
|
users.HashCost = config.Security.HashCost
|
|
|
|
|
|
|
|
// Initialize the rest of the models.
|
2018-05-12 03:15:16 +00:00
|
|
|
posts.DB = b.jsonDB
|
|
|
|
users.DB = b.jsonDB
|
|
|
|
comments.DB = b.jsonDB
|
2018-12-24 19:47:25 +00:00
|
|
|
models.UseDB(b.db)
|
2017-11-07 17:01:02 +00:00
|
|
|
|
2017-12-23 21:22:51 +00:00
|
|
|
// Redis cache?
|
|
|
|
if config.Redis.Enabled {
|
|
|
|
addr := fmt.Sprintf("%s:%d", config.Redis.Host, config.Redis.Port)
|
|
|
|
log.Info("Connecting to Redis at %s/%d", addr, config.Redis.DB)
|
|
|
|
cache, err := redis.New(
|
|
|
|
addr,
|
|
|
|
config.Redis.DB,
|
|
|
|
config.Redis.Prefix,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
log.Error("Redis init error: %s", err.Error())
|
|
|
|
} else {
|
2018-02-10 18:08:45 +00:00
|
|
|
b.Cache = cache
|
2018-05-12 03:15:16 +00:00
|
|
|
b.jsonDB.Cache = cache
|
2018-02-10 03:01:56 +00:00
|
|
|
markdown.Cache = cache
|
2024-02-29 05:33:05 +00:00
|
|
|
ratelimit.Cache = cache
|
2017-12-23 21:22:51 +00:00
|
|
|
}
|
|
|
|
}
|
2018-02-10 23:16:30 +00:00
|
|
|
|
|
|
|
b.registerErrors()
|
2018-02-10 18:08:45 +00:00
|
|
|
}
|
2017-12-23 21:22:51 +00:00
|
|
|
|
2018-02-10 18:08:45 +00:00
|
|
|
// SetupHTTP initializes the Negroni middleware engine and registers routes.
|
|
|
|
func (b *Blog) SetupHTTP() {
|
2017-11-08 03:48:22 +00:00
|
|
|
// Initialize the router.
|
2017-10-08 04:48:58 +00:00
|
|
|
r := mux.NewRouter()
|
2018-02-10 22:36:21 +00:00
|
|
|
setup.Register(r)
|
|
|
|
authctl.Register(r)
|
|
|
|
admin.Register(r, b.MustLogin)
|
2018-02-10 23:07:10 +00:00
|
|
|
contact.Register(r)
|
|
|
|
postctl.Register(r, b.MustLogin)
|
|
|
|
commentctl.Register(r)
|
2018-12-24 19:47:25 +00:00
|
|
|
questionsctl.Register(r, b.MustLogin)
|
2017-11-15 14:55:15 +00:00
|
|
|
|
2017-11-24 19:56:32 +00:00
|
|
|
// GitHub Flavored Markdown CSS.
|
|
|
|
r.Handle("/css/gfm.css", http.StripPrefix("/css", http.FileServer(gfmstyle.Assets)))
|
|
|
|
|
2018-02-10 18:08:45 +00:00
|
|
|
r.PathPrefix("/").HandlerFunc(b.PageHandler)
|
|
|
|
r.NotFoundHandler = http.HandlerFunc(b.PageHandler)
|
2017-10-08 04:48:58 +00:00
|
|
|
|
|
|
|
n := negroni.New(
|
|
|
|
negroni.NewRecovery(),
|
|
|
|
negroni.NewLogger(),
|
2018-02-10 19:14:42 +00:00
|
|
|
negroni.HandlerFunc(sessions.Middleware),
|
2018-02-10 23:07:10 +00:00
|
|
|
negroni.HandlerFunc(middleware.CSRF(responses.Forbidden)),
|
2018-02-10 19:14:42 +00:00
|
|
|
negroni.HandlerFunc(auth.Middleware),
|
2018-04-12 02:34:37 +00:00
|
|
|
negroni.HandlerFunc(middleware.AgeGate(authctl.AgeGate)),
|
2017-10-08 04:48:58 +00:00
|
|
|
)
|
|
|
|
n.UseHandler(r)
|
|
|
|
|
2017-11-15 14:55:15 +00:00
|
|
|
// Keep references handy elsewhere in the app.
|
2018-02-10 18:08:45 +00:00
|
|
|
b.n = n
|
|
|
|
b.r = r
|
2017-10-08 04:48:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ListenAndServe begins listening on the given bind address.
|
|
|
|
func (b *Blog) ListenAndServe(address string) {
|
|
|
|
log.Info("Listening on %s", address)
|
|
|
|
http.ListenAndServe(address, b.n)
|
|
|
|
}
|
2018-02-10 22:36:21 +00:00
|
|
|
|
|
|
|
// MustLogin handles errors from the LoginRequired middleware by redirecting
|
|
|
|
// the user to the login page.
|
|
|
|
func (b *Blog) MustLogin(w http.ResponseWriter, r *http.Request) {
|
|
|
|
responses.Redirect(w, "/login?next="+r.URL.Path)
|
|
|
|
}
|