Version 0.99.9-SNAPSHOT

This commit is contained in:
Simon Brooke 2017-09-09 21:50:54 +01:00
parent d4ac9b3586
commit c76c821770
9 changed files with 135 additions and 109 deletions

View file

@ -1,4 +1,4 @@
(defproject smeagol "0.99.8" (defproject smeagol "0.99.9-SNAPSHOT"
:description "A simple Git-backed Wiki inspired by Gollum" :description "A simple Git-backed Wiki inspired by Gollum"
:url "https://github.com/simon-brooke/smeagol" :url "https://github.com/simon-brooke/smeagol"
:license {:name "GNU General Public License,version 2.0 or (at your option) any later version" :license {:name "GNU General Public License,version 2.0 or (at your option) any later version"
@ -14,6 +14,7 @@
[com.taoensso/tower "3.0.2" :exclusions [com.taoensso/encore]] [com.taoensso/tower "3.0.2" :exclusions [com.taoensso/encore]]
[crypto-password "0.2.0"] [crypto-password "0.2.0"]
[environ "1.1.0"] [environ "1.1.0"]
[hiccup "1.0.5"]
[im.chit/cronj "1.4.4"] [im.chit/cronj "1.4.4"]
[lib-noir "0.9.9" :exclusions [org.clojure/tools.reader]] [lib-noir "0.9.9" :exclusions [org.clojure/tools.reader]]
[markdown-clj "0.9.99" :exclusions [com.keminglabs/cljx]] [markdown-clj "0.9.99" :exclusions [com.keminglabs/cljx]]

View file

@ -3,25 +3,25 @@ To deploy Smeagol as a stand-alone application, either download the jar file for
lein bower install lein bower install
lein ring uberjar lein ring uberjar
This will create a jar file in the `target` directory, named `smeagol-`*VERSION*`-standalone.jar`.
Smeagol cannot access either its configuration or its content from the jar file. Consequently you should set up three environment variables: This will create a jar file in the `target` directory, named `smeagol-`*VERSION*`-standalone.jar`.
1. **SMEAGOL_CONFIG** should be the full or relative pathname of a Smeagol [[Configuration]] file; Smeagol cannot access either its configuration or its content from the jar file, as otherwise they would not be editable. Consequently you should set up three environment variables:
2. **SMEAGOL\_CONTENT\_DIR** should be the full or relative pathname of the directory from which Smeagol should serve content (which may initially be empty, but must be writable by the process which runs Smeagol)'
3. **SMEAGOL_PASSWD** should be the full or relative pathname of a Smeagol Passwd file - see [[Security and authentication]]. This file must contain an entry for at least your initial user, and, if you want to administer users through the user interface, must be writable by the process which runs Smeagol); 1. `SMEAGOL_CONFIG` should be the full or relative pathname of a Smeagol [[Configuration]] file;
2. `SMEAGOL_CONTENT_DIR` should be the full or relative pathname of the directory from which Smeagol should serve content (which may initially be empty, but must be writable by the process which runs Smeagol)'
3. `SMEAGOL_PASSWD` should be the full or relative pathname of a Smeagol Passwd file - see [[Security and authentication]]. This file must contain an entry for at least your initial user, and, if you want to administer users through the user interface, must be writable by the process which runs Smeagol;
You can run the jar file with: You can run the jar file with:
java -jar smeagol-VERSION-standalone.jar java -jar smeagol-VERSION-standalone.jar
## Deploying within a servlet container ## Deploying within a servlet container
To deploy Smeagol within a servlet container, either download the jar file for the release you want to deploy, or clone the source and compile it with: To deploy Smeagol within a servlet container, either download the jar file for the release you want to deploy, or clone the source and compile it with:
lein bower install lein bower install
lein ring uberwar lein ring uberwar
This will create a war file in the `target` directory, named `smeagol-`*VERSION*`-standalone.war`. Deploy this to your servlet container in the normal way; details will depend on your container. Instructions for Tomcat are [here](https://tomcat.apache.org/tomcat-8.0-doc/deployer-howto.html). This will create a war file in the `target` directory, named `smeagol-`*VERSION*`-standalone.war`. Deploy this to your servlet container in the normal way; details will depend on your container. Instructions for Tomcat are [here](https://tomcat.apache.org/tomcat-8.0-doc/deployer-howto.html).
The problem with this is that unless the environment variables (see above) were already set up in the environment of the servlet container at the time when the servlet container were launched, Smeagol will run with its built-in defaults. This will run perfectly satisfactorily provided your servlet container is configured to unpack war files, which most are. The problem with this is that unless the environment variables (see above) were already set up in the environment of the servlet container at the time when the servlet container were launched, Smeagol will run with its built-in defaults. This will run perfectly satisfactorily provided your servlet container is configured to unpack war files, which most are.

View file

@ -9,11 +9,11 @@ To start a web server for the application during development, run:
lein bower install lein bower install
lein ring server lein ring server
This should start a development server, and open a new window or tab in your default browser with the default page of the wiki loaded into it. This should start a development server, and open a new window or tab in your default browser with the default page of the wiki loaded into it.
## Editing ## Editing
I generally use [LightTable]() as my `Clojure` editor, but it doesn't really matter what you use; if you run Smeagol as described above, then all changes you make in the code (and save) will instantly be applied to the running system. This makes for a productive development environment. I generally use [LightTable](http://lighttable.com/) as my `Clojure` editor, but it doesn't really matter what you use; if you run Smeagol as described above, then all changes you make in the code (and save) will instantly be applied to the running system. This makes for a productive development environment.
## Documentation ## Documentation
It is my intention that the code should be sufficiently well documented to be easy to understand. Documentation may be generated from the code by running It is my intention that the code should be sufficiently well documented to be easy to understand. Documentation may be generated from the code by running

View file

@ -46,4 +46,7 @@
(def config (def config
"The actual configuration, as a map." "The actual configuration, as a map."
(read-string (slurp config-file-path))) (try
(read-string (slurp config-file-path))
(catch Exception any
(throw (Exception. "Could not load configuration" any)))))

View file

@ -1,7 +1,8 @@
(ns ^{:doc "Set up, configure, and clean up after the wiki server." (ns ^{:doc "Set up, configure, and clean up after the wiki server."
:author "Simon Brooke"} :author "Simon Brooke"}
smeagol.handler smeagol.handler
(:require [compojure.core :refer [defroutes]] (:require [clojure.java.io :as cjio]
[compojure.core :refer [defroutes]]
[compojure.route :as route] [compojure.route :as route]
[cronj.core :as cronj] [cronj.core :as cronj]
[environ.core :refer [env]] [environ.core :refer [env]]
@ -46,23 +47,6 @@
(route/resources "/") (route/resources "/")
(route/not-found "Not Found")) (route/not-found "Not Found"))
(defn init
"init will be called once when
app is deployed as a servlet on
an app server such as Tomcat
put any initialization code here"
[]
(timbre/merge-config!
{:appenders
{:rotor (rotor/rotor-appender
{:path "smeagol.log"
:max-size (* 512 1024)
:backlog 10})}})
(if (env :dev) (parser/cache-off!))
;;start the expired session cleanup job
(cronj/start! session-manager/cleanup-job)
(timbre/info "\n-=[ smeagol started successfully"
(when (env :dev) "using the development profile") "]=-"))
(defn destroy (defn destroy
"destroy will be called when your application "destroy will be called when your application
@ -72,28 +56,53 @@
(cronj/shutdown! session-manager/cleanup-job) (cronj/shutdown! session-manager/cleanup-job)
(timbre/info "shutdown complete!")) (timbre/info "shutdown complete!"))
(defn init
"init will be called once when
app is deployed as a servlet on
an app server such as Tomcat
put any initialization code here"
[]
(try
(timbre/merge-config!
{:appenders
{:rotor (rotor/rotor-appender
{:path "smeagol.log"
:max-size (* 512 1024)
:backlog 10})}})
(cronj/start! session-manager/cleanup-job)
(if (env :dev) (parser/cache-off!))
;;start the expired session cleanup job
(timbre/info "\n-=[ smeagol started successfully"
(when (env :dev) "using the development profile") "]=-")
(catch Exception any
(timbre/error "Failure during startup" any)
(destroy))))
;; timeout sessions after 30 minutes ;; timeout sessions after 30 minutes
(def session-defaults (def session-defaults
{:timeout (* 60 30) {:timeout (* 60 30)
:timeout-response (redirect "/")}) :timeout-response (redirect "/")})
(defn- mk-defaults
"set to true to enable XSS protection" (defn- make-defaults
[xss-protection?] "set to true to enable XSS protection"
(-> site-defaults [xss-protection?]
(update-in [:session] merge session-defaults) (-> site-defaults
(assoc-in [:security :anti-forgery] xss-protection?))) (update-in [:session] merge session-defaults)
(assoc-in [:security :anti-forgery] xss-protection?)))
(def app (app-handler (def app (app-handler
;; add your application routes here ;; add your application routes here
[wiki-routes base-routes] [wiki-routes base-routes]
;; add custom middleware here ;; add custom middleware here
:middleware (load-middleware) :middleware (load-middleware)
:ring-defaults (mk-defaults false) :ring-defaults (make-defaults true)
;; add access rules here ;; add access rules here
:access-rules [{:redirect "/auth" :access-rules [{:redirect "/auth"
:rule user-access}] :rule user-access}]
;; serialize/deserialize the following data formats ;; serialize/deserialize the following data formats
;; available formats: ;; available formats:
;; :json :json-kw :yaml :yaml-kw :edn :yaml-in-html ;; :json :json-kw :yaml :yaml-kw :edn :yaml-in-html
:formats [:json-kw :edn :transit-json])) :formats [:json-kw :edn :transit-json]))

View file

@ -2,12 +2,16 @@
(ns ^{:doc "Render a page as HTML." (ns ^{:doc "Render a page as HTML."
:author "Simon Brooke"} :author "Simon Brooke"}
smeagol.layout smeagol.layout
(:require [clojure.string :as s] (:require [clojure.java.io :as cjio]
[clojure.string :as s]
[compojure.response :refer [Renderable]] [compojure.response :refer [Renderable]]
[environ.core :refer [env]] [environ.core :refer [env]]
[hiccup.core :refer [html]]
[ring.util.anti-forgery :refer [anti-forgery-field]] [ring.util.anti-forgery :refer [anti-forgery-field]]
[ring.util.response :refer [content-type response]] [ring.util.response :refer [content-type response]]
[selmer.parser :as parser] [selmer.parser :as parser]
[smeagol.configuration :refer [config]]
[smeagol.sanity :refer :all]
[smeagol.util :as util] [smeagol.util :as util]
[taoensso.timbre :as timbre])) [taoensso.timbre :as timbre]))
@ -51,23 +55,30 @@
(deftype RenderableTemplate [template params] (deftype RenderableTemplate [template params]
Renderable Renderable
(render [this request] (render [this request]
(content-type (try
(->> (assoc params (content-type
(keyword (s/replace template #".html" "-selected")) "active" (->> (assoc params
:i18n (util/get-messages request) (keyword (s/replace template #".html" "-selected")) "active"
:dev (env :dev) :i18n (util/get-messages request)
:servlet-context :dev (env :dev)
(if-let [context (:servlet-context request)] :servlet-context
;; If we're not inside a serlvet environment (for (if-let [context (:servlet-context request)]
;; example when using mock requests), then ;; If we're not inside a serlvet environment (for
;; .getContextPath might not exist ;; example when using mock requests), then
(try (.getContextPath context) ;; .getContextPath might not exist
(try (.getContextPath context)
(catch IllegalArgumentException _ context)))) (catch IllegalArgumentException _ context))))
(parser/render-file (str template-path template)) (parser/render-file (str template-path template))
response) response)
"text/html; charset=utf-8"))) "text/html; charset=utf-8")
(catch Exception any
(show-sanity-check-error any)))))
(defn render [template & [params]] (defn render
(RenderableTemplate. template params)) [template & [params]]
(try
(RenderableTemplate. template params)
(catch Exception any
(show-sanity-check-error any))))

View file

@ -40,13 +40,11 @@
(def development-middleware (def development-middleware
[wrap-error-page [wrap-error-page
wrap-exceptions wrap-exceptions])
wrap-anti-forgery])
(def production-middleware (def production-middleware
[#(wrap-internal-error % :log (fn [e] (timbre/error e))) [#(wrap-internal-error % :log (fn [e] (timbre/error e)))])
wrap-anti-forgery])
(defn load-middleware [] (defn load-middleware []

View file

@ -17,6 +17,7 @@
[smeagol.history :as hist] [smeagol.history :as hist]
[smeagol.layout :as layout] [smeagol.layout :as layout]
[smeagol.routes.admin :as admin] [smeagol.routes.admin :as admin]
[smeagol.sanity :refer [show-sanity-check-error]]
[smeagol.util :as util] [smeagol.util :as util]
[smeagol.uploads :as ul] [smeagol.uploads :as ul]
[taoensso.timbre :as timbre])) [taoensso.timbre :as timbre]))
@ -83,6 +84,7 @@
([request] ([request]
(edit-page request (util/get-message :default-page-title request) ".md" "edit.html" "_edit-side-bar.md")) (edit-page request (util/get-message :default-page-title request) ".md" "edit.html" "_edit-side-bar.md"))
([request default suffix template side-bar] ([request default suffix template side-bar]
(show-sanity-check-error)
(let [params (keywordize-keys (:params request)) (let [params (keywordize-keys (:params request))
src-text (:src params) src-text (:src params)
page (or (:page params) default) page (or (:page params) default)
@ -113,21 +115,23 @@
(defn wiki-page (defn wiki-page
"Render the markdown page specified in this `request`, if any. If none found, redirect to edit-page" "Render the markdown page specified in this `request`, if any. If none found, redirect to edit-page"
[request] [request]
(let [params (keywordize-keys (:params request)) (or
page (or (:page params) (util/get-message :default-page-title "Introduction" request)) (show-sanity-check-error)
file-name (str page ".md") (let [params (keywordize-keys (:params request))
file-path (cjio/file util/content-dir file-name) page (or (:page params) (util/get-message :default-page-title "Introduction" request))
exists? (.exists (clojure.java.io/as-file file-path))] file-name (str page ".md")
(cond exists? file-path (cjio/file util/content-dir file-name)
(do exists? (.exists (clojure.java.io/as-file file-path))]
(timbre/info (format "Showing page '%s' from file '%s'" page file-path)) (cond exists?
(layout/render "wiki.html" (do
(merge (util/standard-params request) (timbre/info (format "Showing page '%s' from file '%s'" page file-path))
{:title page (layout/render "wiki.html"
:page page (merge (util/standard-params request)
:content (md->html (slurp file-path)) {:title page
:editable true}))) :page page
true (response/redirect (str "/edit?page=" page))))) :content (md->html (slurp file-path))
:editable true})))
true (response/redirect (str "/edit?page=" page))))))
(defn history-page (defn history-page
@ -202,27 +206,29 @@
(defn auth-page (defn auth-page
"Render the auth page" "Render the auth page"
[request] [request]
(let [params (keywordize-keys (:form-params request)) (or
username (:username params) (show-sanity-check-error)
password (:password params) (let [params (keywordize-keys (:form-params request))
action (:action params) username (:username params)
user (session/get :user) password (:password params)
redirect-to (or (:redirect-to params) "/wiki")] action (:action params)
(cond user (session/get :user)
(= action (util/get-message :logout-label request)) redirect-to (or (:redirect-to params) "/wiki")]
(do (cond
(timbre/info (str "User " user " logging out")) (= action (util/get-message :logout-label request))
(session/remove! :user) (do
(response/redirect redirect-to)) (timbre/info (str "User " user " logging out"))
(and username password (auth/authenticate username password)) (session/remove! :user)
(do (response/redirect redirect-to))
(session/put! :user username) (and username password (auth/authenticate username password))
(response/redirect redirect-to)) (do
true (session/put! :user username)
(layout/render "auth.html" (response/redirect redirect-to))
(merge (util/standard-params request) true
{:title (if user (str (util/get-message :logout-link request) " " user) (util/get-message :login-link request)) (layout/render "auth.html"
:redirect-to ((:headers request) "referer")}))))) (merge (util/standard-params request)
{:title (if user (str (util/get-message :logout-link request) " " user) (util/get-message :login-link request))
:redirect-to ((:headers request) "referer")}))))))
(defn passwd-page (defn passwd-page

View file

@ -51,18 +51,16 @@
:version (System/getProperty "smeagol.version")})) :version (System/getProperty "smeagol.version")}))
(defn raw-get-messages (defn- raw-get-messages
"Return the most acceptable messages collection we have given the "Return the most acceptable messages collection we have given the
`Accept-Language` header in this `request`." `Accept-Language` header in this `request`."
[request] [request]
(merge (merge
(i18n/get-messages (i18n/get-messages
((:headers request) "accept-language") ((:headers request) "accept-language")
;; (cjio/file (io/resource-path) "i18n")
"i18n" "i18n"
"en-GB") "en-GB")
config) config))
)
(def get-messages (memoize raw-get-messages)) (def get-messages (memoize raw-get-messages))