Skip to content

Commit

Permalink
initial import
Browse files Browse the repository at this point in the history
Signed-off-by: Chris Granger <[email protected]>
  • Loading branch information
ibdknox committed Jun 13, 2011
0 parents commit 7e3e10e
Show file tree
Hide file tree
Showing 17 changed files with 621 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pom.xml
*jar
/lib/
/classes/
.lein-deps-sum
sdb.db
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Noir blog

This is a fully functioning blog, that serves as an example website written in noir.

## Usage

```bash
lein deps
lein run
```

To login, an initial user is created:
username :: admin
password :: admin

## License

Copyright (C) 2011 FIXME

Distributed under the Eclipse Public License, the same as Clojure.

9 changes: 9 additions & 0 deletions project.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
(defproject noir-blog "0.1.0"
:description "A fully functional blog that serves an example of a noir project."
:dependencies [[org.clojure/clojure "1.2.1"]
[clj-time "0.3.0"]
[noir "0.4.0"]
[org.markdownj/markdownj "0.3.0-1.0.2b4"]
[simpledb "0.1.4"]]
:main noir-blog.server)

36 changes: 36 additions & 0 deletions resources/public/css/default.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/* Site specific styles. */
body { font-family: Verdana; background: #2a2b2b; color: #d1d9e1; }
h1 { font-size:32px; color: #004c65; paddin:55px 0 0 0;}
h2 { font-size:24px; }
ul {list-style:none; }
a { color: #56BF90; text-decoration:none;}
input { background:#3a3b3b; border:2px solid #4a4b4b; border-radius:8px; padding:10px; width: 590px; color:#babbbb; font-size:14px; }
textarea { background:#3a3b3b; border:2px solid #4a4b4b; border-radius:8px; padding:10px; width: 590px; color:#babbbb; height:350px; font-size:14px; }

input + input { margin-top:10px; }
input + textarea { margin-top:10px; }


#wrapper {width:800px; margin: 0 auto; padding-top:50px; }
#wrapper > div.content { float:left; display:inline; padding-bottom:100px; }

#header { float:left; display:inline; width:100%; margin-bottom:30px;}
#header h1 {float:left; display:inline; color:#56BF90; }
#header h1 a { color:#56BF90; }
#header ul {float:right; display:inline; margin-top:15px;}
#header ul li {float:left; display:inline; margin-left:15px;}

ul.actions {float:right; display:inline; width:140px; }
ul.actions li a { display:block; background:#3a3b3b; border-radius:5px; text-align:center; padding:5px; margin-bottom:10px; }
ul.actions li a:hover { background:#4a4b4b;}
ul.items { float:left; display:inline; width:600px; }
ul.items li a { float:left; width:590px; display:inline; padding:8px 15px; background: #3a3b3b; color: #b1b9c1; border-radius: 5px; margin-bottom:10px; }

ul.posts { float:left; display:inline; }
ul.posts li.post { float:left; display:inline; width: 760px; padding:15px; background: #3a3b3b; border-radius: 10px; margin-bottom:20px; border:2px solid #4a4b4b; }
ul.posts li.post h2 { float:left; display:inline; width:660px; margin-bottom:10px; }
ul.posts li.post .datetime {float:right; display:inline; font-size:11px; color:#8a8b8b; }
ul.posts li.post .content { float:left; display:inline; width:660px; color:#babbbb;}
ul.posts li.post .content p { margin-bottom:18px; }

.submit { display:none; }
57 changes: 57 additions & 0 deletions resources/public/css/reset.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
html {
margin:0;
padding:0;
border:0;
}

body, div, span, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, code,
del, dfn, em, img, q, dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, dialog, figure, footer, header,
hgroup, nav, section {
margin: 0;
padding: 0;
border: 0;
font-weight: inherit;
font-style: inherit;
font-size: 100%;
font-family: inherit;
vertical-align: baseline;
}

article, aside, dialog, figure, footer, header,
hgroup, nav, section {
display:block;
}

body {
line-height: 1.5;
background: white;
}

table {
border-collapse: separate;
border-spacing: 0;
}

caption, th, td {
text-align: left;
font-weight: normal;
float:none !important;
}
table, th, td {
vertical-align: middle;
}

blockquote:before, blockquote:after, q:before, q:after { content: ''; }
blockquote, q { quotes: "" ""; }

a img { border: none; }

/*:focus { outline: 0; }*/



Binary file added resources/public/img/ChrisGrangerLogo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added resources/public/img/bloglink.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added resources/public/img/contactslink.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added resources/public/img/projectslink.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 29 additions & 0 deletions resources/public/js/blog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
$(document).ready( function() {

$("a.hider").click( function(event) {
$(this).next().toggle("slow");
return false;
});

$("a.submit").click( function(event) {

$("form").submit();
return false;

});

$("a.delete").click( function(event) {

if( $(this).hasClass("confirm") ) {
//goto link
return true;
}

$(this).addClass("confirm");
$(this).text("Click to confirm");
return false;

});

});

11 changes: 11 additions & 0 deletions src/noir_blog/models.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
(ns noir-blog.models
(:require [simpledb.core :as db]
[noir-blog.models.user :as users]
[noir-blog.models.post :as posts]))

(defn initialize []
(db/init)
(when-not (db/get :users)
;;db values need to be initialized.. this should only happen once.
(users/init!)
(posts/init!)))
126 changes: 126 additions & 0 deletions src/noir_blog/models/post.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
(ns noir-blog.models.post
(:require [simpledb.core :as db]
[clj-time.core :as ctime]
[clj-time.format :as tform]
[clj-time.coerce :as coerce]
[clojure.string :as string]
[noir-blog.models.user :as users]
[noir.validation :as vali]
[noir.session :as session])
(:import com.petebevin.markdown.MarkdownProcessor))

(def posts-per-page 10)
(def date-format (tform/formatter "MM/dd/yy" (ctime/default-time-zone)))
(def time-format (tform/formatter "h:mma" (ctime/default-time-zone)))
(def mdp (MarkdownProcessor.))

;; Gets

(defn total []
(count (db/get :post-ids)))

(defn id->post [id]
(db/get-in :posts [id]))

(defn ids->posts [ids]
(map id->post ids))

(defn moniker->post [moniker]
(id->post (db/get-in :post-monikers [moniker])))

(defn get-page [page]
(let [page-num (- (Integer. page) 1) ;; make it 1-based indexing
prev (* page-num posts-per-page)]
(ids->posts (take posts-per-page (drop prev (db/get :post-ids))))))

(defn get-latest []
(get-page 1))

;; Mutations and checks

(defn next-id []
(str (db/update! :next-post-id inc)))

(defn gen-moniker [title]
(-> title
(string/lower-case)
(string/replace #"[^a-zA-Z0-9\s]" "")
(string/replace #" " "-")))

(defn new-moniker? [moniker]
(not (contains? (db/get :post-monikers) moniker)))

(defn perma-link [moniker]
(str "/blog/view/" moniker))

(defn edit-url [{:keys [id]}]
(str "/blog/admin/post/edit/" id))

(defn md->html [text]
(. mdp (markdown text)))

(defn wrap-moniker [{:keys [title] :as post}]
(let [moniker (gen-moniker title)]
(-> post
(assoc :moniker moniker)
(assoc :perma-link (perma-link moniker)))))

(defn wrap-markdown [{:keys [body] :as post}]
(assoc post :md-body (md->html body)))

(defn wrap-time [post]
(let [ts (ctime/now)]
(-> post
(assoc :ts (coerce/to-long ts))
(assoc :date (tform/unparse date-format ts))
(assoc :tme (tform/unparse time-format ts)))))

(defn prepare-new [{:keys [title body] :as post}]
(let [id (next-id)
ts (ctime/now)]
(-> post
(assoc :id id)
(assoc :username (users/me))
(wrap-time)
(wrap-moniker)
(wrap-markdown))))

(defn valid? [{:keys [title body]}]
(vali/rule (vali/has-value? title)
[:title "There must be a title"])
(vali/rule (new-moniker? (gen-moniker title))
[:title "That title is already taken."])
(vali/rule (vali/has-value? body)
[:body "There's no post content."])
(not (vali/errors? :title :body)))

;; Operations

(defn add! [post]
(when (valid? post)
(let [{:keys [id moniker] :as final} (prepare-new post)]
(db/update! :posts assoc id final)
(db/update! :post-ids conj id)
(db/update! :post-monikers assoc moniker id))))

(defn edit! [{:keys [id title] :as post}]
(let [{orig-moniker :moniker :as original} (id->post id)
{:keys [moniker] :as final} (-> post
(wrap-moniker)
(wrap-markdown))]
(db/update! :posts assoc id (merge original final))
(db/update! :post-monikers dissoc orig-moniker) ;; remove the old moniker entry in case it changed
(db/update! :post-monikers assoc moniker id)))

(defn remove! [id]
(let [{:keys [moniker]} (id->post id)
neue-ids (remove #{id} (db/get :post-ids))]
(db/put! :post-ids neue-ids)
(db/update! :posts dissoc id)
(db/update! :post-monikers dissoc moniker)))

(defn init! []
(db/put! :next-post-id -1)
(db/put! :posts {})
(db/put! :post-ids (list))
(db/put! :post-monikers {}))
63 changes: 63 additions & 0 deletions src/noir_blog/models/user.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
(ns noir-blog.models.user
(:require [simpledb.core :as db]
[noir.util.crypt :as crypt]
[noir.validation :as vali]
[noir.session :as session]))

;; Gets

(defn all []
(vals (db/get :users)))

(defn get-username [username]
(db/get-in :users [username]))

(defn admin? []
(session/get :admin))

(defn me []
(session/get :username))

;; Mutations and Checks

(defn prepare [{password :password :as user}]
(assoc user :password (crypt/encrypt password)))

(defn valid? [{:keys [username password]}]
(vali/rule (not (db/get-in :users [username]))
[:username "That username is already taken"])
(vali/rule (vali/min-length? username 3)
[:username "Username must be at least 3 characters."])
(vali/rule (vali/min-length? password 5)
[:password "Password must be at least 5 characters."])
(not (vali/errors? :username :password)))

;; Operations

(defn- store! [{username :username :as user}]
(db/update! :users assoc username user))

(defn login! [{:keys [username password] :as user}]
(let [{stored-pass :password} (get-username username)]
(if (and stored-pass
(crypt/compare password stored-pass))
(do
(session/put! :admin true)
(session/put! :username username))
(vali/set-error :username "Invalid username or password"))))

(defn add! [{:keys [username password]}]
(let [user (-> {:username username :password password}
(prepare))]
(when (valid? user)
(store! user))))

(defn edit! [user]
(add! user))

(defn remove! [username]
(db/update! :users dissoc username))

(defn init! []
(db/put! :users {})
(store! (prepare {:username "admin" :password "admin"})))
12 changes: 12 additions & 0 deletions src/noir_blog/server.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
(ns noir-blog.server
(:require [noir.server :as server]
[noir-blog.models :as models]))

(server/load-views "src/noir_blog/views/")

(defn -main [& m]
(let [mode (or (first m) :dev)]
(models/initialize)
(server/start 8080 {:mode (keyword mode)
:ns 'noir-blog})))

Loading

0 comments on commit 7e3e10e

Please sign in to comment.