Permalink
Browse files

Merge branch 'release/1.0.0'

  • Loading branch information...
2 parents 1ca94b5 + cec6723 commit e0a84a544c77b7ec741bcc0f2f69c63754adcc00 @amalloy amalloy committed May 20, 2011
View
@@ -4,6 +4,7 @@
:db-pwd nil
:host "smtp.googlemail.com"
:user "team@4clojure.com"
- :problem-submission false
+ :problem-submission true
:advanced-user-count 50
- :pass ""}
+ :pass ""
+ :golfing-active true}
View
@@ -1,3 +1,3 @@
@echo off
for /f %%a in ('lein classpath') do @set project_classpath=%%a
-java -cp %project_classpath% clojure.main .\src\foreclojure\data_set.clj
+java -cp %project_classpath% clojure.main -i .\src\foreclojure\mongo.clj -e "(use 'foreclojure.mongo) (prepare-mongo) (shutdown-agents)"
View
@@ -1,3 +1,3 @@
project_classpath=`lein classpath`
echo $project_classpath
-java -cp $project_classpath clojure.main ./src/foreclojure/data_set.clj
+java -cp $project_classpath clojure.main -i ./src/foreclojure/mongo.clj -e "(use 'foreclojure.mongo) (prepare-mongo) (shutdown-agents)"
View
@@ -1,7 +1,7 @@
-(defproject foreclojure "0.3.1"
+(defproject foreclojure "1.0.0"
:description "4clojure - a website for lisp beginners"
- :dependencies [[org.clojure/clojure "1.2.1"]
- [org.clojure/clojure-contrib "1.2.0"]
+ :dependencies [[clojure "1.2.1"]
+ [clojure-contrib "1.2.0"]
[compojure "0.6.2"]
[hiccup "0.2.4"]
[clojail "0.4.0-SNAPSHOT"]
View
@@ -1,7 +1,7 @@
(ns foreclojure.core
(:use compojure.core
[foreclojure static problems login register golf
- users config social version graphs db-utils utils]
+ users config social version graphs mongo utils]
ring.adapter.jetty
somnium.congomongo
(ring.middleware (reload :only [wrap-reload])
@@ -10,20 +10,7 @@
[sandbar.stateful-session :as session]
[ring.util.response :as response]))
-(mongo!
- :host (if-let [host (:db-host config)]
- host
- "localhost")
- :db "mydb")
-
-(if-let [db-user (:db-user config)]
- (if-let [db-pwd (:db-pwd config)]
- (authenticate db-user db-pwd)))
-
-(add-index! :users [:user] :unique true)
-(add-index! :users [[:solved -1]])
-
-(reconcile-solved-count)
+(prepare-mongo)
(defroutes main-routes
(GET "/" [] (welcome-page))
@@ -3,12 +3,9 @@
(defn load-problems []
(do
- (mongo! :db :mydb)
-
(insert! :seqs
{:_id "problems"
:seq 87})
-
(insert! :problems
{:_id 1
:title "Nothing but the Truth"
@@ -1014,5 +1011,3 @@ number of prime numbers."
"(= (__ 1 2 10 100 1300) '(= (* (+ 1 2 10) 100) 1300)"]})
))
-
-(load-problems)
@@ -1,23 +0,0 @@
-(ns foreclojure.db-utils
- (:use [foreclojure.problems]
- [foreclojure.users]
- [somnium.congomongo]))
-
-(defn reconcile-solved-count
- "Overwrites the times-solved field in the problems collection based on data from the users collection. Should only be called on server startup since it isn't a safe operation. Also updates the total-solved agent."
- []
- (send
- total-solved +
- (let [problems (get-problem-list)]
- (reduce
- #(do
- (update! :problems
- {:_id (first %2)}
- {:$set {:times-solved (last %2)}})
- (+ %1 (last %2)))
- 0
- (reduce #(update-in %1 [%2] inc)
- (reduce #(conj %1 [%2 0])
- {}
- (map :_id problems))
- (mapcat :solved (get-users)))))))
View
@@ -0,0 +1,69 @@
+(ns foreclojure.mongo
+ (:use somnium.congomongo
+ [foreclojure.data-set :only [load-problems]]
+ [foreclojure.config :only [config]]
+ [foreclojure.problems :only [total-solved get-problem-list]]
+ [foreclojure.users :only [get-users]]))
+
+(defn connect-to-db []
+ (let [{:keys [db-user db-pwd db-host]} config]
+ (mongo!
+ :host (or db-host "localhost")
+ :db "mydb")
+ (when (and db-user db-pwd)
+ (authenticate db-user db-pwd))))
+
+(defn prepare-problems []
+ (when-not (fetch-one :problems)
+ (load-problems))
+ (add-index! :problems [:solved]))
+
+(defn prepare-seqs []
+ (update! :seqs
+ {:_id "problems"}
+ {:$set {:seq (apply max
+ (map :_id
+ (fetch :problems
+ :only [:_id])))}}))
+
+;; make it easier to get off the ground by marking contributors automatically
+;; useful since some "in-development" features aren't enabled for all users
+(defn prepare-users []
+ (add-index! :users [:user] :unique true)
+ (add-index! :users [[:solved -1]])
+ (update! :users
+ {:user {:$in ["amalloy" "dbyrne" "raynes" "cmeier" "devn"
+ "citizen428" "daviddavis" "clinteger"]}}
+ {:$set {:contributor true}}
+ :upsert false
+ :multiple true))
+
+(defn prepare-solutions []
+ (add-index! :solutions [:user :problem]))
+
+(defn reconcile-solved-count
+ "Overwrites the times-solved field in the problems collection based on data from the users collection. Should only be called on server startup since it isn't a safe operation. Also updates the total-solved agent."
+ []
+ (send
+ total-solved +
+ (let [problems (get-problem-list)]
+ (reduce
+ #(do
+ (update! :problems
+ {:_id (first %2)}
+ {:$set {:times-solved (last %2)}})
+ (+ %1 (last %2)))
+ 0
+ (reduce #(update-in %1 [%2] inc)
+ (reduce #(conj %1 [%2 0])
+ {}
+ (map :_id problems))
+ (mapcat :solved (get-users)))))))
+
+(defn prepare-mongo []
+ (connect-to-db)
+ (prepare-problems)
+ (prepare-seqs)
+ (prepare-users)
+ (prepare-solutions)
+ (reconcile-solved-count))
@@ -2,7 +2,8 @@
(:use (foreclojure utils config
[social :only [tweet-link gist!]]
[feeds :only [create-feed]]
- [users :only [golfer?]])
+ [users :only [golfer? get-user-id]]
+ [solutions :only [save-solution get-solution]])
(clojail [core :exclude [safe-read]] testers)
somnium.congomongo
(hiccup form-helpers page-helpers core)
@@ -62,15 +63,15 @@
(count (remove #(Character/isWhitespace %)
code)))
-(defn record-golf-score! [user-name problem-id score]
+(defn record-golf-score! [user-id problem-id score]
(let [user-score-key (keyword (str "scores." problem-id))
problem-score-key (keyword (str "scores." score))
[problem-scores-key user-subkey] (map mongo-key-from-number
[score problem-id])]
(when-let [{:keys [_id scores] :as user}
(from-mongo
(fetch-one :users
- :where {:user user-name}))]
+ :where {:_id user-id}))]
(let [old-score-real (get scores user-subkey)
old-score-test (or old-score-real 1e6)
old-score-key (keyword (str "scores." old-score-real))]
@@ -91,6 +92,17 @@
{:_id _id}
{:$set {user-score-key score}}))))))
+(defn store-completed-state! [username problem-id code]
+ (let [{user-id :_id} (fetch-one :users
+ :where {:user username}
+ :only [:_id])]
+ (when (not-any? #{problem-id} (get-solved username))
+ (update! :users {:_id user-id} {:$addToSet {:solved problem-id}})
+ (update! :problems {:_id problem-id} {:$inc {:times-solved 1}})
+ (send total-solved inc))
+ (record-golf-score! user-id problem-id (code-length code))
+ (save-solution (? user-id) (? problem-id) (? code))))
+
(defn mark-completed [problem code & [user]]
(let [user (or user (session/session-get :user))
{:keys [_id approved]} problem
@@ -101,11 +113,7 @@
(cond
(not approved) (str "You've solved the unapproved problem. Now you can approve it!")
user (do
- (when (not-any? #{_id} (get-solved user))
- (update! :users {:user user} {:$addToSet {:solved _id}})
- (update! :problems {:_id _id} {:$inc {:times-solved 1}})
- (send total-solved inc))
- (record-golf-score! user _id (code-length code))
+ (store-completed-state! user _id code)
(str "Congratulations, you've solved the problem!"
"<br />" (next-problem-link _id)))
:else (str "You've solved the problem! If you "
@@ -182,7 +190,7 @@
[:span#graph-link "View Chart"]]])))
(def-page code-box [id]
- (let [{:keys [title tags description restricted tests approved user]}
+ (let [{:keys [_id title tags description restricted tests approved user]}
(get-problem (Integer. id))]
[:div
[:span#prob-title
@@ -213,7 +221,10 @@
[:p#instruct "Code which fills in the blank: "]
(text-area {:id "code-box"
:spellcheck "false"}
- :code (session/flash-get :code))
+ :code (or (session/flash-get :code)
+ (-> (session/session-get :user)
+ (get-user-id)
+ (get-solution ,,, _id))))
[:div#golfgraph
(render-golf-chart)]
(hidden-field :id id)
@@ -330,16 +341,18 @@
(flash-error "You are not authorized to submit a problem." "/problems"))))
(defn edit-problem [id]
- (let [{:keys [title user tags restricted description tests]} (get-problem id)]
- (doseq [[k v] {:prob-id id
- :author user
- :title title
- :tags (s/join " " tags)
- :restricted (s/join " " restricted)
- :description description
- :tests (s/join "\r\n\r\n" tests)}]
- (session/flash-put! k v))
- (response/redirect "/problems/submit")))
+ (if (approver? (session/session-get :user))
+ (let [{:keys [title user tags restricted description tests]} (get-problem id)]
+ (doseq [[k v] {:prob-id id
+ :author user
+ :title title
+ :tags (s/join " " tags)
+ :restricted (s/join " " restricted)
+ :description description
+ :tests (s/join "\r\n\r\n" tests)}]
+ (session/flash-put! k v))
+ (response/redirect "/problems/submit"))
+ (flash-error "You don't have access to this page" "/problems")))
(defn approve-problem [id]
"take a user submitted problem and approve it"
View
@@ -23,37 +23,52 @@
:last-sent (if ok now last-sent)}))))
(apply f args)))))
-(def clojure-hashtag (throttled (constantly " #clojure")
+(def clojure-hashtag (throttled (constantly "#clojure ")
(* 1000 60 60))) ; hourly
-(defn tweet-link [status & [anchor-text]]
- (str "<a href=\"http://twitter.com/home?status="
- (URLEncoder/encode status) "\">"
+(defn tweet-link [id status & [anchor-text]]
+ (str "<a href=\"http://twitter.com/share?"
+ "text=" (URLEncoder/encode status)
+ "&url=" (URLEncoder/encode
+ (str "https://4clojure.com/problem/" id))
+ "&related=4clojure"
+ "\">"
(or anchor-text "Twitter")
"</a>"))
+(defn get-problem-title [id]
+ (:title
+ (fetch-one :problems
+ :only [:title]
+ :where {:_id id})))
+
(defn gist!
"Create a new gist containing a user's solution to a problem and
return its url."
[user-name problem-num solution]
- (let [user-name (or user-name "anonymous")
- {name :title} (fetch-one :problems
- :where {:_id problem-num})
+ (let [[user-name possessive] (if user-name
+ [user-name "'s"]
+ ["anonymous" nil])
+ name (get-problem-title problem-num)
filename (str user-name "-4clojure-solution" problem-num ".clj")
- text (str ";; " user-name "'s solution to " name "\n"
+ text (str ";; " user-name possessive " solution to " name "\n"
";; https://4clojure.com/problem/" problem-num
"\n\n"
solution)]
(try
(->> (gist/new-gist {} filename text)
:repo
(str "https://gist.github.com/"))
- (catch Throwable _ nil))))
+ (catch Throwable _))))
(defn tweet-solution [id gist-url & [link-text]]
- (let [status-msg (str "Check out how I solved https://4clojure.com/problem/"
- id " - " gist-url " #4clojure" (clojure-hashtag))]
- (tweet-link status-msg link-text)))
+ (let [status-msg (str "Check out how I solved "
+ (let [title (get-problem-title id)]
+ (if (> (count title) 35)
+ (str "problem " id)
+ (str "\"" title "\"")))
+ " on #4clojure " (clojure-hashtag) gist-url)]
+ (tweet-link id status-msg link-text)))
(def-page share-page []
(if-let [[id code] (session/session-get :code)]
@@ -0,0 +1,17 @@
+(ns foreclojure.solutions
+ (:use foreclojure.utils
+ somnium.congomongo
+ (amalloy.utils [debug :only [?]]))
+ (:require [clojure.string :as s]))
+
+(defn get-solution [user-id problem-id]
+ (:code (fetch-one :solutions
+ :where {:user user-id
+ :problem problem-id})))
+
+(defn save-solution [user-id problem-id code]
+ (update! :solutions
+ {:user user-id
+ :problem problem-id}
+ {:$set {:code code}}
+ :upsert true))
@@ -8,6 +8,12 @@
(when (:golfing-active config)
[:golfer])))
+(defn get-user-id [name]
+ (:_id
+ (fetch-one :users
+ :where {:user name}
+ :only [:_id])))
+
(defn get-users []
(let [users (from-mongo
(fetch :users
Oops, something went wrong.

0 comments on commit e0a84a5

Please sign in to comment.