diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..a4bcedb --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: lein with-profile production trampoline ring server diff --git a/README.md b/README.md index 2bc042c..14fbdc4 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,29 @@ -smeagol -======= +# Welcome to Smeagol -Simple Wiki engine inspired by Gollum +Smeagol is a simple Git-backed Wiki inspired by [Gollum](https://github.com/gollum/gollum/wiki). + +## Prerequisites + +You will need [Leiningen][1] 2.0 or above installed. + +[1]: https://github.com/technomancy/leiningen + +## Running + +To start a web server for the application, run: + + lein ring server + +Alternatively, if you want to deploy to a servlet container, the simplest thing is to run: + + lein ring uberwar + +(a command which I'm sure Smeagol would entirely appreciate) and deploy the resulting war file. + +## TODO + +The editor is at present very primitive - right back from the beginnings of the Web. It would be nice to have a rich embedded editor like [Hallo](https://github.com/bergie/hallo) or [Aloha](http://aloha-editor.org/Content.Node/index.html) but I havenven't (yet) had time to integrate them! + +## License + +Copyright © 2014 Simon Brooke diff --git a/project.clj b/project.clj new file mode 100644 index 0000000..46b7c28 --- /dev/null +++ b/project.clj @@ -0,0 +1,38 @@ +(defproject smeagol "0.1.0-SNAPSHOT" + :description "A simple Git-backed Wiki inspired by Gollum" + :url "http://example.com/FIXME" + :dependencies [[org.clojure/clojure "1.6.0"] + [lib-noir "0.9.4"] + [ring-server "0.3.1"] + [selmer "0.7.2"] + [com.taoensso/timbre "3.3.1"] + [com.taoensso/tower "3.0.2"] + [markdown-clj "0.9.55" + :exclusions [com.keminglabs/cljx]] + [environ "1.0.0"] + [im.chit/cronj "1.4.2"] + [noir-exception "0.2.2"] + [prone "0.6.0"]] + + :repl-options {:init-ns smeagol.repl} + :jvm-opts ["-server"] + :plugins [[lein-ring "0.8.13"] + [lein-environ "1.0.0"] + [lein-ancient "0.5.5"]] + :ring {:handler smeagol.handler/app + :init smeagol.handler/init + :destroy smeagol.handler/destroy} + :profiles + {:uberjar {:omit-source true + :env {:production true} + :aot :all} + :production {:ring {:open-browser? false + :stacktraces? false + :auto-reload? false}} + :dev {:dependencies [[ring-mock "0.1.5"] + [ring/ring-devel "1.3.1"] + [pjstadig/humane-test-output "0.6.0"]] + :injections [(require 'pjstadig.humane-test-output) + (pjstadig.humane-test-output/activate!)] + :env {:dev true}}} + :min-lein-version "2.0.0") diff --git a/resources/public/content/Introduction.md b/resources/public/content/Introduction.md new file mode 100644 index 0000000..d08ede7 --- /dev/null +++ b/resources/public/content/Introduction.md @@ -0,0 +1,21 @@ +# Welcome to Smeagol! + +Smeagol is a simple Wiki engine inspired by [Gollum](https://github.com/gollum/gollum/wiki). Gollum is a Wiki engine written in Ruby, which uses a number of simple text formats including [Markdown](http://daringfireball.net/projects/markdown/), which uses [Git](http://git-scm.com/) to provide versioning and backup. I needed a new Wiki for a project and thought Gollum would be ideal - but unfortunately it doesn't provide user authentication, which I needed, and it was simpler for me to reimplement the bits I did need in Clojure than to modify Gollum. + +So at this stage Smeagol is a Wiki engine written in Clojure which uses Markdown as its text format, which does have user authentication, and which will soon use Git as its versioning and backup system. + +## Markup syntax + +Smeagol uses the Markdown format as provided by [markdown-clj](https://github.com/yogthos/markdown-clj), with the addition that anything enclosed in double square brackets, \[\[like this\]\], will be treated as a link into the wiki. + +## Security and authentication + +Not done yet. + +## Todo + +Git integration! + +## Editing the framing content + +You can edit the [[\_left-bar]], the [[\_edit-left-bar]], and the [[\_header]]. diff --git a/resources/public/content/_edit-left-bar.md b/resources/public/content/_edit-left-bar.md new file mode 100644 index 0000000..d491cbc --- /dev/null +++ b/resources/public/content/_edit-left-bar.md @@ -0,0 +1,13 @@ +####Formatting hints ++ \# Main heading ++ \#\# Second level heading ++ \#\#\# Third level heading ++ \[\[Link within this wiki\]\] ++ \[Link outside this wiki\] \(http://url.goes.here/\) ++ \!\[image alt text\]\(http://url.of.image\) ++ \+ (at start of line) ordinary bulleted lists ++ \1\. (at start of line) numbered lists ++ \**bold*\* ++ \__italic_\_ + +More documentation [here](http://daringfireball.net/projects/markdown/syntax) \ No newline at end of file diff --git a/resources/public/content/_header.md b/resources/public/content/_header.md new file mode 100644 index 0000000..b5d7dc2 --- /dev/null +++ b/resources/public/content/_header.md @@ -0,0 +1 @@ +This is the header. There isn't yet much in it. You could [edit](edit?content=_header) it to provide internal navigation or branding. diff --git a/resources/public/content/_left-bar.md b/resources/public/content/_left-bar.md new file mode 100644 index 0000000..5586090 --- /dev/null +++ b/resources/public/content/_left-bar.md @@ -0,0 +1 @@ +This is the left bar. There's nothing in it yet. You could [edit](edit?content=_left-bar) it to provide internal navigation or branding. diff --git a/resources/public/css/screen.css b/resources/public/css/screen.css new file mode 100644 index 0000000..d759510 --- /dev/null +++ b/resources/public/css/screen.css @@ -0,0 +1,6 @@ +html, +body { + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + height: 100%; + padding-top: 40px; +} diff --git a/resources/public/css/standard.css b/resources/public/css/standard.css new file mode 100644 index 0000000..6fa4869 --- /dev/null +++ b/resources/public/css/standard.css @@ -0,0 +1,154 @@ +body { + margin: 0; + padding: 0; + font-family: sans-serif; +} + +/* ids generally in document order */ + +/* top-of-page navigation, not editable, provided by Smeagol */ +#nav{ + margin: 0; + padding: 0; + top: 0; + width: 100%; + _position: absolute; + _top: expression(document.documentElement.scrollTop); + z-index: 149; + background:rgba(40,40,40,0.8); +} + +/* only needed for fly-out menu effect on tablet and phone stylesheets */ +#nav-icon { + display: none; +} + +#nav ul li { + padding: 0; + margin: 0; + display: inline; +} + +#nav ul li a { + color: white; + text-decoration: none; + font-weight: bold; + padding: 0.1em 0.75em; + margin: 0; +} + +#nav ul li.active a { background: silver;} +li.nav-item a:hover { background: rgb( 240, 240, 240) } +li.nav-item a:active { background: gray; color: white; } + +/* Overall container div, holds all content of page. Yes, I know it shouldn't have fixed width */ +#main-container{ + clear: both; +/* width:100%; */ +} + + +/* header for all pages in the Wiki - editable, provided by users. Within main-container */ +#header { + width:100%; + background-color: gray; + color: white; +} + +/* left bar for all pages in the Wiki - editable, provided by users. Within main-container */ +#left-bar { + width: 17%; + height: 100%; + float: left; +} + +/* content of the current in the Wiki - editable, provided by users. Within main-container */ +#content { + border: thin solid silver; + width: 80%; + float: right; +} + + +/* footer of the page - not-editable, provided by Smeagol */ +#footer { + clear: both; + font-size: smaller; + padding: 0 2em; + text-align: center; + color:white; + background:rgba(196,196,196,0.95); + width: 100%; + margin: 0; + bottom:0; + position:fixed; + z-index:150; + _position:absolute; + _top:expression(eval(document.documentElement.scrollTop+ + (document.documentElement.clientHeight-this.offsetHeight))); +} + +.error { + background-color: red; + color: white; +} + +.widget { + background-color: silver; + border: thin solid white; + margin-top: 0; + margin-bottom: 0; +} + +.wiki { + margin: 0; +} + +div.error { + width: 100%; +} + +form { + border: thin solid silver; +} + +div.content, form, p, pre, ul, ol, dl, menu, h1, h2, h3, h4, h5 { + padding: 0.25em 10%; +} + +input { + background-color: white; +} + +input.submit { + background-color: green; +} + +input.required:after { + content: " \*"; + color: red; +} + +label { + width: 30em; + min-width: 20em; + border-right: thin solid gray; +} + +menu li { + display: inline; +} + +menu li::before { + content: "|| "; +} + +table.music-ruled tr:nth-child(odd) { + background-color: silver; +} + +th, td { + text-align: left; + padding: 0 0.25em; +} + diff --git a/resources/public/favicon.ico b/resources/public/favicon.ico new file mode 100644 index 0000000..ca89d76 Binary files /dev/null and b/resources/public/favicon.ico differ diff --git a/resources/public/favicon.png b/resources/public/favicon.png new file mode 100644 index 0000000..8736fa4 Binary files /dev/null and b/resources/public/favicon.png differ diff --git a/resources/public/fonts/glyphicons-halflings-regular.eot b/resources/public/fonts/glyphicons-halflings-regular.eot new file mode 100644 index 0000000..78b119f Binary files /dev/null and b/resources/public/fonts/glyphicons-halflings-regular.eot differ diff --git a/resources/public/fonts/glyphicons-halflings-regular.svg b/resources/public/fonts/glyphicons-halflings-regular.svg new file mode 100644 index 0000000..83f74b2 --- /dev/null +++ b/resources/public/fonts/glyphicons-halflings-regular.svg @@ -0,0 +1,229 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/public/fonts/glyphicons-halflings-regular.ttf b/resources/public/fonts/glyphicons-halflings-regular.ttf new file mode 100644 index 0000000..ba41d0f Binary files /dev/null and b/resources/public/fonts/glyphicons-halflings-regular.ttf differ diff --git a/resources/public/fonts/glyphicons-halflings-regular.woff b/resources/public/fonts/glyphicons-halflings-regular.woff new file mode 100644 index 0000000..60dd28d Binary files /dev/null and b/resources/public/fonts/glyphicons-halflings-regular.woff differ diff --git a/resources/public/img/clojure-icon.gif b/resources/public/img/clojure-icon.gif new file mode 100644 index 0000000..84eee16 Binary files /dev/null and b/resources/public/img/clojure-icon.gif differ diff --git a/resources/public/img/github-logo-transparent.png b/resources/public/img/github-logo-transparent.png new file mode 100644 index 0000000..6a37959 Binary files /dev/null and b/resources/public/img/github-logo-transparent.png differ diff --git a/resources/public/img/smeagol.png b/resources/public/img/smeagol.png new file mode 100644 index 0000000..09787cb Binary files /dev/null and b/resources/public/img/smeagol.png differ diff --git a/resources/public/img/threelines.png b/resources/public/img/threelines.png new file mode 100644 index 0000000..f26babb Binary files /dev/null and b/resources/public/img/threelines.png differ diff --git a/resources/public/md/docs.md b/resources/public/md/docs.md new file mode 100644 index 0000000..267f874 --- /dev/null +++ b/resources/public/md/docs.md @@ -0,0 +1,20 @@ + +### Managing Your Middleware + +Request middleware functions are located under the `smeagol.middleware` namespace. +A request logging helper called `log-request` has already been defined for you there. + +This namespace also defines two vectors for organizing the middleware called `development-middleware` and `production-middleware`. +Any middleware that you only wish to run in development mode, such as `log-request`, should be added to the first vector. + +### Here are some links to get started + +1. [HTML templating](http://www.luminusweb.net/docs/html_templating.md) +2. [Accessing the database](http://www.luminusweb.net/docs/database.md) +3. [Serving static resources](http://www.luminusweb.net/docs/static_resources.md) +4. [Setting response types](http://www.luminusweb.net/docs/responses.md) +5. [Defining routes](http://www.luminusweb.net/docs/routes.md) +6. [Adding middleware](http://www.luminusweb.net/docs/middleware.md) +7. [Sessions and cookies](http://www.luminusweb.net/docs/sessions_cookies.md) +8. [Security](http://www.luminusweb.net/docs/security.md) +9. [Deploying the application](http://www.luminusweb.net/docs/deployment.md) diff --git a/resources/templates/about.html b/resources/templates/about.html new file mode 100644 index 0000000..9295dec --- /dev/null +++ b/resources/templates/about.html @@ -0,0 +1,4 @@ +{% extends "templates/base.html" %} +{% block content %} +

