├── README.rst └── todo-clj ├── .gitignore ├── LICENSE ├── Procfile ├── README.md ├── doc └── intro.md ├── project.clj ├── resources └── public │ ├── css │ ├── normalize.css │ ├── papier-1.3.1.min.css │ └── style.css │ └── js │ └── main.js ├── src └── todo_clj │ ├── core.clj │ ├── db.clj │ ├── db │ └── todo.clj │ ├── handler │ ├── main.clj │ └── todo.clj │ ├── main.clj │ ├── middleware.clj │ ├── middleware │ └── http_response.clj │ ├── util │ ├── response.clj │ └── validation.clj │ └── view │ ├── layout.clj │ ├── main.clj │ └── todo.clj └── test └── todo_clj └── core_test.clj /README.rst: -------------------------------------------------------------------------------- 1 | ===================================== 2 | Clojure で作る Web アプリケーション 3 | ===================================== 4 | 5 | このプロジェクトは作者の個人的なプロジェクトである `Clojure の日本語ガイド `_ の中にある "Clojure で Web 開発をはじめてみよう" を補足するためのものです。 6 | -------------------------------------------------------------------------------- /todo-clj/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | .hgignore 11 | .hg/ 12 | -------------------------------------------------------------------------------- /todo-clj/LICENSE: -------------------------------------------------------------------------------- 1 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 2 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 3 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 4 | 5 | 1. DEFINITIONS 6 | 7 | "Contribution" means: 8 | 9 | a) in the case of the initial Contributor, the initial code and 10 | documentation distributed under this Agreement, and 11 | 12 | b) in the case of each subsequent Contributor: 13 | 14 | i) changes to the Program, and 15 | 16 | ii) additions to the Program; 17 | 18 | where such changes and/or additions to the Program originate from and are 19 | distributed by that particular Contributor. A Contribution 'originates' from 20 | a Contributor if it was added to the Program by such Contributor itself or 21 | anyone acting on such Contributor's behalf. Contributions do not include 22 | additions to the Program which: (i) are separate modules of software 23 | distributed in conjunction with the Program under their own license 24 | agreement, and (ii) are not derivative works of the Program. 25 | 26 | "Contributor" means any person or entity that distributes the Program. 27 | 28 | "Licensed Patents" mean patent claims licensable by a Contributor which are 29 | necessarily infringed by the use or sale of its Contribution alone or when 30 | combined with the Program. 31 | 32 | "Program" means the Contributions distributed in accordance with this 33 | Agreement. 34 | 35 | "Recipient" means anyone who receives the Program under this Agreement, 36 | including all Contributors. 37 | 38 | 2. GRANT OF RIGHTS 39 | 40 | a) Subject to the terms of this Agreement, each Contributor hereby grants 41 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 42 | reproduce, prepare derivative works of, publicly display, publicly perform, 43 | distribute and sublicense the Contribution of such Contributor, if any, and 44 | such derivative works, in source code and object code form. 45 | 46 | b) Subject to the terms of this Agreement, each Contributor hereby grants 47 | Recipient a non-exclusive, worldwide, royalty-free patent license under 48 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 49 | transfer the Contribution of such Contributor, if any, in source code and 50 | object code form. This patent license shall apply to the combination of the 51 | Contribution and the Program if, at the time the Contribution is added by the 52 | Contributor, such addition of the Contribution causes such combination to be 53 | covered by the Licensed Patents. The patent license shall not apply to any 54 | other combinations which include the Contribution. No hardware per se is 55 | licensed hereunder. 56 | 57 | c) Recipient understands that although each Contributor grants the licenses 58 | to its Contributions set forth herein, no assurances are provided by any 59 | Contributor that the Program does not infringe the patent or other 60 | intellectual property rights of any other entity. Each Contributor disclaims 61 | any liability to Recipient for claims brought by any other entity based on 62 | infringement of intellectual property rights or otherwise. As a condition to 63 | exercising the rights and licenses granted hereunder, each Recipient hereby 64 | assumes sole responsibility to secure any other intellectual property rights 65 | needed, if any. For example, if a third party patent license is required to 66 | allow Recipient to distribute the Program, it is Recipient's responsibility 67 | to acquire that license before distributing the Program. 68 | 69 | d) Each Contributor represents that to its knowledge it has sufficient 70 | copyright rights in its Contribution, if any, to grant the copyright license 71 | set forth in this Agreement. 72 | 73 | 3. REQUIREMENTS 74 | 75 | A Contributor may choose to distribute the Program in object code form under 76 | its own license agreement, provided that: 77 | 78 | a) it complies with the terms and conditions of this Agreement; and 79 | 80 | b) its license agreement: 81 | 82 | i) effectively disclaims on behalf of all Contributors all warranties and 83 | conditions, express and implied, including warranties or conditions of title 84 | and non-infringement, and implied warranties or conditions of merchantability 85 | and fitness for a particular purpose; 86 | 87 | ii) effectively excludes on behalf of all Contributors all liability for 88 | damages, including direct, indirect, special, incidental and consequential 89 | damages, such as lost profits; 90 | 91 | iii) states that any provisions which differ from this Agreement are offered 92 | by that Contributor alone and not by any other party; and 93 | 94 | iv) states that source code for the Program is available from such 95 | Contributor, and informs licensees how to obtain it in a reasonable manner on 96 | or through a medium customarily used for software exchange. 97 | 98 | When the Program is made available in source code form: 99 | 100 | a) it must be made available under this Agreement; and 101 | 102 | b) a copy of this Agreement must be included with each copy of the Program. 103 | 104 | Contributors may not remove or alter any copyright notices contained within 105 | the Program. 106 | 107 | Each Contributor must identify itself as the originator of its Contribution, 108 | if any, in a manner that reasonably allows subsequent Recipients to identify 109 | the originator of the Contribution. 110 | 111 | 4. COMMERCIAL DISTRIBUTION 112 | 113 | Commercial distributors of software may accept certain responsibilities with 114 | respect to end users, business partners and the like. While this license is 115 | intended to facilitate the commercial use of the Program, the Contributor who 116 | includes the Program in a commercial product offering should do so in a 117 | manner which does not create potential liability for other Contributors. 118 | Therefore, if a Contributor includes the Program in a commercial product 119 | offering, such Contributor ("Commercial Contributor") hereby agrees to defend 120 | and indemnify every other Contributor ("Indemnified Contributor") against any 121 | losses, damages and costs (collectively "Losses") arising from claims, 122 | lawsuits and other legal actions brought by a third party against the 123 | Indemnified Contributor to the extent caused by the acts or omissions of such 124 | Commercial Contributor in connection with its distribution of the Program in 125 | a commercial product offering. The obligations in this section do not apply 126 | to any claims or Losses relating to any actual or alleged intellectual 127 | property infringement. In order to qualify, an Indemnified Contributor must: 128 | a) promptly notify the Commercial Contributor in writing of such claim, and 129 | b) allow the Commercial Contributor tocontrol, and cooperate with the 130 | Commercial Contributor in, the defense and any related settlement 131 | negotiations. The Indemnified Contributor may participate in any such claim 132 | at its own expense. 133 | 134 | For example, a Contributor might include the Program in a commercial product 135 | offering, Product X. That Contributor is then a Commercial Contributor. If 136 | that Commercial Contributor then makes performance claims, or offers 137 | warranties related to Product X, those performance claims and warranties are 138 | such Commercial Contributor's responsibility alone. Under this section, the 139 | Commercial Contributor would have to defend claims against the other 140 | Contributors related to those performance claims and warranties, and if a 141 | court requires any other Contributor to pay any damages as a result, the 142 | Commercial Contributor must pay those damages. 143 | 144 | 5. NO WARRANTY 145 | 146 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON 147 | AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER 148 | EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR 149 | CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A 150 | PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the 151 | appropriateness of using and distributing the Program and assumes all risks 152 | associated with its exercise of rights under this Agreement , including but 153 | not limited to the risks and costs of program errors, compliance with 154 | applicable laws, damage to or loss of data, programs or equipment, and 155 | unavailability or interruption of operations. 156 | 157 | 6. DISCLAIMER OF LIABILITY 158 | 159 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 160 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 161 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION 162 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 163 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 164 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 165 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY 166 | OF SUCH DAMAGES. 167 | 168 | 7. GENERAL 169 | 170 | If any provision of this Agreement is invalid or unenforceable under 171 | applicable law, it shall not affect the validity or enforceability of the 172 | remainder of the terms of this Agreement, and without further action by the 173 | parties hereto, such provision shall be reformed to the minimum extent 174 | necessary to make such provision valid and enforceable. 175 | 176 | If Recipient institutes patent litigation against any entity (including a 177 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 178 | (excluding combinations of the Program with other software or hardware) 179 | infringes such Recipient's patent(s), then such Recipient's rights granted 180 | under Section 2(b) shall terminate as of the date such litigation is filed. 181 | 182 | All Recipient's rights under this Agreement shall terminate if it fails to 183 | comply with any of the material terms or conditions of this Agreement and 184 | does not cure such failure in a reasonable period of time after becoming 185 | aware of such noncompliance. If all Recipient's rights under this Agreement 186 | terminate, Recipient agrees to cease use and distribution of the Program as 187 | soon as reasonably practicable. However, Recipient's obligations under this 188 | Agreement and any licenses granted by Recipient relating to the Program shall 189 | continue and survive. 190 | 191 | Everyone is permitted to copy and distribute copies of this Agreement, but in 192 | order to avoid inconsistency the Agreement is copyrighted and may only be 193 | modified in the following manner. The Agreement Steward reserves the right to 194 | publish new versions (including revisions) of this Agreement from time to 195 | time. No one other than the Agreement Steward has the right to modify this 196 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The 197 | Eclipse Foundation may assign the responsibility to serve as the Agreement 198 | Steward to a suitable separate entity. Each new version of the Agreement will 199 | be given a distinguishing version number. The Program (including 200 | Contributions) may always be distributed subject to the version of the 201 | Agreement under which it was received. In addition, after a new version of 202 | the Agreement is published, Contributor may elect to distribute the Program 203 | (including its Contributions) under the new version. Except as expressly 204 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or 205 | licenses to the intellectual property of any Contributor under this 206 | Agreement, whether expressly, by implication, estoppel or otherwise. All 207 | rights in the Program not expressly granted under this Agreement are 208 | reserved. 209 | 210 | This Agreement is governed by the laws of the State of New York and the 211 | intellectual property laws of the United States of America. No party to this 212 | Agreement will bring a legal action under this Agreement more than one year 213 | after the cause of action arose. Each party waives its rights to a jury trial 214 | in any resulting litigation. 215 | -------------------------------------------------------------------------------- /todo-clj/Procfile: -------------------------------------------------------------------------------- 1 | web: java $JVM_OPT -jar target/todo-clj.jar host 0.0.0.0 port $PORT 2 | -------------------------------------------------------------------------------- /todo-clj/README.md: -------------------------------------------------------------------------------- 1 | # todo-clj 2 | 3 | A Clojure library designed to ... well, that part is up to you. 4 | 5 | ## Usage 6 | 7 | FIXME 8 | 9 | ## License 10 | 11 | Copyright © 2015 FIXME 12 | 13 | Distributed under the Eclipse Public License either version 1.0 or (at 14 | your option) any later version. 15 | -------------------------------------------------------------------------------- /todo-clj/doc/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to todo-clj 2 | 3 | TODO: write [great documentation](http://jacobian.org/writing/what-to-write/) 4 | -------------------------------------------------------------------------------- /todo-clj/project.clj: -------------------------------------------------------------------------------- 1 | (defproject todo-clj "0.1.0-SNAPSHOT" 2 | :description "FIXME: write description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :min-lein-version "2.5.3" 7 | :dependencies [[org.clojure/clojure "1.7.0"] 8 | [ring "1.4.0"] 9 | [compojure "1.4.0"] 10 | [hiccup "1.0.5"] 11 | [environ "1.0.1"] 12 | [org.clojure/java.jdbc "0.4.2"] 13 | [org.postgresql/postgresql "9.4-1205-jdbc42"] 14 | [bouncer "0.3.3"] 15 | [ring/ring-defaults "0.1.5"] 16 | [metosin/ring-http-response "0.6.5"] 17 | [slingshot "0.12.2"] 18 | [potemkin "0.4.1"]] 19 | :plugins [[lein-environ "1.0.1"]] 20 | :uberjar-name "todo-clj.jar" 21 | :profiles 22 | {:dev {:dependencies [[prone "0.8.2"]] 23 | :env {:dev true 24 | :db {:dbtype "postgresql" :dbname "todo_clj_dev" :host "localhost" :port 5432 :user "username" :password "password"}}} 25 | :uberjar {:aot :all 26 | :main todo-clj.main}}) 27 | -------------------------------------------------------------------------------- /todo-clj/resources/public/css/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ 2 | 3 | /** 4 | * 1. Set default font family to sans-serif. 5 | * 2. Prevent iOS and IE text size adjust after device orientation change, 6 | * without disabling user zoom. 7 | */ 8 | 9 | html { 10 | font-family: sans-serif; /* 1 */ 11 | -ms-text-size-adjust: 100%; /* 2 */ 12 | -webkit-text-size-adjust: 100%; /* 2 */ 13 | } 14 | 15 | /** 16 | * Remove default margin. 17 | */ 18 | 19 | body { 20 | margin: 0; 21 | } 22 | 23 | /* HTML5 display definitions 24 | ========================================================================== */ 25 | 26 | /** 27 | * Correct `block` display not defined for any HTML5 element in IE 8/9. 28 | * Correct `block` display not defined for `details` or `summary` in IE 10/11 29 | * and Firefox. 30 | * Correct `block` display not defined for `main` in IE 11. 31 | */ 32 | 33 | article, 34 | aside, 35 | details, 36 | figcaption, 37 | figure, 38 | footer, 39 | header, 40 | hgroup, 41 | main, 42 | menu, 43 | nav, 44 | section, 45 | summary { 46 | display: block; 47 | } 48 | 49 | /** 50 | * 1. Correct `inline-block` display not defined in IE 8/9. 51 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. 52 | */ 53 | 54 | audio, 55 | canvas, 56 | progress, 57 | video { 58 | display: inline-block; /* 1 */ 59 | vertical-align: baseline; /* 2 */ 60 | } 61 | 62 | /** 63 | * Prevent modern browsers from displaying `audio` without controls. 64 | * Remove excess height in iOS 5 devices. 65 | */ 66 | 67 | audio:not([controls]) { 68 | display: none; 69 | height: 0; 70 | } 71 | 72 | /** 73 | * Address `[hidden]` styling not present in IE 8/9/10. 74 | * Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22. 75 | */ 76 | 77 | [hidden], 78 | template { 79 | display: none; 80 | } 81 | 82 | /* Links 83 | ========================================================================== */ 84 | 85 | /** 86 | * Remove the gray background color from active links in IE 10. 87 | */ 88 | 89 | a { 90 | background-color: transparent; 91 | } 92 | 93 | /** 94 | * Improve readability of focused elements when they are also in an 95 | * active/hover state. 96 | */ 97 | 98 | a:active, 99 | a:hover { 100 | outline: 0; 101 | } 102 | 103 | /* Text-level semantics 104 | ========================================================================== */ 105 | 106 | /** 107 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome. 108 | */ 109 | 110 | abbr[title] { 111 | border-bottom: 1px dotted; 112 | } 113 | 114 | /** 115 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. 116 | */ 117 | 118 | b, 119 | strong { 120 | font-weight: bold; 121 | } 122 | 123 | /** 124 | * Address styling not present in Safari and Chrome. 125 | */ 126 | 127 | dfn { 128 | font-style: italic; 129 | } 130 | 131 | /** 132 | * Address variable `h1` font-size and margin within `section` and `article` 133 | * contexts in Firefox 4+, Safari, and Chrome. 134 | */ 135 | 136 | h1 { 137 | font-size: 2em; 138 | margin: 0.67em 0; 139 | } 140 | 141 | /** 142 | * Address styling not present in IE 8/9. 143 | */ 144 | 145 | mark { 146 | background: #ff0; 147 | color: #000; 148 | } 149 | 150 | /** 151 | * Address inconsistent and variable font size in all browsers. 152 | */ 153 | 154 | small { 155 | font-size: 80%; 156 | } 157 | 158 | /** 159 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 160 | */ 161 | 162 | sub, 163 | sup { 164 | font-size: 75%; 165 | line-height: 0; 166 | position: relative; 167 | vertical-align: baseline; 168 | } 169 | 170 | sup { 171 | top: -0.5em; 172 | } 173 | 174 | sub { 175 | bottom: -0.25em; 176 | } 177 | 178 | /* Embedded content 179 | ========================================================================== */ 180 | 181 | /** 182 | * Remove border when inside `a` element in IE 8/9/10. 183 | */ 184 | 185 | img { 186 | border: 0; 187 | } 188 | 189 | /** 190 | * Correct overflow not hidden in IE 9/10/11. 191 | */ 192 | 193 | svg:not(:root) { 194 | overflow: hidden; 195 | } 196 | 197 | /* Grouping content 198 | ========================================================================== */ 199 | 200 | /** 201 | * Address margin not present in IE 8/9 and Safari. 202 | */ 203 | 204 | figure { 205 | margin: 1em 40px; 206 | } 207 | 208 | /** 209 | * Address differences between Firefox and other browsers. 210 | */ 211 | 212 | hr { 213 | box-sizing: content-box; 214 | height: 0; 215 | } 216 | 217 | /** 218 | * Contain overflow in all browsers. 219 | */ 220 | 221 | pre { 222 | overflow: auto; 223 | } 224 | 225 | /** 226 | * Address odd `em`-unit font size rendering in all browsers. 227 | */ 228 | 229 | code, 230 | kbd, 231 | pre, 232 | samp { 233 | font-family: monospace, monospace; 234 | font-size: 1em; 235 | } 236 | 237 | /* Forms 238 | ========================================================================== */ 239 | 240 | /** 241 | * Known limitation: by default, Chrome and Safari on OS X allow very limited 242 | * styling of `select`, unless a `border` property is set. 243 | */ 244 | 245 | /** 246 | * 1. Correct color not being inherited. 247 | * Known issue: affects color of disabled elements. 248 | * 2. Correct font properties not being inherited. 249 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. 250 | */ 251 | 252 | button, 253 | input, 254 | optgroup, 255 | select, 256 | textarea { 257 | color: inherit; /* 1 */ 258 | font: inherit; /* 2 */ 259 | margin: 0; /* 3 */ 260 | } 261 | 262 | /** 263 | * Address `overflow` set to `hidden` in IE 8/9/10/11. 264 | */ 265 | 266 | button { 267 | overflow: visible; 268 | } 269 | 270 | /** 271 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 272 | * All other form control elements do not inherit `text-transform` values. 273 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. 274 | * Correct `select` style inheritance in Firefox. 275 | */ 276 | 277 | button, 278 | select { 279 | text-transform: none; 280 | } 281 | 282 | /** 283 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 284 | * and `video` controls. 285 | * 2. Correct inability to style clickable `input` types in iOS. 286 | * 3. Improve usability and consistency of cursor style between image-type 287 | * `input` and others. 288 | */ 289 | 290 | button, 291 | html input[type="button"], /* 1 */ 292 | input[type="reset"], 293 | input[type="submit"] { 294 | -webkit-appearance: button; /* 2 */ 295 | cursor: pointer; /* 3 */ 296 | } 297 | 298 | /** 299 | * Re-set default cursor for disabled elements. 300 | */ 301 | 302 | button[disabled], 303 | html input[disabled] { 304 | cursor: default; 305 | } 306 | 307 | /** 308 | * Remove inner padding and border in Firefox 4+. 309 | */ 310 | 311 | button::-moz-focus-inner, 312 | input::-moz-focus-inner { 313 | border: 0; 314 | padding: 0; 315 | } 316 | 317 | /** 318 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in 319 | * the UA stylesheet. 320 | */ 321 | 322 | input { 323 | line-height: normal; 324 | } 325 | 326 | /** 327 | * It's recommended that you don't attempt to style these elements. 328 | * Firefox's implementation doesn't respect box-sizing, padding, or width. 329 | * 330 | * 1. Address box sizing set to `content-box` in IE 8/9/10. 331 | * 2. Remove excess padding in IE 8/9/10. 332 | */ 333 | 334 | input[type="checkbox"], 335 | input[type="radio"] { 336 | box-sizing: border-box; /* 1 */ 337 | padding: 0; /* 2 */ 338 | } 339 | 340 | /** 341 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain 342 | * `font-size` values of the `input`, it causes the cursor style of the 343 | * decrement button to change from `default` to `text`. 344 | */ 345 | 346 | input[type="number"]::-webkit-inner-spin-button, 347 | input[type="number"]::-webkit-outer-spin-button { 348 | height: auto; 349 | } 350 | 351 | /** 352 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome. 353 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome. 354 | */ 355 | 356 | input[type="search"] { 357 | -webkit-appearance: textfield; /* 1 */ 358 | box-sizing: content-box; /* 2 */ 359 | } 360 | 361 | /** 362 | * Remove inner padding and search cancel button in Safari and Chrome on OS X. 363 | * Safari (but not Chrome) clips the cancel button when the search input has 364 | * padding (and `textfield` appearance). 365 | */ 366 | 367 | input[type="search"]::-webkit-search-cancel-button, 368 | input[type="search"]::-webkit-search-decoration { 369 | -webkit-appearance: none; 370 | } 371 | 372 | /** 373 | * Define consistent border, margin, and padding. 374 | */ 375 | 376 | fieldset { 377 | border: 1px solid #c0c0c0; 378 | margin: 0 2px; 379 | padding: 0.35em 0.625em 0.75em; 380 | } 381 | 382 | /** 383 | * 1. Correct `color` not being inherited in IE 8/9/10/11. 384 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 385 | */ 386 | 387 | legend { 388 | border: 0; /* 1 */ 389 | padding: 0; /* 2 */ 390 | } 391 | 392 | /** 393 | * Remove default vertical scrollbar in IE 8/9/10/11. 394 | */ 395 | 396 | textarea { 397 | overflow: auto; 398 | } 399 | 400 | /** 401 | * Don't inherit the `font-weight` (applied by a rule above). 402 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. 403 | */ 404 | 405 | optgroup { 406 | font-weight: bold; 407 | } 408 | 409 | /* Tables 410 | ========================================================================== */ 411 | 412 | /** 413 | * Remove most spacing between table cells. 414 | */ 415 | 416 | table { 417 | border-collapse: collapse; 418 | border-spacing: 0; 419 | } 420 | 421 | td, 422 | th { 423 | padding: 0; 424 | } 425 | -------------------------------------------------------------------------------- /todo-clj/resources/public/css/papier-1.3.1.min.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}button,html,input,input[type=button],textarea{box-sizing:border-box;font:400 16px 'Source Sans Pro','Helvetica Neue',Helvetica,arial,sans-serif}*,:after,:before{box-sizing:inherit}.depth-0{box-shadow:none}.card,.depth-1,.pager,.panel,.tabs,button,input:focus,input[type=button],textarea:focus{box-shadow:0 1px 3px rgba(0,0,0,.12),0 1px 2px rgba(0,0,0,.24)}.depth-2{box-shadow:0 3px 6px rgba(0,0,0,.16),0 3px 6px rgba(0,0,0,.23)}.depth-3,button:hover{box-shadow:0 10px 20px rgba(0,0,0,.19),0 6px 6px rgba(0,0,0,.23)}.depth-4{box-shadow:0 14px 28px rgba(0,0,0,.25),0 10px 10px rgba(0,0,0,.22)}.depth-5{box-shadow:0 19px 38px rgba(0,0,0,.3),0 15px 12px rgba(0,0,0,.22)}.round-1,pre{border-radius:1px}.card,.round-2{border-radius:2px}.round-3{border-radius:3px}.round-4{border-radius:4px}.round-5{border-radius:5px}.round-10{border-radius:10px}.round{border-radius:100%}.inner-seamless{padding:0}.outer-seamless{margin:0}.subtle{opacity:.5}.full-width{width:100%}.uppercase{text-transform:uppercase}.window-height{height:100vh}.vertical-center-container{display:table;width:100%}.vertical-center{display:table-cell;vertical-align:middle}@media screen and (min-width:568px){.s-padding{padding:.5em}.m-padding{padding:1em}.l-padding{padding:1.5em}.s-margin{margin:.5em}.m-margin{margin:1em}.l-margin{margin:1.5em}}.s-padding{padding:.25em}.m-padding{padding:.5em}.l-padding{padding:.75em}.s-margin{margin:.25em}.m-margin{margin:.5em}.l-margin{margin:.75em}.left{text-align:left}.center{text-align:center}.right{text-align:right}.inline-block{display:inline-block}.hidden{display:none}.bg-no,.bg-no a{background:0 0}.bg-subtle,.bg-subtle a{background:#f8f8f8;color:#000}.bg-white,.bg-white a{background:#fff;color:#000}.bg-red,.bg-red a{background:#f44336;color:#fff}.bg-pink,.bg-pink a{background:#e91e63;color:#fff}.bg-purple,.bg-purple a{background:#9c27b0;color:#fff}.bg-deep-purple,.bg-deep-purple a{background:#673ab7;color:#fff}.bg-indigo,.bg-indigo a{background:#3f51b5;color:#fff}.bg-blue,.bg-blue a{background:#2196f3;color:#fff}.bg-light-blue,.bg-light-blue a{background:#03a9f4;color:#fff}.bg-cyan,.bg-cyan a{background:#00bcd4;color:#fff}.bg-teal,.bg-teal a{background:#009688;color:#fff}.bg-green,.bg-green a{background:#4caf50;color:#fff}.bg-light-green,.bg-light-green a{background:#8bc34a;color:#fff}.bg-lime,.bg-lime a{background:#cddc39;color:#fff}.bg-yellow,.bg-yellow a{background:#ffeb3b;color:#fff}.bg-amber,.bg-amber a{background:#ffc107;color:#fff}.bg-orange,.bg-orange a{background:#ff9800;color:#fff}.bg-deep-orange,.bg-deep-orange a{background:#ff5722;color:#fff}.bg-brown,.bg-brown a{background:#795548;color:#fff}.bg-grey,.bg-grey a{background:#9e9e9e;color:#fff}.bg-blue-grey,.bg-blue-grey a{background:#607d8b;color:#fff}.bg-black,.bg-black a{background:#000;color:#fff}.bg-almost-black,.bg-almost-black a{background:#333;color:#fff}.card{padding:.25em;margin:.25em;background:#fff}@media screen and (min-width:568px){.card{padding:.5em;margin:.5em}}.row:after{content:'';display:table;clear:both}[class*=col-]{float:left;width:100%;min-height:1px}.col-0{width:0}.col-1{width:8.3333333333333%}.col-2{width:16.6666666666667%}.col-3{width:25%}.col-4{width:33.3333333333333%}.col-5{width:41.6666666666667%}.col-6{width:50%}.col-7{width:58.3333333333333%}.col-8{width:66.6666666666667%}.col-9{width:75%}.col-10{width:83.3333333333333%}.col-11{width:91.6666666666667%}.col-12{width:100%}@media screen and (min-width:568px){.col-sm-0{width:0}.col-sm-1{width:8.3333333333333%}.col-sm-2{width:16.6666666666667%}.col-sm-3{width:25%}.col-sm-4{width:33.3333333333333%}.col-sm-5{width:41.6666666666667%}.col-sm-6{width:50%}.col-sm-7{width:58.3333333333333%}.col-sm-8{width:66.6666666666667%}.col-sm-9{width:75%}.col-sm-10{width:83.3333333333333%}.col-sm-11{width:91.6666666666667%}.col-sm-12{width:100%}}@media screen and (min-width:768px){.col-md-0{width:0}.col-md-1{width:8.3333333333333%}.col-md-2{width:16.6666666666667%}.col-md-3{width:25%}.col-md-4{width:33.3333333333333%}.col-md-5{width:41.6666666666667%}.col-md-6{width:50%}.col-md-7{width:58.3333333333333%}.col-md-8{width:66.6666666666667%}.col-md-9{width:75%}.col-md-10{width:83.3333333333333%}.col-md-11{width:91.6666666666667%}.col-md-12{width:100%}}@media screen and (min-width:1024px){.col-lg-0{width:0}.col-lg-1{width:8.3333333333333%}.col-lg-2{width:16.6666666666667%}.col-lg-3{width:25%}.col-lg-4{width:33.3333333333333%}.col-lg-5{width:41.6666666666667%}.col-lg-6{width:50%}.col-lg-7{width:58.3333333333333%}.col-lg-8{width:66.6666666666667%}.col-lg-9{width:75%}.col-lg-10{width:83.3333333333333%}.col-lg-11{width:91.6666666666667%}.col-lg-12{width:100%}}@media screen and (min-width:1280px){.col-xl-0{width:0}.col-xl-1{width:8.3333333333333%}.col-xl-2{width:16.6666666666667%}.col-xl-3{width:25%}.col-xl-4{width:33.3333333333333%}.col-xl-5{width:41.6666666666667%}.col-xl-6{width:50%}.col-xl-7{width:58.3333333333333%}.col-xl-8{width:66.6666666666667%}.col-xl-9{width:75%}.col-xl-10{width:83.3333333333333%}.col-xl-11{width:91.6666666666667%}.col-xl-12{width:100%}}button,input[type=button]{border:0;border-radius:3px;background:0 0;padding:.5em 1.5em;box-shadow:0 1px 3px rgba(0,0,0,.12),0 1px 2px rgba(0,0,0,.24);text-shadow:1px 1px rgba(255,255,255,.1);outline:0;margin:.25em 0;-webkit-transition:all 100ms;transition:all 100ms}button:last-child,input[type=button]:last-child{margin-bottom:0}button:hover,input[type=button]:hover{-webkit-transform:translateY(-2px);-ms-transform:translateY(-2px);transform:translateY(-2px)}button,button.bg-white,input[type=button]{color:#000}button a,button.bg-white a,input[type=button] a{color:#000;text-decoration:none}button.xs{font-size:.65em}button.s{font-size:.75em}button.m{font-size:1em}button.l{font-size:1.25em}button.xl{font-size:1.5em}button.disabled,button[disabled]{background:#eaeaea;color:#a8a8a8}button.loose{padding:1em 2em}button .icon{margin-right:.5em}input,textarea{margin:.25em 0;padding:.5em;border-radius:2px;border:1px solid #ccc;box-sizing:border-box}input:last-child,textarea:last-child{margin-bottom:0}html input[disabled],html textarea[disabled]{background:#eee;cursor:not-allowed}[readonly]{background:#eee;color:#000}input:focus,textarea:focus{-webkit-transition:all 250ms;transition:all 250ms;-webkit-transform:translateY(-1px);-ms-transform:translateY(-1px);transform:translateY(-1px);outline:0;border:1px solid #2196f3}.pager{list-style:none;margin-top:.5em;padding:0;display:inline-block}.pager:first-child{margin-top:0}.pager:after{content:'';display:table;clear:both}.pager li{margin:0;padding:.5em .75em;float:left;display:inline-block;border-right:1px solid #eee;line-height:1;vertical-align:middle;-webkit-transition:all 250ms;transition:all 250ms}.pager li a{text-decoration:none;color:#000}.pager li.active{background:#f2f2f2;font-weight:700}.pager li:hover{background:#eee}:last-child{margin-bottom:0}@media screen and (min-width:568px){.pager{margin-top:1em}}.alert{padding:.25em;margin:.25em 0;border-radius:2px}.alert:last-child{margin-bottom:0}@media screen and (min-width:568px){.alert{padding:1em;margin:.5em 0}}.alert-success{background:#dbefdc;color:#265828}.alert-info{background:#d3eafd;color:#074c83}.alert-warning{background:#ffeacc;color:#804c00}.alert-danger{background:#fdd9d7;color:#8d1108}table{border:1px solid #eee;margin:.25em 0}table:last-child{margin-bottom:0}table thead{background:#eee}table td,table th{padding:.5em;border-right:1px solid #eee}table tr{border-bottom:1px solid #eee}table tr:last-child{border-bottom:0}table .zebra>:nth-child(even),table.zebra>:nth-child(even){background:#f9f9f9}table .zebra>:nth-child(odd),table.zebra>:nth-child(odd){background:#fff}table .hover>*,table.hover>*{-webkit-transition:all 250ms;transition:all 250ms}table .hover>:hover,table.hover>:hover{background:#e5f7ff}@media screen and (min-width:568px){table{margin:.5em 0}}hr{border:0;border-bottom:1px solid #eee;margin:1em 0}@media screen and (min-width:568px){hr{margin:2em 0}}.panel{margin:.25em 0}.panel:last-child{margin-bottom:0}.panel .main,.panel header,.panel main{padding:.5em}.panel header{font-weight:700;border-bottom:1px solid #eee}@media screen and (min-width:568px){.panel{margin:.5em 0}.panel .main,.panel header,.panel main{padding:.75em}}.tabs{list-style:none;margin:.25em 0;padding:0;border-top-left-radius:2px;border-top-right-radius:2px;text-transform:uppercase}.tabs:last-child{margin-bottom:0}.tabs.tabs-justified li{text-align:center;display:table-cell;width:1%}.tabs li{list-style:none;display:inline-block;padding:.5em;margin:0;opacity:.5;-webkit-transition:all 100ms;transition:all 100ms;border-bottom:2px solid transparent}.tabs li.active,.tabs li:hover{opacity:1;border-bottom:2px solid #fff}.tabs li a{display:block;text-decoration:none}@media screen and (min-width:568px){.tabs{margin:.5em 0}.tabs li{padding:1em}}blockquote,h1,h2,h3,h4,h5,h6,p,pre{margin:.5em .25em;padding:0}blockquote{padding:.5em .5em .5em 1em;box-shadow:inset 5px 0 #eee}pre{padding:.5em;background:#eee} -------------------------------------------------------------------------------- /todo-clj/resources/public/css/style.css: -------------------------------------------------------------------------------- 1 | header.top-bar { 2 | padding: 5px; 3 | } 4 | 5 | a.wide-link { 6 | margin: 0 5px; 7 | } 8 | 9 | li.error-message { 10 | color: red; 11 | } -------------------------------------------------------------------------------- /todo-clj/resources/public/js/main.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayato-p/intro-web-clojure/3567218b62d6d9445c7ca8e5cc3ced5133a8166c/todo-clj/resources/public/js/main.js -------------------------------------------------------------------------------- /todo-clj/src/todo_clj/core.clj: -------------------------------------------------------------------------------- 1 | (ns todo-clj.core 2 | (:require [compojure.core :refer [routes]] 3 | [ring.adapter.jetty :as server] 4 | [todo-clj.handler.main :refer [main-routes]] 5 | [todo-clj.handler.todo :refer [todo-routes]] 6 | [todo-clj.middleware :refer [middleware-set]])) 7 | 8 | (defonce server (atom nil)) 9 | 10 | (def app 11 | (middleware-set 12 | (routes 13 | todo-routes 14 | main-routes))) 15 | 16 | (defn start-server [& {:keys [host port join?] 17 | :or {host "localhost" port 3000 join? false}}] 18 | (let [port (if (string? port) (Integer/parseInt port) port)] 19 | (when-not @server 20 | (reset! server (server/run-jetty #'app {:host host :port port :join? join?}))))) 21 | 22 | (defn stop-server [] 23 | (when @server 24 | (.stop @server) 25 | (reset! server nil))) 26 | 27 | (defn restart-server [] 28 | (when @server 29 | (stop-server) 30 | (start-server))) 31 | -------------------------------------------------------------------------------- /todo-clj/src/todo_clj/db.clj: -------------------------------------------------------------------------------- 1 | (ns todo-clj.db 2 | (:require [clojure.java.jdbc :as jdbc] 3 | [environ.core :refer [env]])) 4 | 5 | (def db-spec 6 | (:db env)) 7 | 8 | (defn migrated? [] 9 | (pos? (count (jdbc/query db-spec "select tablename from pg_tables where schemaname = 'public'")))) 10 | 11 | (defn migrate [] 12 | (when-not (migrated?) 13 | (jdbc/db-do-commands 14 | db-spec 15 | (jdbc/create-table-ddl :todo [:id :serial] [:title :varchar])))) 16 | -------------------------------------------------------------------------------- /todo-clj/src/todo_clj/db/todo.clj: -------------------------------------------------------------------------------- 1 | (ns todo-clj.db.todo 2 | (:require [clojure.java.jdbc :as jdbc] 3 | [todo-clj.db :as db])) 4 | 5 | (defn save-todo [title] 6 | (jdbc/insert! db/db-spec :todo {:title title})) 7 | 8 | (defn update-todo [id title] 9 | (jdbc/update! db/db-spec :todo {:title title} ["id = ?" id])) 10 | 11 | (defn find-todo-all [] 12 | (jdbc/query db/db-spec "select * from todo")) 13 | 14 | (defn find-first-todo [id] 15 | (first (jdbc/query db/db-spec ["select * from todo where id = ?" id]))) 16 | 17 | (defn delete-todo [id] 18 | (jdbc/delete! db/db-spec :todo ["id = ?" id])) 19 | -------------------------------------------------------------------------------- /todo-clj/src/todo_clj/handler/main.clj: -------------------------------------------------------------------------------- 1 | (ns todo-clj.handler.main 2 | (:require [compojure.core :refer [defroutes GET]] 3 | [compojure.route :as route] 4 | [todo-clj.util.response :as res] 5 | [todo-clj.view.main :as view])) 6 | 7 | (defn home [req] 8 | (-> (view/home-view req) 9 | res/ok 10 | res/html)) 11 | 12 | (defroutes main-routes 13 | (GET "/" _ home) 14 | (route/not-found res/not-found!)) 15 | -------------------------------------------------------------------------------- /todo-clj/src/todo_clj/handler/todo.clj: -------------------------------------------------------------------------------- 1 | (ns todo-clj.handler.todo 2 | (:require [bouncer.validators :as v] 3 | [compojure.core :refer [defroutes context GET POST]] 4 | [todo-clj.db.todo :as todo] 5 | [todo-clj.util.response :as res] 6 | [todo-clj.util.validation :as uv] 7 | [todo-clj.view.todo :as view])) 8 | 9 | (def todo-validator {:title [[v/required :message "TODO を入力してください"]]}) 10 | 11 | (defn todo-index [req] 12 | (let [todo-list (todo/find-todo-all)] 13 | (-> (view/todo-index-view req todo-list) 14 | res/ok 15 | res/html))) 16 | 17 | (defn todo-new [req] 18 | (-> (view/todo-new-view req) 19 | res/ok 20 | res/html)) 21 | 22 | (defn todo-new-post [{:as req :keys [params]}] 23 | (uv/with-fallback #(todo-new (assoc req :errors %)) 24 | (let [params (uv/validate params todo-validator)] 25 | (if-let [todo (first (todo/save-todo (:title params)))] 26 | (-> (res/found (str "/todo/" (:id todo))) 27 | (assoc :flash {:msg "TODO を正常に追加しました。"}) 28 | res/html) 29 | (res/internal-server-error!))))) 30 | 31 | (defn todo-search [req] "TODO search") 32 | 33 | (defn todo-show [{:as req :keys [params]}] 34 | (if-let [todo (todo/find-first-todo (Long/parseLong (:todo-id params)))] 35 | (-> (view/todo-show-view req todo) 36 | res/ok 37 | res/html) 38 | (res/not-found!))) 39 | 40 | (defn todo-edit [{:as req :keys [params]}] 41 | (if-let [todo (todo/find-first-todo (Long/parseLong (:todo-id params)))] 42 | (-> (view/todo-edit-view req todo) 43 | res/ok 44 | res/html) 45 | (res/not-found!))) 46 | 47 | (defn todo-edit-post [{:as req :keys [params]}] 48 | (uv/with-fallback #(todo-edit (assoc req :errors %)) 49 | (let [params (uv/validate params todo-validator) 50 | todo-id (Long/parseLong (:todo-id params))] 51 | (if (pos? (first (todo/update-todo todo-id (:title params)))) 52 | (-> (res/found (str "/todo/" todo-id)) 53 | (assoc :flash {:msg "TODO を正常に更新しました"}) 54 | res/html) 55 | (res/conflict!))))) 56 | 57 | (defn todo-delete [{:as req :keys [params]}] 58 | (if-let [todo (todo/find-first-todo (Long/parseLong (:todo-id params)))] 59 | (-> (view/todo-delete-view req todo) 60 | res/ok 61 | res/html) 62 | (res/not-found!))) 63 | 64 | (defn todo-delete-post [{:as req :keys [params]}] 65 | (let [todo-id (Long/parseLong (:todo-id params))] 66 | (if (pos? (first (todo/delete-todo todo-id))) 67 | (-> (res/found "/todo") 68 | (assoc :flash {:msg "TODO を正常に削除しました"}) 69 | res/html) 70 | (res/conflict!)))) 71 | 72 | (defroutes todo-routes 73 | (context "/todo" _ 74 | (GET "/" _ todo-index) 75 | (GET "/new" _ todo-new) 76 | (POST "/new" _ todo-new-post) 77 | (GET "/search" _ todo-search) 78 | (context "/:todo-id" _ 79 | (GET "/" _ todo-show) 80 | (GET "/edit" _ todo-edit) 81 | (POST "/edit" _ todo-edit-post) 82 | (GET "/delete" _ todo-delete) 83 | (POST "/delete" _ todo-delete-post)))) 84 | -------------------------------------------------------------------------------- /todo-clj/src/todo_clj/main.clj: -------------------------------------------------------------------------------- 1 | (ns todo-clj.main 2 | (:require [todo-clj.core :as core] 3 | [todo-clj.db :as db]) 4 | (:gen-class)) 5 | 6 | (defn -main [& {:as args}] 7 | (db/migrate) 8 | (core/start-server 9 | :host (get args "host") :port (get args "port") :join? true)) 10 | -------------------------------------------------------------------------------- /todo-clj/src/todo_clj/middleware.clj: -------------------------------------------------------------------------------- 1 | (ns todo-clj.middleware 2 | (:require [environ.core :refer [env]] 3 | [ring.middleware.defaults :as defaults] 4 | [todo-clj.middleware.http-response :as http-response])) 5 | 6 | (defn- try-resolve [sym] 7 | (try 8 | (require (symbol (namespace sym))) 9 | (resolve sym) 10 | (catch java.io.FileNotFoundException _) 11 | (catch RuntimeException _))) 12 | 13 | (defn wrap-dev [handler] 14 | {:pre [(or (fn? handler) (and (var? handler) (fn? (deref handler))))]} 15 | (let [wrap-exceptions (try-resolve 'prone.middleware/wrap-exceptions) 16 | wrap-reload (try-resolve 'ring.middleware.reload/wrap-reload)] 17 | (if (and wrap-reload wrap-exceptions) 18 | (-> handler 19 | wrap-exceptions 20 | wrap-reload) 21 | (throw (RuntimeException. "Middleware requires ring/ring-devel and prone;"))))) 22 | 23 | (def ^:private wrap #'defaults/wrap) 24 | 25 | (defn middleware-set [handler] 26 | (-> handler 27 | http-response/wrap-http-response 28 | (wrap wrap-dev (:dev env)) 29 | (defaults/wrap-defaults defaults/site-defaults))) 30 | -------------------------------------------------------------------------------- /todo-clj/src/todo_clj/middleware/http_response.clj: -------------------------------------------------------------------------------- 1 | (ns todo-clj.middleware.http-response 2 | (:require [hiccup.core :as h] 3 | [ring.util.http-status :as status] 4 | [slingshot.slingshot :refer [try+]] 5 | [todo-clj.util.response :as res])) 6 | 7 | (defn- error-view [{:as response :keys [status]}] 8 | (let [{:keys [name description]} (status/status status)] 9 | (-> `([:h1 ~name] 10 | [:h2 ~description]) 11 | h/html 12 | res/ok 13 | res/html))) 14 | 15 | (defn wrap-http-response [handler] 16 | (fn [req] 17 | (try+ 18 | (handler req) 19 | (catch [:type :ring.util.http-response/response] {:keys [response]} 20 | (error-view response))))) 21 | -------------------------------------------------------------------------------- /todo-clj/src/todo_clj/util/response.clj: -------------------------------------------------------------------------------- 1 | (ns todo-clj.util.response 2 | (:require [potemkin :as p] 3 | [ring.util.http-response :as res])) 4 | 5 | (defmacro import-ns [ns-sym] 6 | (do 7 | `(p/import-vars 8 | [~ns-sym 9 | ~@(map first (ns-publics ns-sym))]))) 10 | 11 | (import-ns ring.util.http-response) 12 | 13 | (defn html [res] 14 | (res/content-type res "text/html; charset=utf-8")) 15 | -------------------------------------------------------------------------------- /todo-clj/src/todo_clj/util/validation.clj: -------------------------------------------------------------------------------- 1 | (ns todo-clj.util.validation 2 | (:require [bouncer.core :as b] 3 | [slingshot.slingshot :refer [try+ throw+]])) 4 | 5 | (defn validate [& args] 6 | (let [[errors org+errors] (apply b/validate args)] 7 | (if (nil? errors) 8 | org+errors 9 | (throw+ {:type ::validation-error :errors errors})))) 10 | 11 | (defmacro with-fallback [fallback & body] 12 | `(try+ 13 | ~@body 14 | (catch [:type ::validation-error] {:keys [errors#]} 15 | (~fallback errors#)))) 16 | -------------------------------------------------------------------------------- /todo-clj/src/todo_clj/view/layout.clj: -------------------------------------------------------------------------------- 1 | (ns todo-clj.view.layout 2 | (:require [hiccup.page :refer [html5 include-css include-js]])) 3 | 4 | (defn common [req & body] 5 | (html5 6 | [:head 7 | [:title "TODO-clj"] 8 | (include-css "/css/normalize.css" 9 | "/css/papier-1.3.1.min.css" 10 | "/css/style.css") 11 | (include-js "/js/main.js")] 12 | [:body 13 | [:header.top-bar.bg-green.depth-3 "TODO-clj"] 14 | [:main body]])) 15 | -------------------------------------------------------------------------------- /todo-clj/src/todo_clj/view/main.clj: -------------------------------------------------------------------------------- 1 | (ns todo-clj.view.main 2 | (:require [todo-clj.view.layout :as layout])) 3 | 4 | (defn home-view [req] 5 | (->> [:section.card 6 | [:h2 "ホーム画面"] 7 | [:a {:href "/todo"} "TODO 一覧"]] 8 | (layout/common req))) 9 | -------------------------------------------------------------------------------- /todo-clj/src/todo_clj/view/todo.clj: -------------------------------------------------------------------------------- 1 | (ns todo-clj.view.todo 2 | (:require [hiccup.form :as hf] 3 | [ring.util.anti-forgery :refer [anti-forgery-field]] 4 | [todo-clj.view.layout :as layout])) 5 | 6 | (defn error-messages [req] 7 | (when-let [errors (:errors req)] 8 | [:ul 9 | (for [[k v] errors 10 | msg v] 11 | [:li.error-message msg])])) 12 | 13 | (defn todo-index-view [req todo-list] 14 | (->> [:section.card 15 | (when-let [{:keys [msg]} (:flash req)] 16 | [:div.alert.alert-success [:strong msg]]) 17 | [:h2 "TODO 一覧"] 18 | [:ul 19 | (for [{:keys [id title]} todo-list] 20 | [:li [:a {:href (str "/todo/" id)} title]])] 21 | [:a.wide-link {:href "/todo/new"} "追加する"]] 22 | (layout/common req))) 23 | 24 | (defn todo-new-view [req] 25 | (->> [:section.card 26 | [:h2 "TODO 追加"] 27 | (hf/form-to 28 | [:post "/todo/new"] 29 | (anti-forgery-field) 30 | (error-messages req) 31 | [:input {:name :title :placeholder "TODO を入力してください"}] 32 | [:button.bg-blue "追加する"])] 33 | (layout/common req))) 34 | 35 | (defn todo-show-view [req todo] 36 | (let [todo-id (:id todo)] 37 | (->> [:section.card 38 | (when-let [{:keys [msg]} (:flash req)] 39 | [:div.alert.alert-success [:strong msg]]) 40 | [:h2 (:title todo)] 41 | [:a.wide-link {:href (str "/todo/" todo-id "/edit")} "修正する"] 42 | [:a.wide-link {:href (str "/todo/" todo-id "/delete")} "削除する"]] 43 | (layout/common req)))) 44 | 45 | (defn todo-edit-view [req todo] 46 | (let [todo-id (get-in req [:params :todo-id])] 47 | (->> [:section.card 48 | [:h2 "TODO 編集"] 49 | (hf/form-to 50 | [:post (str "/todo/" todo-id "/edit")] 51 | (anti-forgery-field) 52 | (error-messages req) 53 | [:input {:name :title :value (:title todo) 54 | :placeholder "TODO を入力してください"}] 55 | [:button.bg-blue "更新する"])] 56 | (layout/common req)))) 57 | 58 | (defn todo-delete-view [req todo] 59 | (let [todo-id (get-in req [:params :todo-id])] 60 | (->> [:section.card 61 | [:h2 "TODO 削除"] 62 | (hf/form-to 63 | [:post (str "/todo/" todo-id "/delete")] 64 | (anti-forgery-field) 65 | [:p "次の TODO を本当に削除しますか?"] 66 | [:p "*" (:title todo)] 67 | [:button.bg-red "削除する"])] 68 | (layout/common req)))) 69 | -------------------------------------------------------------------------------- /todo-clj/test/todo_clj/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns todo-clj.core-test 2 | (:require [clojure.test :refer :all] 3 | [todo-clj.core :refer :all])) 4 | 5 | (deftest a-test 6 | (testing "FIXME, I fail." 7 | (is (= 0 1)))) 8 | --------------------------------------------------------------------------------