1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 | _ = require 'underscore'
|
22 | crypto = require 'crypto'
|
23 | express = require 'express'
|
24 | fs = require 'fs'
|
25 | mongoose = require 'mongoose'
|
26 | nib = require 'nib'
|
27 | stitch = require 'stitch'
|
28 | stylus = require 'stylus'
|
29 | uglify = require 'uglify-js'
|
30 |
|
31 |
|
32 | module.exports.middleware = (options) ->
|
33 | desktop = stitch.createPackage
|
34 | paths: ["#{__dirname}/desktop"]
|
35 | dependencies: [
|
36 | "#{__dirname}/public/js/jquery.js"
|
37 | "#{__dirname}/public/js/jquery-ui.js"
|
38 | "#{__dirname}/public/js/underscore.js"
|
39 | "#{__dirname}/public/js/backbone.js"
|
40 | ]
|
41 |
|
42 | desktop.compile (err, source) ->
|
43 | {gen_code, ast_squeeze, ast_mangle} = uglify.uglify
|
44 | minified = gen_code ast_squeeze ast_mangle uglify.parser.parse source
|
45 | fs.writeFile "#{__dirname}/public/tomato-desktop.js", minified, (err) ->
|
46 | throw err if err
|
47 | console.log "compiled #{__dirname}/public/tomato-desktop.js"
|
48 |
|
49 |
|
50 | ALPHA = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-'
|
51 | rc = -> ALPHA[Math.floor Math.random() * ALPHA.length]
|
52 | NOTPRESENT = (rc() for x in [0...100]).join ''
|
53 |
|
54 | TIMERS = _.extend { workSec: 25 * 60, breakSec: 5 * 60 }, options?.timers
|
55 |
|
56 | mongoose.connect options?.db or 'mongodb://localhost/tomato'
|
57 |
|
58 |
|
59 | TaskSchema = new mongoose.Schema
|
60 | id: String
|
61 | name: String
|
62 | order:
|
63 | type: Number
|
64 | default: 0
|
65 | min: -1e100
|
66 | max: 1e100
|
67 | createdAt: Date
|
68 | updatedAt: Date
|
69 | finishedAt: Date
|
70 | tomatoes: [
|
71 | startedAt: Date
|
72 | finishedAt: Date
|
73 | ]
|
74 |
|
75 |
|
76 | BreakSchema = new mongoose.Schema
|
77 | startedAt: Date
|
78 | finishedAt: Date
|
79 | flavor: String
|
80 |
|
81 |
|
82 | TomatoSchema = new mongoose.Schema
|
83 | slug:
|
84 | type: String
|
85 | unique: true
|
86 | trim: true
|
87 | match: /^[^\/]+$/
|
88 | workSec:
|
89 | type: Number
|
90 | min: 1
|
91 | breakSec:
|
92 | type: Number
|
93 | min: 1
|
94 | createdAt: Date
|
95 | updatedAt: Date
|
96 | tasks: [ TaskSchema ]
|
97 | breaks: [ BreakSchema ]
|
98 |
|
99 | Task = mongoose.model 'Task', TaskSchema
|
100 | Break = mongoose.model 'Break', BreakSchema
|
101 | Tomato = mongoose.model 'Tomato', TomatoSchema
|
102 |
|
103 | app = express.createServer()
|
104 |
|
105 | app.configure 'development', ->
|
106 | app.use express.errorHandler dumpExceptions: true, showStack: true
|
107 |
|
108 | app.configure 'production', ->
|
109 | app.use express.errorHandler()
|
110 |
|
111 | app.configure ->
|
112 | app.set 'views', __dirname
|
113 | app.set 'view options', layout: false
|
114 | app.set 'view engine', 'jade'
|
115 | app.use express.logger 'short'
|
116 | app.use stylus.middleware
|
117 | src: "#{__dirname}/public"
|
118 | compile: (str, path) ->
|
119 | stylus(str).set('filename', path).set('compress', true).use(nib())
|
120 | app.use express.methodOverride()
|
121 | app.use express.bodyParser()
|
122 | app.use express.static "#{__dirname}/public"
|
123 | app.use app.router
|
124 |
|
125 | app.get '/desktop.js', desktop.createServer()
|
126 |
|
127 |
|
128 | app.get '/', (req, res) ->
|
129 | id = (rc() for x in [0...16]).join ''
|
130 | now = Date.now()
|
131 | tomato = slug: id, createdAt: now, updatedAt: now, tasks: []
|
132 | new Tomato(_.extend tomato, TIMERS).save (err) ->
|
133 | return res.send(err, 500) if err
|
134 | res.redirect "/#{id}"
|
135 |
|
136 |
|
137 |
|
138 | fetchTomato = (req, res, next) ->
|
139 | conditions = slug: req.param 'tomato'
|
140 | fields = ['slug', 'workSec', 'breakSec', 'updatedAt']
|
141 | Tomato.findOne conditions, fields, (err, tomato) ->
|
142 | return res.send(err, 500) if err
|
143 | return res.send(404) unless tomato
|
144 | res.locals tomato: tomato
|
145 | next()
|
146 |
|
147 |
|
148 | app.get '/:tomato', fetchTomato, (req, res) ->
|
149 | ctx =
|
150 | analytics: options?.analytics
|
151 | basepath: (app.settings.basepath or '').replace /\/$/, ''
|
152 | res.render 'main', ctx
|
153 |
|
154 |
|
155 | app.put '/:tomato', fetchTomato, (req, res) ->
|
156 | tomato = res.local 'tomato'
|
157 | now = Date.now()
|
158 | for key in ['slug', 'workSec', 'breakSec']
|
159 | value = req.param key, NOTPRESENT
|
160 | if value isnt NOTPRESENT
|
161 | tomato.set key, value
|
162 | tomato.set 'updatedAt', now
|
163 | tomato.save (err) ->
|
164 | return res.send(err, 500) if err
|
165 | res.send 200
|
166 |
|
167 |
|
168 | app.del '/:tomato', (req, res) ->
|
169 | Tomato.remove { slug: req.param 'tomato' }, (err) ->
|
170 | return res.send(err, 500) if err
|
171 | res.send 200
|
172 |
|
173 |
|
174 |
|
175 | fetchTasks = (req, res, next) ->
|
176 | conditions = slug: req.param 'tomato'
|
177 | fields = ['slug', 'updatedAt', 'tasks']
|
178 | Tomato.findOne conditions, fields, (err, tomato) ->
|
179 | return res.send(err, 500) if err
|
180 | return res.send(404) unless tomato
|
181 | res.locals tomato: tomato
|
182 | next()
|
183 |
|
184 | fetchTask = (req, res, next) ->
|
185 | id = req.param 'task'
|
186 | conditions = slug: req.param 'tomato'
|
187 | fields = ['slug', 'updatedAt', 'tasks']
|
188 | Tomato.findOne conditions, fields, (err, tomato) ->
|
189 | return res.send(err, 500) if err
|
190 | return res.send(404) unless tomato
|
191 | task = _.find tomato.tasks, (t) -> t.id is id
|
192 | return res.send(404) unless task?
|
193 | res.locals tomato: tomato, task: task
|
194 | next()
|
195 |
|
196 |
|
197 | app.get '/:tomato/tasks', fetchTasks, (req, res) ->
|
198 | res.send res.local('tomato').tasks
|
199 |
|
200 |
|
201 | app.post '/:tomato/tasks', fetchTasks, (req, res) ->
|
202 | hash = (s) ->
|
203 | h = crypto.createHash 'md5'
|
204 | h.update s
|
205 | return h.digest 'hex'
|
206 |
|
207 | tomato = res.local 'tomato'
|
208 | name = req.param 'name'
|
209 | now = Date.now()
|
210 | task =
|
211 | id: hash "#{now}:#{name}"
|
212 | name: name
|
213 | order: req.param('order')
|
214 | createdAt: now
|
215 | updatedAt: now
|
216 | finishedAt: null
|
217 | tomato.tasks.push task
|
218 | tomato.set 'updatedAt', now
|
219 | tomato.save (err) ->
|
220 | return res.send(err, 500) if err
|
221 | res.send task
|
222 |
|
223 |
|
224 | app.get '/:tomato/tasks/:task', fetchTask, (req, res) ->
|
225 | res.send res.local 'task'
|
226 |
|
227 |
|
228 | app.put '/:tomato/tasks/:task', fetchTask, (req, res) ->
|
229 | tomato = res.local 'tomato'
|
230 | task = res.local 'task'
|
231 | now = Date.now()
|
232 | for key in ['name', 'order', 'finishedAt', 'tomatoes']
|
233 | value = req.param key, NOTPRESENT
|
234 | if value isnt NOTPRESENT
|
235 | task.set key, value
|
236 | task.set 'updatedAt', now
|
237 | tomato.set 'updatedAt', now
|
238 | tomato.save (err) ->
|
239 | return res.send(err, 500) if err
|
240 | res.send 200
|
241 |
|
242 |
|
243 | app.del '/:tomato/tasks/:task', fetchTasks, (req, res) ->
|
244 | tomato = res.local 'tomato'
|
245 | now = Date.now()
|
246 | index = _.indexOf _.pluck(tomato.tasks, 'id'), req.param 'task'
|
247 | tomato.tasks.splice index, 1
|
248 | tomato.set 'updatedAt', now
|
249 | tomato.save (err) ->
|
250 | return res.send(err, 500) if err
|
251 | res.send 200
|
252 |
|
253 |
|
254 |
|
255 | fetchBreaks = (req, res, next) ->
|
256 | conditions = slug: req.param 'tomato'
|
257 | fields = ['slug', 'updatedAt', 'breaks']
|
258 | Tomato.findOne conditions, fields, (err, tomato) ->
|
259 | return res.send(err, 500) if err
|
260 | return res.send(404) unless tomato
|
261 | res.locals tomato: tomato
|
262 | next()
|
263 |
|
264 | fetchBreak = (req, res, next) ->
|
265 | id = req.param 'break'
|
266 | conditions = slug: req.param 'tomato'
|
267 | fields = ['slug', 'updatedAt', 'breaks']
|
268 | Tomato.findOne conditions, fields, (err, tomato) ->
|
269 | return res.send(err, 500) if err
|
270 | return res.send(404) unless tomato
|
271 | brake_ = _.find tomato.breaks, (t) -> t.id is id
|
272 | return res.send(404) unless brake
|
273 | res.locals tomato: tomato, break: brake
|
274 | next()
|
275 |
|
276 |
|
277 | app.get '/:tomato/breaks', fetchBreaks, (req, res) ->
|
278 | res.send res.local('tomato').breaks
|
279 |
|
280 |
|
281 | app.post '/:tomato/breaks', fetchBreaks, (req, res) ->
|
282 | hash = (s) ->
|
283 | h = crypto.createHash 'md5'
|
284 | h.update s
|
285 | return h.digest 'hex'
|
286 |
|
287 |
|
288 | app.get '/:tomato/breaks/:break', fetchBreak, (req, res) ->
|
289 | res.send res.local 'break'
|
290 |
|
291 |
|
292 | app.put '/:tomato/breaks/:break', fetchBreak, (req, res) ->
|
293 | tomato = res.local 'tomato'
|
294 | brake = res.local 'break'
|
295 | now = Date.now()
|
296 | for key in ['name', 'order', 'finishedAt', 'tomatoes']
|
297 | value = req.param key, NOTPRESENT
|
298 | if value isnt NOTPRESENT
|
299 | brake.set key, value
|
300 | brake.set 'updatedAt', now
|
301 | tomato.set 'updatedAt', now
|
302 | tomato.save (err) ->
|
303 | return res.send(err, 500) if err
|
304 | res.send 200
|
305 |
|
306 |
|
307 | app.del '/:tomato/breaks/:break', fetchBreaks, (req, res) ->
|
308 | tomato = res.local 'tomato'
|
309 | now = Date.now()
|
310 | index = _.indexOf _.pluck(tomato.breaks, 'id'), req.param 'break'
|
311 | tomato.breaks.splice index, 1
|
312 | tomato.set 'updatedAt', now
|
313 | tomato.save (err) ->
|
314 | return res.send(err, 500) if err
|
315 | res.send 200
|
316 |
|
317 | return app
|
318 |
|
319 |
|
320 | unless module.parent
|
321 | exports.middleware().listen 4000
|
322 | console.log 'tomato server listening on http://localhost:4000'
|