this is the story of smeagol... work in progress

+{% endblock %} diff --git a/resources/templates/base.html b/resources/templates/base.html new file mode 100644 index 0000000..68c92dd --- /dev/null +++ b/resources/templates/base.html @@ -0,0 +1,48 @@ + + + + {{title}} + + {{title}} + + + + + + + + + + +
+ {% if message %} +
+

{{message}}

+
+ {% endif %} + {% if error %} +
+

{{error}}

+
+ {% endif %} + + {% block content %} + {% endblock %} +
+ + + + diff --git a/resources/templates/edit.html b/resources/templates/edit.html new file mode 100644 index 0000000..62f0288 --- /dev/null +++ b/resources/templates/edit.html @@ -0,0 +1,20 @@ +{% extends "templates/base.html" %} +{% block content %} + +
+ {{left-bar|safe}} +
+
+
+ + +

+ + +

+
+
+{% endblock %} diff --git a/resources/templates/wiki.html b/resources/templates/wiki.html new file mode 100644 index 0000000..b2159f2 --- /dev/null +++ b/resources/templates/wiki.html @@ -0,0 +1,13 @@ +{% extends "templates/base.html" %} +{% block content %} + +
+ {{left-bar|safe}} +
+
+ {{content|safe}} +
+{% endblock %} diff --git a/smeagol.log b/smeagol.log new file mode 100644 index 0000000..1d93bde --- /dev/null +++ b/smeagol.log @@ -0,0 +1,20 @@ +2014-Nov-09 20:07:44 +0000 fletcher INFO [smeagol.handler] - +-=[ smeagol started successfully using the development profile ]=- +2014-Nov-09 20:32:47 +0000 fletcher INFO [smeagol.handler] - smeagol is shutting down... +2014-Nov-09 20:32:47 +0000 fletcher INFO [smeagol.handler] - shutdown complete! +2014-Nov-09 20:33:00 +0000 fletcher INFO [smeagol.handler] - +-=[ smeagol started successfully using the development profile ]=- +2014-Nov-09 20:35:05 +0000 fletcher INFO [smeagol.handler] - smeagol is shutting down... +2014-Nov-09 20:35:05 +0000 fletcher INFO [smeagol.handler] - shutdown complete! +2014-Nov-09 20:35:19 +0000 fletcher INFO [smeagol.handler] - +-=[ smeagol started successfully using the development profile ]=- +2014-Nov-09 21:07:26 +0000 fletcher INFO [smeagol.handler] - smeagol is shutting down... +2014-Nov-09 21:07:26 +0000 fletcher INFO [smeagol.handler] - shutdown complete! +2014-Nov-09 21:07:40 +0000 fletcher INFO [smeagol.handler] - +-=[ smeagol started successfully using the development profile ]=- +2014-Nov-09 21:10:52 +0000 fletcher INFO [smeagol.handler] - smeagol is shutting down... +2014-Nov-09 21:10:52 +0000 fletcher INFO [smeagol.handler] - shutdown complete! +2014-Nov-10 07:24:44 +0000 fletcher INFO [smeagol.handler] - +-=[ smeagol started successfully using the development profile ]=- +2014-Nov-10 13:15:36 +0000 fletcher INFO [smeagol.handler] - smeagol is shutting down... +2014-Nov-10 13:15:37 +0000 fletcher INFO [smeagol.handler] - shutdown complete! diff --git a/src/smeagol/handler.clj b/src/smeagol/handler.clj new file mode 100644 index 0000000..afe8733 --- /dev/null +++ b/src/smeagol/handler.clj @@ -0,0 +1,81 @@ +(ns smeagol.handler + (:require [compojure.core :refer [defroutes]] + [smeagol.routes.wiki :refer [wiki-routes]] + [smeagol.middleware :refer [load-middleware]] + [smeagol.session-manager :as session-manager] + [noir.response :refer [redirect]] + [noir.util.middleware :refer [app-handler]] + [noir.util.route :refer [restricted]] + [noir.session :as session] + [ring.middleware.defaults :refer [site-defaults]] + [compojure.route :as route] + [taoensso.timbre :as timbre] + [taoensso.timbre.appenders.rotor :as rotor] + [selmer.parser :as parser] + [environ.core :refer [env]] + [cronj.core :as cronj])) + +(defn user-access [request] + (session/get :user)) + +(defroutes base-routes + (route/resources "/") + (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/set-config! + [:appenders :rotor] + {:min-level :info + :enabled? true + :async? false ; should be always false for rotor + :max-message-per-msecs nil + :fn rotor/appender-fn}) + + (timbre/set-config! + [:shared-appender-config :rotor] + {: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 + "destroy will be called when your application + shuts down, put any clean up code here" + [] + (timbre/info "smeagol is shutting down...") + (cronj/shutdown! session-manager/cleanup-job) + (timbre/info "shutdown complete!")) + +;; timeout sessions after 30 minutes +(def session-defaults + {:timeout (* 60 30) + :timeout-response (redirect "/")}) + +(defn- mk-defaults + "set to true to enable XSS protection" + [xss-protection?] + (-> site-defaults + (update-in [:session] merge session-defaults) + (assoc-in [:security :anti-forgery] xss-protection?))) + +(def app (app-handler + ;; add your application routes here + [wiki-routes base-routes] + ;; add custom middleware here + :middleware (load-middleware) + :ring-defaults (mk-defaults false) + ;; add access rules here + :access-rules [{:redirect "/login" + :rule user-access}] + ;; serialize/deserialize the following data formats + ;; available formats: + ;; :json :json-kw :yaml :yaml-kw :edn :yaml-in-html + :formats [:json-kw :edn :transit-json])) diff --git a/src/smeagol/layout.clj b/src/smeagol/layout.clj new file mode 100644 index 0000000..d8f1612 --- /dev/null +++ b/src/smeagol/layout.clj @@ -0,0 +1,30 @@ +(ns smeagol.layout + (:require [selmer.parser :as parser] + [clojure.string :as s] + [ring.util.response :refer [content-type response]] + [compojure.response :refer [Renderable]] + [environ.core :refer [env]])) + +(def template-path "templates/") + +(deftype RenderableTemplate [template params] + Renderable + (render [this request] + (content-type + (->> (assoc params + (keyword (s/replace template #".html" "-selected")) "active" + :dev (env :dev) + :servlet-context + (if-let [context (:servlet-context request)] + ;; If we're not inside a serlvet environment (for + ;; example when using mock requests), then + ;; .getContextPath might not exist + (try (.getContextPath context) + (catch IllegalArgumentException _ context)))) + (parser/render-file (str template-path template)) + response) + "text/html; charset=utf-8"))) + +(defn render [template & [params]] + (RenderableTemplate. template params)) + diff --git a/src/smeagol/middleware.clj b/src/smeagol/middleware.clj new file mode 100644 index 0000000..7836f2c --- /dev/null +++ b/src/smeagol/middleware.clj @@ -0,0 +1,23 @@ +(ns smeagol.middleware + (:require [taoensso.timbre :as timbre] + [selmer.parser :as parser] + [environ.core :refer [env]] + [selmer.middleware :refer [wrap-error-page]] + [prone.middleware :refer [wrap-exceptions]] + [noir-exception.core :refer [wrap-internal-error]])) + +(defn log-request [handler] + (fn [req] + (timbre/debug req) + (handler req))) + +(def development-middleware + [wrap-error-page + wrap-exceptions]) + +(def production-middleware + [#(wrap-internal-error % :log (fn [e] (timbre/error e)))]) + +(defn load-middleware [] + (concat (when (env :dev) development-middleware) + production-middleware)) diff --git a/src/smeagol/repl.clj b/src/smeagol/repl.clj new file mode 100644 index 0000000..e72969b --- /dev/null +++ b/src/smeagol/repl.clj @@ -0,0 +1,34 @@ +(ns smeagol.repl + (:use smeagol.handler + ring.server.standalone + [ring.middleware file-info file])) + +(defonce server (atom nil)) + +(defn get-handler [] + ;; #'app expands to (var app) so that when we reload our code, + ;; the server is forced to re-resolve the symbol in the var + ;; rather than having its own copy. When the root binding + ;; changes, the server picks it up without having to restart. + (-> #'app + ; Makes static assets in $PROJECT_DIR/resources/public/ available. + (wrap-file "resources") + ; Content-Type, Content-Length, and Last Modified headers for files in body + (wrap-file-info))) + +(defn start-server + "used for starting the server in development mode from REPL" + [& [port]] + (let [port (if port (Integer/parseInt port) 3000)] + (reset! server + (serve (get-handler) + {:port port + :init init + :auto-reload? true + :destroy destroy + :join? false})) + (println (str "You can view the site at http://localhost:" port)))) + +(defn stop-server [] + (.stop @server) + (reset! server nil)) diff --git a/src/smeagol/routes/wiki.clj b/src/smeagol/routes/wiki.clj new file mode 100644 index 0000000..93395e8 --- /dev/null +++ b/src/smeagol/routes/wiki.clj @@ -0,0 +1,67 @@ +(ns smeagol.routes.wiki + (:use clojure.walk) + (:require [compojure.core :refer :all] + [noir.io :as io] + [noir.response :as response] + [smeagol.layout :as layout] + [smeagol.util :as util])) + +(defn process-source + "Process `source-text` and save it to the specified `file-path`, finally redirecting to wiki-page" + [file-path source-text request] + (let [params (keywordize-keys (:params request)) + content (or (:content params) "Introduction")] + (spit file-path source-text) + (response/redirect (str "wiki?" content)) + )) + +(defn edit-page + "Render a page in a text-area for editing. This could have been done in the same function as wiki-page, + and that would have been neat, but I couldn't see how to establish security if that were done." + [request] + (let [params (keywordize-keys (:params request)) + src-text (:src params) + content (:content params) + file-name (str "/content/" content ".md") + file-path (str (io/resource-path) file-name) + exists? (.exists (clojure.java.io/as-file file-path))] + (cond src-text (process-source file-path src-text request) + true + (layout/render "edit.html" + {:title content + :left-bar (util/md->html "/content/_edit-left-bar.md") + :header (util/md->html "/content/_header.md") + :content (if exists? (io/slurp-resource file-name) "")})))) + +(defn local-links + [html-src] + (clojure.string/replace html-src #"\[\[[^\[\]]*\]\]" + #(let [text (clojure.string/replace %1 #"[\[\]]" "")] + (str "" text "")))) + +(defn wiki-page + "Render the markdown page specified in this `request`, if any. If none found, redirect to edit-page" + [request] + (let [params (keywordize-keys (:params request)) + content (or (:content params) "Introduction") + file-name (str "/content/" content ".md") + file-path (str (io/resource-path) file-name) + exists? (.exists (clojure.java.io/as-file file-path))] + (cond exists? + (layout/render "wiki.html" + {:title content + :left-bar (util/md->html "/content/_left-bar.md") + :header (util/md->html "/content/_header.md") + :content (local-links (util/md->html file-name))}) + true (response/redirect (str "edit?content=" content))))) + + +(defn about-page [] + (layout/render "about.html")) + +(defroutes wiki-routes + (GET "/wiki" request (wiki-page request)) + (GET "/" request (wiki-page request)) + (GET "/edit" request (edit-page request)) + (POST "/edit" request (edit-page request)) + (GET "/about" [] (about-page))) diff --git a/src/smeagol/session_manager.clj b/src/smeagol/session_manager.clj new file mode 100644 index 0000000..ea9ec33 --- /dev/null +++ b/src/smeagol/session_manager.clj @@ -0,0 +1,11 @@ +(ns smeagol.session-manager + (:require [noir.session :refer [clear-expired-sessions]] + [cronj.core :refer [cronj]])) + +(def cleanup-job + (cronj + :entries + [{:id "session-cleanup" + :handler (fn [_ _] (clear-expired-sessions)) + :schedule "* /30 * * * * *" + :opts {}}])) diff --git a/src/smeagol/util.clj b/src/smeagol/util.clj new file mode 100644 index 0000000..c94236b --- /dev/null +++ b/src/smeagol/util.clj @@ -0,0 +1,8 @@ +(ns smeagol.util + (:require [noir.io :as io] + [markdown.core :as md])) + +(defn md->html + "reads a markdown file from public/md and returns an HTML string" + [filename] + (md/md-to-html-string (io/slurp-resource filename))) diff --git a/test/smeagol/test/handler.clj b/test/smeagol/test/handler.clj new file mode 100644 index 0000000..dad82f9 --- /dev/null +++ b/test/smeagol/test/handler.clj @@ -0,0 +1,13 @@ +(ns smeagol.test.handler + (:use clojure.test + ring.mock.request + smeagol.handler)) + +(deftest test-app + (testing "main route" + (let [response (app (request :get "/"))] + (is (= 200 (:status response))))) + + (testing "not-found route" + (let [response (app (request :get "/invalid"))] + (is (= 404 (:status response))))))