punchcard 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +23 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +59 -0
- data/LICENSE +20 -0
- data/README.rdoc +62 -0
- data/Rakefile +61 -0
- data/VERSION +1 -0
- data/config.ru +11 -0
- data/lib/public/cardbg.png +0 -0
- data/lib/public/css/blueprint.css +265 -0
- data/lib/public/js/app.js +35 -0
- data/lib/punchcard.rb +29 -0
- data/lib/punchcard/mongohq.rb +10 -0
- data/lib/punchcard/person.rb +50 -0
- data/lib/punchcard/punch.rb +40 -0
- data/lib/views/index.haml +1 -0
- data/lib/views/layout.haml +28 -0
- data/lib/views/screen.sass +53 -0
- data/punchcard.gemspec +92 -0
- data/test/helper.rb +17 -0
- data/test/test_database.rb +120 -0
- data/test/test_punchcard.rb +4 -0
- metadata +215 -0
data/.document
ADDED
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
punchcard (0.0.0)
|
5
|
+
bson_ext
|
6
|
+
gravtastic
|
7
|
+
haml (>= 3.0.24)
|
8
|
+
mongoid
|
9
|
+
sass (>= 3.0.0)
|
10
|
+
sinatra (>= 1.0.0)
|
11
|
+
sinatra-mongoid
|
12
|
+
|
13
|
+
GEM
|
14
|
+
remote: http://rubygems.org/
|
15
|
+
specs:
|
16
|
+
activesupport (2.3.10)
|
17
|
+
bson (1.0.9)
|
18
|
+
bson_ext (1.1.4)
|
19
|
+
durran-validatable (2.0.1)
|
20
|
+
gravtastic (3.1.0)
|
21
|
+
haml (3.0.24)
|
22
|
+
mongo (1.0.9)
|
23
|
+
bson (>= 1.0.5)
|
24
|
+
mongo_ext (0.19.3)
|
25
|
+
mongoid (1.9.2)
|
26
|
+
activesupport (< 3.0.0)
|
27
|
+
bson (~> 1.0.1)
|
28
|
+
durran-validatable (>= 2.0.1)
|
29
|
+
mongo (~> 1.0.1)
|
30
|
+
will_paginate (< 2.9)
|
31
|
+
rack (1.2.1)
|
32
|
+
rack-test (0.5.6)
|
33
|
+
rack (>= 1.0)
|
34
|
+
sass (3.1.0.alpha.200)
|
35
|
+
shoulda (2.10.3)
|
36
|
+
sinatra (1.1.0)
|
37
|
+
rack (~> 1.1)
|
38
|
+
tilt (~> 1.1)
|
39
|
+
sinatra-mongoid (0.0.2)
|
40
|
+
mongo_ext
|
41
|
+
mongoid
|
42
|
+
sinatra
|
43
|
+
tilt (1.1)
|
44
|
+
will_paginate (2.3.15)
|
45
|
+
|
46
|
+
PLATFORMS
|
47
|
+
ruby
|
48
|
+
|
49
|
+
DEPENDENCIES
|
50
|
+
bson_ext
|
51
|
+
gravtastic
|
52
|
+
haml (>= 3.0.24)
|
53
|
+
mongoid
|
54
|
+
punchcard!
|
55
|
+
rack-test (>= 0.5.6)
|
56
|
+
sass (>= 3.0.0)
|
57
|
+
shoulda (= 2.10.3)
|
58
|
+
sinatra (>= 1.0.0)
|
59
|
+
sinatra-mongoid
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Christoph Olszowka
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
= Punchcard
|
2
|
+
|
3
|
+
A simple Sinatra app that lets you track who is and has been in the office at what times.
|
4
|
+
|
5
|
+
== Setup (theory)
|
6
|
+
|
7
|
+
Create a folder for your app, add a Gemfile:
|
8
|
+
|
9
|
+
source :rubygems
|
10
|
+
gem 'punchcard'
|
11
|
+
|
12
|
+
Run bundle install. You'll also need a config.ru that looks like this:
|
13
|
+
|
14
|
+
require 'bundler'
|
15
|
+
Bundler.require
|
16
|
+
Punchcard.set :mongo_db, 'punchcard'
|
17
|
+
run Punchcard
|
18
|
+
|
19
|
+
Now running rackup should launch punchcard.
|
20
|
+
|
21
|
+
== Setup (for real on Heroku)
|
22
|
+
|
23
|
+
Same as above, but you can ditch the mongo_db setup line. Initialize a git repo in your folder
|
24
|
+
|
25
|
+
$ git init
|
26
|
+
$ git commit -a -m "Initial commit"
|
27
|
+
$ heroku create --stack bamboo-mri-1.9.2
|
28
|
+
$ heroku addons:add mongohq:free
|
29
|
+
$ git push heroku
|
30
|
+
|
31
|
+
Now head over to your app's url! Please also check out the next two sections for auth and adding people.
|
32
|
+
|
33
|
+
== Authentication
|
34
|
+
|
35
|
+
You'll surely want to secure your punch card from general web access. The simplest way to do so is by
|
36
|
+
using the rack auth middleware by defining it somewhere in your config.ru:
|
37
|
+
|
38
|
+
Punchcard.use Rack::Auth::Basic do |username, password|
|
39
|
+
[username, password] == ['admin', 'admin']
|
40
|
+
end
|
41
|
+
|
42
|
+
== Adding people
|
43
|
+
|
44
|
+
Currently, you'll have to add users directly from the console. After creating your app on heroku,
|
45
|
+
you can do so by running
|
46
|
+
|
47
|
+
$ heroku console
|
48
|
+
> Person.create!(:name => 'Forrest Gump', :email => 'forrest@shrimpco.org')
|
49
|
+
|
50
|
+
== Note on Patches/Pull Requests
|
51
|
+
|
52
|
+
* Fork the project.
|
53
|
+
* Make your feature addition or bug fix.
|
54
|
+
* Add tests for it. This is important so I don't break it in a
|
55
|
+
future version unintentionally.
|
56
|
+
* Commit, do not mess with rakefile, version, or history.
|
57
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
58
|
+
* Send me a pull request. Bonus points for topic branches.
|
59
|
+
|
60
|
+
== Copyright
|
61
|
+
|
62
|
+
Copyright (c) 2010 Christoph Olszowka. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "punchcard"
|
8
|
+
gem.summary = %Q{Simple sinatra/mongodb app for tracking time when people have been in the office}
|
9
|
+
gem.description = %Q{Simple sinatra/mongodb app for tracking time when people have been in the office}
|
10
|
+
gem.email = "christoph at olszowka de"
|
11
|
+
gem.homepage = "http://github.com/colszowka/punchcard"
|
12
|
+
gem.authors = ["Christoph Olszowka"]
|
13
|
+
gem.add_dependency 'sinatra', ">= 1.0.0"
|
14
|
+
gem.add_dependency 'sinatra-mongoid', '>= 0'
|
15
|
+
gem.add_dependency 'bson_ext', '>= 0'
|
16
|
+
gem.add_dependency 'haml', '>= 3.0.24'
|
17
|
+
gem.add_dependency 'sass', '>= 3.0.0'
|
18
|
+
gem.add_dependency 'mongoid', '>= 0'
|
19
|
+
gem.add_dependency 'gravtastic', '>= 0'
|
20
|
+
gem.add_development_dependency "shoulda", "2.10.3"
|
21
|
+
gem.add_development_dependency "rack-test", ">= 0.5.6"
|
22
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
23
|
+
end
|
24
|
+
Jeweler::GemcutterTasks.new
|
25
|
+
rescue LoadError
|
26
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
27
|
+
end
|
28
|
+
|
29
|
+
require 'rake/testtask'
|
30
|
+
Rake::TestTask.new(:test) do |test|
|
31
|
+
test.libs << 'lib' << 'test'
|
32
|
+
test.pattern = 'test/**/test_*.rb'
|
33
|
+
test.verbose = true
|
34
|
+
end
|
35
|
+
|
36
|
+
begin
|
37
|
+
require 'rcov/rcovtask'
|
38
|
+
Rcov::RcovTask.new do |test|
|
39
|
+
test.libs << 'test'
|
40
|
+
test.pattern = 'test/**/test_*.rb'
|
41
|
+
test.verbose = true
|
42
|
+
end
|
43
|
+
rescue LoadError
|
44
|
+
task :rcov do
|
45
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
task :test => :check_dependencies
|
50
|
+
|
51
|
+
task :default => :test
|
52
|
+
|
53
|
+
require 'rake/rdoctask'
|
54
|
+
Rake::RDocTask.new do |rdoc|
|
55
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
56
|
+
|
57
|
+
rdoc.rdoc_dir = 'rdoc'
|
58
|
+
rdoc.title = "punchcard #{version}"
|
59
|
+
rdoc.rdoc_files.include('README*')
|
60
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
61
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/config.ru
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# Example config.ru for running from direct git clone
|
2
|
+
# See README to see how to launch from gem
|
3
|
+
require './lib/punchcard'
|
4
|
+
|
5
|
+
Punchcard.set :mongo_db, 'punchcard'
|
6
|
+
|
7
|
+
Punchcard.use Rack::Auth::Basic do |username, password|
|
8
|
+
[username, password] == ['admin', 'admin']
|
9
|
+
end
|
10
|
+
|
11
|
+
run Punchcard
|
Binary file
|
@@ -0,0 +1,265 @@
|
|
1
|
+
/* -----------------------------------------------------------------------
|
2
|
+
|
3
|
+
|
4
|
+
Blueprint CSS Framework 1.0
|
5
|
+
http://blueprintcss.org
|
6
|
+
|
7
|
+
* Copyright (c) 2007-Present. See LICENSE for more info.
|
8
|
+
* See README for instructions on how to use Blueprint.
|
9
|
+
* For credits and origins, see AUTHORS.
|
10
|
+
* This is a compressed file. See the sources in the 'src' directory.
|
11
|
+
|
12
|
+
----------------------------------------------------------------------- */
|
13
|
+
|
14
|
+
/* reset.css */
|
15
|
+
html {margin:0;padding:0;border:0;}
|
16
|
+
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;}
|
17
|
+
article, aside, dialog, figure, footer, header, hgroup, nav, section {display:block;}
|
18
|
+
body {line-height:1.5;background:white;}
|
19
|
+
table {border-collapse:separate;border-spacing:0;}
|
20
|
+
caption, th, td {text-align:left;font-weight:normal;float:none !important;}
|
21
|
+
table, th, td {vertical-align:middle;}
|
22
|
+
blockquote:before, blockquote:after, q:before, q:after {content:'';}
|
23
|
+
blockquote, q {quotes:"" "";}
|
24
|
+
a img {border:none;}
|
25
|
+
:focus {outline:0;}
|
26
|
+
|
27
|
+
/* typography.css */
|
28
|
+
html {font-size:100.01%;}
|
29
|
+
body {font-size:75%;color:#222;background:#fff;font-family:"Helvetica Neue", Arial, Helvetica, sans-serif;}
|
30
|
+
h1, h2, h3, h4, h5, h6 {font-weight:normal;color:#111;}
|
31
|
+
h1 {font-size:3em;line-height:1;margin-bottom:0.5em;}
|
32
|
+
h2 {font-size:2em;margin-bottom:0.75em;}
|
33
|
+
h3 {font-size:1.5em;line-height:1;margin-bottom:1em;}
|
34
|
+
h4 {font-size:1.2em;line-height:1.25;margin-bottom:1.25em;}
|
35
|
+
h5 {font-size:1em;font-weight:bold;margin-bottom:1.5em;}
|
36
|
+
h6 {font-size:1em;font-weight:bold;}
|
37
|
+
h1 img, h2 img, h3 img, h4 img, h5 img, h6 img {margin:0;}
|
38
|
+
p {margin:0 0 1.5em;}
|
39
|
+
.left {float:left !important;}
|
40
|
+
p .left {margin:1.5em 1.5em 1.5em 0;padding:0;}
|
41
|
+
.right {float:right !important;}
|
42
|
+
p .right {margin:1.5em 0 1.5em 1.5em;padding:0;}
|
43
|
+
a:focus, a:hover {color:#09f;}
|
44
|
+
a {color:#06c;text-decoration:underline;}
|
45
|
+
blockquote {margin:1.5em;color:#666;font-style:italic;}
|
46
|
+
strong, dfn {font-weight:bold;}
|
47
|
+
em, dfn {font-style:italic;}
|
48
|
+
sup, sub {line-height:0;}
|
49
|
+
abbr, acronym {border-bottom:1px dotted #666;}
|
50
|
+
address {margin:0 0 1.5em;font-style:italic;}
|
51
|
+
del {color:#666;}
|
52
|
+
pre {margin:1.5em 0;white-space:pre;}
|
53
|
+
pre, code, tt {font:1em 'andale mono', 'lucida console', monospace;line-height:1.5;}
|
54
|
+
li ul, li ol {margin:0;}
|
55
|
+
ul, ol {margin:0 1.5em 1.5em 0;padding-left:1.5em;}
|
56
|
+
ul {list-style-type:disc;}
|
57
|
+
ol {list-style-type:decimal;}
|
58
|
+
dl {margin:0 0 1.5em 0;}
|
59
|
+
dl dt {font-weight:bold;}
|
60
|
+
dd {margin-left:1.5em;}
|
61
|
+
table {margin-bottom:1.4em;width:100%;}
|
62
|
+
th {font-weight:bold;}
|
63
|
+
thead th {background:#c3d9ff;}
|
64
|
+
th, td, caption {padding:4px 10px 4px 5px;}
|
65
|
+
tbody tr:nth-child(even) td, tbody tr.even td {background:#e5ecf9;}
|
66
|
+
tfoot {font-style:italic;}
|
67
|
+
caption {background:#eee;}
|
68
|
+
.small {font-size:.8em;margin-bottom:1.875em;line-height:1.875em;}
|
69
|
+
.large {font-size:1.2em;line-height:2.5em;margin-bottom:1.25em;}
|
70
|
+
.hide {display:none;}
|
71
|
+
.quiet {color:#666;}
|
72
|
+
.loud {color:#000;}
|
73
|
+
.highlight {background:#ff0;}
|
74
|
+
.added {background:#060;color:#fff;}
|
75
|
+
.removed {background:#900;color:#fff;}
|
76
|
+
.first {margin-left:0;padding-left:0;}
|
77
|
+
.last {margin-right:0;padding-right:0;}
|
78
|
+
.top {margin-top:0;padding-top:0;}
|
79
|
+
.bottom {margin-bottom:0;padding-bottom:0;}
|
80
|
+
|
81
|
+
/* forms.css */
|
82
|
+
label {font-weight:bold;}
|
83
|
+
fieldset {padding:0 1.4em 1.4em 1.4em;margin:0 0 1.5em 0;border:1px solid #ccc;}
|
84
|
+
legend {font-weight:bold;font-size:1.2em;margin-top:-0.2em;margin-bottom:1em;}
|
85
|
+
fieldset, #IE8#HACK {padding-top:1.4em;}
|
86
|
+
legend, #IE8#HACK {margin-top:0;margin-bottom:0;}
|
87
|
+
input[type=text], input[type=password], input.text, input.title, textarea {background-color:#fff;border:1px solid #bbb;}
|
88
|
+
input[type=text]:focus, input[type=password]:focus, input.text:focus, input.title:focus, textarea:focus {border-color:#666;}
|
89
|
+
select {background-color:#fff;border-width:1px;border-style:solid;}
|
90
|
+
input[type=text], input[type=password], input.text, input.title, textarea, select {margin:0.5em 0;}
|
91
|
+
input.text, input.title {width:300px;padding:5px;}
|
92
|
+
input.title {font-size:1.5em;}
|
93
|
+
textarea {width:390px;height:250px;padding:5px;}
|
94
|
+
form.inline {line-height:3;}
|
95
|
+
form.inline p {margin-bottom:0;}
|
96
|
+
.error, .alert, .notice, .success, .info {padding:0.8em;margin-bottom:1em;border:2px solid #ddd;}
|
97
|
+
.error, .alert {background:#fbe3e4;color:#8a1f11;border-color:#fbc2c4;}
|
98
|
+
.notice {background:#fff6bf;color:#514721;border-color:#ffd324;}
|
99
|
+
.success {background:#e6efc2;color:#264409;border-color:#c6d880;}
|
100
|
+
.info {background:#d5edf8;color:#205791;border-color:#92cae4;}
|
101
|
+
.error a, .alert a {color:#8a1f11;}
|
102
|
+
.notice a {color:#514721;}
|
103
|
+
.success a {color:#264409;}
|
104
|
+
.info a {color:#205791;}
|
105
|
+
|
106
|
+
/* grid.css */
|
107
|
+
.container {width:950px;margin:0 auto;}
|
108
|
+
.showgrid {background:url(src/grid.png);}
|
109
|
+
.column, .span-1, .span-2, .span-3, .span-4, .span-5, .span-6, .span-7, .span-8, .span-9, .span-10, .span-11, .span-12, .span-13, .span-14, .span-15, .span-16, .span-17, .span-18, .span-19, .span-20, .span-21, .span-22, .span-23, .span-24 {float:left;margin-right:10px;}
|
110
|
+
.last {margin-right:0;}
|
111
|
+
.span-1 {width:30px;}
|
112
|
+
.span-2 {width:70px;}
|
113
|
+
.span-3 {width:110px;}
|
114
|
+
.span-4 {width:150px;}
|
115
|
+
.span-5 {width:190px;}
|
116
|
+
.span-6 {width:230px;}
|
117
|
+
.span-7 {width:270px;}
|
118
|
+
.span-8 {width:310px;}
|
119
|
+
.span-9 {width:350px;}
|
120
|
+
.span-10 {width:390px;}
|
121
|
+
.span-11 {width:430px;}
|
122
|
+
.span-12 {width:470px;}
|
123
|
+
.span-13 {width:510px;}
|
124
|
+
.span-14 {width:550px;}
|
125
|
+
.span-15 {width:590px;}
|
126
|
+
.span-16 {width:630px;}
|
127
|
+
.span-17 {width:670px;}
|
128
|
+
.span-18 {width:710px;}
|
129
|
+
.span-19 {width:750px;}
|
130
|
+
.span-20 {width:790px;}
|
131
|
+
.span-21 {width:830px;}
|
132
|
+
.span-22 {width:870px;}
|
133
|
+
.span-23 {width:910px;}
|
134
|
+
.span-24 {width:950px;margin-right:0;}
|
135
|
+
input.span-1, textarea.span-1, input.span-2, textarea.span-2, input.span-3, textarea.span-3, input.span-4, textarea.span-4, input.span-5, textarea.span-5, input.span-6, textarea.span-6, input.span-7, textarea.span-7, input.span-8, textarea.span-8, input.span-9, textarea.span-9, input.span-10, textarea.span-10, input.span-11, textarea.span-11, input.span-12, textarea.span-12, input.span-13, textarea.span-13, input.span-14, textarea.span-14, input.span-15, textarea.span-15, input.span-16, textarea.span-16, input.span-17, textarea.span-17, input.span-18, textarea.span-18, input.span-19, textarea.span-19, input.span-20, textarea.span-20, input.span-21, textarea.span-21, input.span-22, textarea.span-22, input.span-23, textarea.span-23, input.span-24, textarea.span-24 {border-left-width:1px;border-right-width:1px;padding-left:5px;padding-right:5px;}
|
136
|
+
input.span-1, textarea.span-1 {width:18px;}
|
137
|
+
input.span-2, textarea.span-2 {width:58px;}
|
138
|
+
input.span-3, textarea.span-3 {width:98px;}
|
139
|
+
input.span-4, textarea.span-4 {width:138px;}
|
140
|
+
input.span-5, textarea.span-5 {width:178px;}
|
141
|
+
input.span-6, textarea.span-6 {width:218px;}
|
142
|
+
input.span-7, textarea.span-7 {width:258px;}
|
143
|
+
input.span-8, textarea.span-8 {width:298px;}
|
144
|
+
input.span-9, textarea.span-9 {width:338px;}
|
145
|
+
input.span-10, textarea.span-10 {width:378px;}
|
146
|
+
input.span-11, textarea.span-11 {width:418px;}
|
147
|
+
input.span-12, textarea.span-12 {width:458px;}
|
148
|
+
input.span-13, textarea.span-13 {width:498px;}
|
149
|
+
input.span-14, textarea.span-14 {width:538px;}
|
150
|
+
input.span-15, textarea.span-15 {width:578px;}
|
151
|
+
input.span-16, textarea.span-16 {width:618px;}
|
152
|
+
input.span-17, textarea.span-17 {width:658px;}
|
153
|
+
input.span-18, textarea.span-18 {width:698px;}
|
154
|
+
input.span-19, textarea.span-19 {width:738px;}
|
155
|
+
input.span-20, textarea.span-20 {width:778px;}
|
156
|
+
input.span-21, textarea.span-21 {width:818px;}
|
157
|
+
input.span-22, textarea.span-22 {width:858px;}
|
158
|
+
input.span-23, textarea.span-23 {width:898px;}
|
159
|
+
input.span-24, textarea.span-24 {width:938px;}
|
160
|
+
.append-1 {padding-right:40px;}
|
161
|
+
.append-2 {padding-right:80px;}
|
162
|
+
.append-3 {padding-right:120px;}
|
163
|
+
.append-4 {padding-right:160px;}
|
164
|
+
.append-5 {padding-right:200px;}
|
165
|
+
.append-6 {padding-right:240px;}
|
166
|
+
.append-7 {padding-right:280px;}
|
167
|
+
.append-8 {padding-right:320px;}
|
168
|
+
.append-9 {padding-right:360px;}
|
169
|
+
.append-10 {padding-right:400px;}
|
170
|
+
.append-11 {padding-right:440px;}
|
171
|
+
.append-12 {padding-right:480px;}
|
172
|
+
.append-13 {padding-right:520px;}
|
173
|
+
.append-14 {padding-right:560px;}
|
174
|
+
.append-15 {padding-right:600px;}
|
175
|
+
.append-16 {padding-right:640px;}
|
176
|
+
.append-17 {padding-right:680px;}
|
177
|
+
.append-18 {padding-right:720px;}
|
178
|
+
.append-19 {padding-right:760px;}
|
179
|
+
.append-20 {padding-right:800px;}
|
180
|
+
.append-21 {padding-right:840px;}
|
181
|
+
.append-22 {padding-right:880px;}
|
182
|
+
.append-23 {padding-right:920px;}
|
183
|
+
.prepend-1 {padding-left:40px;}
|
184
|
+
.prepend-2 {padding-left:80px;}
|
185
|
+
.prepend-3 {padding-left:120px;}
|
186
|
+
.prepend-4 {padding-left:160px;}
|
187
|
+
.prepend-5 {padding-left:200px;}
|
188
|
+
.prepend-6 {padding-left:240px;}
|
189
|
+
.prepend-7 {padding-left:280px;}
|
190
|
+
.prepend-8 {padding-left:320px;}
|
191
|
+
.prepend-9 {padding-left:360px;}
|
192
|
+
.prepend-10 {padding-left:400px;}
|
193
|
+
.prepend-11 {padding-left:440px;}
|
194
|
+
.prepend-12 {padding-left:480px;}
|
195
|
+
.prepend-13 {padding-left:520px;}
|
196
|
+
.prepend-14 {padding-left:560px;}
|
197
|
+
.prepend-15 {padding-left:600px;}
|
198
|
+
.prepend-16 {padding-left:640px;}
|
199
|
+
.prepend-17 {padding-left:680px;}
|
200
|
+
.prepend-18 {padding-left:720px;}
|
201
|
+
.prepend-19 {padding-left:760px;}
|
202
|
+
.prepend-20 {padding-left:800px;}
|
203
|
+
.prepend-21 {padding-left:840px;}
|
204
|
+
.prepend-22 {padding-left:880px;}
|
205
|
+
.prepend-23 {padding-left:920px;}
|
206
|
+
.border {padding-right:4px;margin-right:5px;border-right:1px solid #ddd;}
|
207
|
+
.colborder {padding-right:24px;margin-right:25px;border-right:1px solid #ddd;}
|
208
|
+
.pull-1 {margin-left:-40px;}
|
209
|
+
.pull-2 {margin-left:-80px;}
|
210
|
+
.pull-3 {margin-left:-120px;}
|
211
|
+
.pull-4 {margin-left:-160px;}
|
212
|
+
.pull-5 {margin-left:-200px;}
|
213
|
+
.pull-6 {margin-left:-240px;}
|
214
|
+
.pull-7 {margin-left:-280px;}
|
215
|
+
.pull-8 {margin-left:-320px;}
|
216
|
+
.pull-9 {margin-left:-360px;}
|
217
|
+
.pull-10 {margin-left:-400px;}
|
218
|
+
.pull-11 {margin-left:-440px;}
|
219
|
+
.pull-12 {margin-left:-480px;}
|
220
|
+
.pull-13 {margin-left:-520px;}
|
221
|
+
.pull-14 {margin-left:-560px;}
|
222
|
+
.pull-15 {margin-left:-600px;}
|
223
|
+
.pull-16 {margin-left:-640px;}
|
224
|
+
.pull-17 {margin-left:-680px;}
|
225
|
+
.pull-18 {margin-left:-720px;}
|
226
|
+
.pull-19 {margin-left:-760px;}
|
227
|
+
.pull-20 {margin-left:-800px;}
|
228
|
+
.pull-21 {margin-left:-840px;}
|
229
|
+
.pull-22 {margin-left:-880px;}
|
230
|
+
.pull-23 {margin-left:-920px;}
|
231
|
+
.pull-24 {margin-left:-960px;}
|
232
|
+
.pull-1, .pull-2, .pull-3, .pull-4, .pull-5, .pull-6, .pull-7, .pull-8, .pull-9, .pull-10, .pull-11, .pull-12, .pull-13, .pull-14, .pull-15, .pull-16, .pull-17, .pull-18, .pull-19, .pull-20, .pull-21, .pull-22, .pull-23, .pull-24 {float:left;position:relative;}
|
233
|
+
.push-1 {margin:0 -40px 1.5em 40px;}
|
234
|
+
.push-2 {margin:0 -80px 1.5em 80px;}
|
235
|
+
.push-3 {margin:0 -120px 1.5em 120px;}
|
236
|
+
.push-4 {margin:0 -160px 1.5em 160px;}
|
237
|
+
.push-5 {margin:0 -200px 1.5em 200px;}
|
238
|
+
.push-6 {margin:0 -240px 1.5em 240px;}
|
239
|
+
.push-7 {margin:0 -280px 1.5em 280px;}
|
240
|
+
.push-8 {margin:0 -320px 1.5em 320px;}
|
241
|
+
.push-9 {margin:0 -360px 1.5em 360px;}
|
242
|
+
.push-10 {margin:0 -400px 1.5em 400px;}
|
243
|
+
.push-11 {margin:0 -440px 1.5em 440px;}
|
244
|
+
.push-12 {margin:0 -480px 1.5em 480px;}
|
245
|
+
.push-13 {margin:0 -520px 1.5em 520px;}
|
246
|
+
.push-14 {margin:0 -560px 1.5em 560px;}
|
247
|
+
.push-15 {margin:0 -600px 1.5em 600px;}
|
248
|
+
.push-16 {margin:0 -640px 1.5em 640px;}
|
249
|
+
.push-17 {margin:0 -680px 1.5em 680px;}
|
250
|
+
.push-18 {margin:0 -720px 1.5em 720px;}
|
251
|
+
.push-19 {margin:0 -760px 1.5em 760px;}
|
252
|
+
.push-20 {margin:0 -800px 1.5em 800px;}
|
253
|
+
.push-21 {margin:0 -840px 1.5em 840px;}
|
254
|
+
.push-22 {margin:0 -880px 1.5em 880px;}
|
255
|
+
.push-23 {margin:0 -920px 1.5em 920px;}
|
256
|
+
.push-24 {margin:0 -960px 1.5em 960px;}
|
257
|
+
.push-1, .push-2, .push-3, .push-4, .push-5, .push-6, .push-7, .push-8, .push-9, .push-10, .push-11, .push-12, .push-13, .push-14, .push-15, .push-16, .push-17, .push-18, .push-19, .push-20, .push-21, .push-22, .push-23, .push-24 {float:left;position:relative;}
|
258
|
+
div.prepend-top, .prepend-top {margin-top:1.5em;}
|
259
|
+
div.append-bottom, .append-bottom {margin-bottom:1.5em;}
|
260
|
+
.box {padding:1.5em;margin-bottom:1.5em;background:#e5eCf9;}
|
261
|
+
hr {background:#ddd;color:#ddd;clear:both;float:none;width:100%;height:1px;margin:0 0 1.45em;border:none;}
|
262
|
+
hr.space {background:#fff;color:#fff;visibility:hidden;}
|
263
|
+
.clearfix:after, .container:after {content:"\0020";display:block;height:0;clear:both;visibility:hidden;overflow:hidden;}
|
264
|
+
.clearfix, .container {display:block;}
|
265
|
+
.clear {clear:both;}
|
@@ -0,0 +1,35 @@
|
|
1
|
+
// Update status for all people
|
2
|
+
function update_status() {
|
3
|
+
$.getJSON('/status.json', function(json) {
|
4
|
+
$.each(json, function(index, person) {
|
5
|
+
update_person(person);
|
6
|
+
});
|
7
|
+
});
|
8
|
+
};
|
9
|
+
|
10
|
+
// Render the state of the person from given json
|
11
|
+
function update_person(json) {
|
12
|
+
var rendered_template = $.tmpl('personTemplate', json);
|
13
|
+
|
14
|
+
if ($('#people #' + json._id).size() == 0) {
|
15
|
+
rendered_template.appendTo('#people');
|
16
|
+
} else {
|
17
|
+
$('#people #' + json._id).replaceWith(rendered_template);
|
18
|
+
};
|
19
|
+
};
|
20
|
+
|
21
|
+
$(document).ready(function() {
|
22
|
+
$( "#personTemplate" ).template( "personTemplate" );
|
23
|
+
|
24
|
+
$('#people li').live('dblclick', function() {
|
25
|
+
user_id = $(this).attr('id');
|
26
|
+
$.post('/punch/' + user_id, function(data) {
|
27
|
+
//$('.result').html(data);
|
28
|
+
var json = jQuery.parseJSON(data);
|
29
|
+
update_person(json);
|
30
|
+
});
|
31
|
+
});
|
32
|
+
|
33
|
+
update_status();
|
34
|
+
var timer = setInterval( update_status, 5000);
|
35
|
+
});
|
data/lib/punchcard.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler.require(:default)
|
3
|
+
|
4
|
+
require 'punchcard/person'
|
5
|
+
require 'punchcard/punch'
|
6
|
+
require 'punchcard/mongohq'
|
7
|
+
|
8
|
+
class Punchcard < Sinatra::Base
|
9
|
+
set :views, File.join(File.dirname(__FILE__), 'views')
|
10
|
+
set :public, File.join(File.dirname(__FILE__), 'public')
|
11
|
+
|
12
|
+
get '/' do
|
13
|
+
haml :index
|
14
|
+
end
|
15
|
+
|
16
|
+
get '/status.json' do
|
17
|
+
Person.order_by(:name).all.to_json
|
18
|
+
end
|
19
|
+
|
20
|
+
post '/punch/:id' do
|
21
|
+
@person = Person.find(params[:id])
|
22
|
+
@person.punch!
|
23
|
+
@person.to_json
|
24
|
+
end
|
25
|
+
|
26
|
+
get '/css/screen.css' do
|
27
|
+
sass :screen
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# Automagic Mongo db autosetup for heroku & mongohq
|
2
|
+
if ENV['MONGOHQ_URL']
|
3
|
+
require 'uri'
|
4
|
+
mongo_uri = URI.parse(ENV['MONGOHQ_URL'])
|
5
|
+
ENV['MONGOID_HOST'] = mongo_uri.host
|
6
|
+
ENV['MONGOID_PORT'] = mongo_uri.port.to_s
|
7
|
+
ENV['MONGOID_USERNAME'] = mongo_uri.user
|
8
|
+
ENV['MONGOID_PASSWORD'] = mongo_uri.password
|
9
|
+
ENV['MONGOID_DATABASE'] = mongo_uri.path.gsub("/", "")
|
10
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
class Person
|
2
|
+
include Mongoid::Document
|
3
|
+
|
4
|
+
field :name
|
5
|
+
field :email
|
6
|
+
|
7
|
+
index :name, :unique => true
|
8
|
+
|
9
|
+
embeds_many :punches
|
10
|
+
|
11
|
+
validates_presence_of :name, :email
|
12
|
+
|
13
|
+
include Gravtastic
|
14
|
+
has_gravatar
|
15
|
+
|
16
|
+
# Returns the currently pending punch when present, nil otherwise
|
17
|
+
def pending?
|
18
|
+
punches.pending.first
|
19
|
+
end
|
20
|
+
|
21
|
+
# Punches in when no punches pending, punches out when a pending punch exists!
|
22
|
+
# When punching in, checks whether a recently finished punch exists and reopens if so instead
|
23
|
+
# of creating a new punch
|
24
|
+
def punch!
|
25
|
+
if punch = punches.pending.first
|
26
|
+
punch.punch_out!
|
27
|
+
else
|
28
|
+
if recently_finished = punches.recently_finished.first
|
29
|
+
recently_finished.reopen!
|
30
|
+
else
|
31
|
+
punches.create!
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def payload
|
37
|
+
{
|
38
|
+
:_id => id,
|
39
|
+
:name => name,
|
40
|
+
:email => email,
|
41
|
+
:gravatar_url => gravatar_url(:size => 80),
|
42
|
+
:pending => !!pending?,
|
43
|
+
:checked_in_at => pending?.try(:checked_in_at)
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_json(*args)
|
48
|
+
payload.to_json
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class Punch
|
2
|
+
include Mongoid::Document
|
3
|
+
field :checked_in_at, :type => Time
|
4
|
+
field :checked_out_at, :type => Time
|
5
|
+
|
6
|
+
embedded_in :person, :inverse_of => :punches
|
7
|
+
|
8
|
+
scope :pending, where(:checked_out_at.exists => false)
|
9
|
+
scope :finished, where(:checked_out_at.exists => true, :checked_in_at.exists => true)
|
10
|
+
scope :recently_finished, where(:checked_out_at.gt => 30.minutes.ago)
|
11
|
+
|
12
|
+
validates_presence_of :checked_in_at
|
13
|
+
|
14
|
+
before_validation do |p|
|
15
|
+
p.checked_in_at ||= Time.now
|
16
|
+
end
|
17
|
+
|
18
|
+
validate do |p|
|
19
|
+
p.errors.add :person, "Person already has a pending punch!" if p.person.pending? and p.person.pending? != p
|
20
|
+
end
|
21
|
+
|
22
|
+
# Punches this punch out when pending
|
23
|
+
def punch_out!
|
24
|
+
return false if checked_out_at.present?
|
25
|
+
# Make sure no one stays the night...
|
26
|
+
if self.checked_in_at.to_date < Date.today
|
27
|
+
self.checked_out_at = self.checked_in_at.end_of_day
|
28
|
+
else
|
29
|
+
self.checked_out_at = Time.now
|
30
|
+
end
|
31
|
+
save!
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
def reopen!
|
36
|
+
self.checked_out_at = nil
|
37
|
+
save!
|
38
|
+
self
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
%ul#people
|
@@ -0,0 +1,28 @@
|
|
1
|
+
!!!
|
2
|
+
%html
|
3
|
+
%head
|
4
|
+
%title Punchcard
|
5
|
+
%script{:src => "http://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js"}
|
6
|
+
%script{:src => 'http://ajax.microsoft.com/ajax/jquery.templates/beta1/jquery.tmpl.min.js'}
|
7
|
+
%script{:src => "/js/app.js"}
|
8
|
+
:plain
|
9
|
+
<link rel="stylesheet" href="/css/blueprint.css" type="text/css" media="screen" />
|
10
|
+
<link rel="stylesheet" href="/css/screen.css" type="text/css" media="screen" />
|
11
|
+
|
12
|
+
:plain
|
13
|
+
<script id="personTemplate" type="text/x-jquery-tmpl">
|
14
|
+
<li id="${_id}">
|
15
|
+
<img src="${gravatar_url}" title="${name}" class="gravatar {{if pending == true}}pending{{/if}} ${pending}"/>
|
16
|
+
<div class="person">${name}</div>
|
17
|
+
<div class="checked_in_at">{{if checked_in_at}}Checked in at ${checked_in_at}{{/if}} </div>
|
18
|
+
</li>
|
19
|
+
</script>
|
20
|
+
|
21
|
+
%body
|
22
|
+
#header
|
23
|
+
|
24
|
+
#main
|
25
|
+
= yield
|
26
|
+
.clear
|
27
|
+
|
28
|
+
#footer
|
@@ -0,0 +1,53 @@
|
|
1
|
+
body
|
2
|
+
font-family: "Lucida Grande", Helvetica, Arial, Verdana, sans-serif
|
3
|
+
background: #efefef
|
4
|
+
|
5
|
+
$green: #090
|
6
|
+
$yellow: #fc0
|
7
|
+
|
8
|
+
#main
|
9
|
+
width: 800px
|
10
|
+
margin-top: 30px
|
11
|
+
padding: 30px 20px
|
12
|
+
margin-left: auto
|
13
|
+
margin-right: auto
|
14
|
+
background: #eadab6 url('/cardbg.png') repeat-x
|
15
|
+
-moz-box-shadow: 3px 3px 4px #d2bd8e
|
16
|
+
|
17
|
+
.clear
|
18
|
+
clear: both
|
19
|
+
|
20
|
+
#people
|
21
|
+
list-style: none
|
22
|
+
margin: 0
|
23
|
+
padding: 0
|
24
|
+
|
25
|
+
li
|
26
|
+
clear: both
|
27
|
+
cursor: pointer
|
28
|
+
margin-bottom: 20px
|
29
|
+
font-weight: bold
|
30
|
+
font-size: 50px
|
31
|
+
|
32
|
+
&:last-child
|
33
|
+
margin-bottom: 0
|
34
|
+
.gravatar
|
35
|
+
margin-bottom: 0
|
36
|
+
|
37
|
+
.checked_in_at
|
38
|
+
color: #888
|
39
|
+
font-size: 16px
|
40
|
+
position: relative
|
41
|
+
top: -10px
|
42
|
+
|
43
|
+
.gravatar
|
44
|
+
float: left
|
45
|
+
width: 80px
|
46
|
+
margin-bottom: 20px
|
47
|
+
margin-right: 25px
|
48
|
+
border: 3px solid $yellow
|
49
|
+
-moz-box-shadow: 0px 0px 10px $yellow
|
50
|
+
&.pending
|
51
|
+
border-color: $green
|
52
|
+
-moz-box-shadow: 0px 0px 15px $green
|
53
|
+
|
data/punchcard.gemspec
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{punchcard}
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Christoph Olszowka"]
|
12
|
+
s.date = %q{2010-12-08}
|
13
|
+
s.description = %q{Simple sinatra/mongodb app for tracking time when people have been in the office}
|
14
|
+
s.email = %q{christoph at olszowka de}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".gitignore",
|
22
|
+
"Gemfile",
|
23
|
+
"Gemfile.lock",
|
24
|
+
"LICENSE",
|
25
|
+
"README.rdoc",
|
26
|
+
"Rakefile",
|
27
|
+
"VERSION",
|
28
|
+
"config.ru",
|
29
|
+
"lib/public/cardbg.png",
|
30
|
+
"lib/public/css/blueprint.css",
|
31
|
+
"lib/public/js/app.js",
|
32
|
+
"lib/punchcard.rb",
|
33
|
+
"lib/punchcard/mongohq.rb",
|
34
|
+
"lib/punchcard/person.rb",
|
35
|
+
"lib/punchcard/punch.rb",
|
36
|
+
"lib/views/index.haml",
|
37
|
+
"lib/views/layout.haml",
|
38
|
+
"lib/views/screen.sass",
|
39
|
+
"punchcard.gemspec",
|
40
|
+
"test/helper.rb",
|
41
|
+
"test/test_database.rb",
|
42
|
+
"test/test_punchcard.rb"
|
43
|
+
]
|
44
|
+
s.homepage = %q{http://github.com/colszowka/punchcard}
|
45
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
46
|
+
s.require_paths = ["lib"]
|
47
|
+
s.rubygems_version = %q{1.3.7}
|
48
|
+
s.summary = %q{Simple sinatra/mongodb app for tracking time when people have been in the office}
|
49
|
+
s.test_files = [
|
50
|
+
"test/helper.rb",
|
51
|
+
"test/test_database.rb",
|
52
|
+
"test/test_punchcard.rb"
|
53
|
+
]
|
54
|
+
|
55
|
+
if s.respond_to? :specification_version then
|
56
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
57
|
+
s.specification_version = 3
|
58
|
+
|
59
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
60
|
+
s.add_runtime_dependency(%q<sinatra>, [">= 1.0.0"])
|
61
|
+
s.add_runtime_dependency(%q<sinatra-mongoid>, [">= 0"])
|
62
|
+
s.add_runtime_dependency(%q<bson_ext>, [">= 0"])
|
63
|
+
s.add_runtime_dependency(%q<haml>, [">= 3.0.24"])
|
64
|
+
s.add_runtime_dependency(%q<sass>, [">= 3.0.0"])
|
65
|
+
s.add_runtime_dependency(%q<mongoid>, [">= 0"])
|
66
|
+
s.add_runtime_dependency(%q<gravtastic>, [">= 0"])
|
67
|
+
s.add_development_dependency(%q<shoulda>, ["= 2.10.3"])
|
68
|
+
s.add_development_dependency(%q<rack-test>, [">= 0.5.6"])
|
69
|
+
else
|
70
|
+
s.add_dependency(%q<sinatra>, [">= 1.0.0"])
|
71
|
+
s.add_dependency(%q<sinatra-mongoid>, [">= 0"])
|
72
|
+
s.add_dependency(%q<bson_ext>, [">= 0"])
|
73
|
+
s.add_dependency(%q<haml>, [">= 3.0.24"])
|
74
|
+
s.add_dependency(%q<sass>, [">= 3.0.0"])
|
75
|
+
s.add_dependency(%q<mongoid>, [">= 0"])
|
76
|
+
s.add_dependency(%q<gravtastic>, [">= 0"])
|
77
|
+
s.add_dependency(%q<shoulda>, ["= 2.10.3"])
|
78
|
+
s.add_dependency(%q<rack-test>, [">= 0.5.6"])
|
79
|
+
end
|
80
|
+
else
|
81
|
+
s.add_dependency(%q<sinatra>, [">= 1.0.0"])
|
82
|
+
s.add_dependency(%q<sinatra-mongoid>, [">= 0"])
|
83
|
+
s.add_dependency(%q<bson_ext>, [">= 0"])
|
84
|
+
s.add_dependency(%q<haml>, [">= 3.0.24"])
|
85
|
+
s.add_dependency(%q<sass>, [">= 3.0.0"])
|
86
|
+
s.add_dependency(%q<mongoid>, [">= 0"])
|
87
|
+
s.add_dependency(%q<gravtastic>, [">= 0"])
|
88
|
+
s.add_dependency(%q<shoulda>, ["= 2.10.3"])
|
89
|
+
s.add_dependency(%q<rack-test>, [">= 0.5.6"])
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
data/test/helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
Bundler.require
|
4
|
+
require 'test/unit'
|
5
|
+
require 'shoulda'
|
6
|
+
|
7
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
8
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
9
|
+
require 'punchcard'
|
10
|
+
|
11
|
+
Punchcard.set :mongo_db, 'punchcard_test'
|
12
|
+
|
13
|
+
class Test::Unit::TestCase
|
14
|
+
def setup
|
15
|
+
Person.delete_all
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestDatabase < Test::Unit::TestCase
|
4
|
+
context "A vanilla person" do
|
5
|
+
setup { @person = Person.new }
|
6
|
+
should "not be valid" do
|
7
|
+
assert !@person.valid?
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
context "A newly created person" do
|
12
|
+
setup do
|
13
|
+
@person = Person.create(:name => 'Christoph Olszowka', :email => 'foo@example.com')
|
14
|
+
end
|
15
|
+
|
16
|
+
should "have no punches" do
|
17
|
+
assert_equal 0, @person.punches.count
|
18
|
+
end
|
19
|
+
|
20
|
+
should "not be pending" do
|
21
|
+
assert_nil @person.pending?
|
22
|
+
end
|
23
|
+
|
24
|
+
context "that gets punched" do
|
25
|
+
setup do
|
26
|
+
assert @person.punch!.instance_of?(Punch), "Should have been able to get punched and have returned a Punch"
|
27
|
+
end
|
28
|
+
|
29
|
+
should "have one pending punch" do
|
30
|
+
assert_equal Punch, @person.pending?.class
|
31
|
+
assert_equal 1, @person.punches.pending.length
|
32
|
+
assert_equal 0, @person.punches.finished.length
|
33
|
+
end
|
34
|
+
|
35
|
+
should "not allow to create another pending punch" do
|
36
|
+
assert_raise Mongoid::Errors::Validations do
|
37
|
+
@person.punches.create!
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context "and gets punched again" do
|
42
|
+
setup do
|
43
|
+
assert @person.punch!.instance_of?(Punch), "Should have been able to get punched and have returned a Punch"
|
44
|
+
end
|
45
|
+
|
46
|
+
should "have finished the pending punch" do
|
47
|
+
assert @person.punches.first.checked_out_at > 5.seconds.ago
|
48
|
+
end
|
49
|
+
|
50
|
+
should "not be pending" do
|
51
|
+
assert_nil @person.pending?
|
52
|
+
end
|
53
|
+
|
54
|
+
should "have one punch and no pending punches" do
|
55
|
+
assert_equal 1, @person.punches.count
|
56
|
+
assert_equal 0, @person.punches.pending.length
|
57
|
+
assert_equal 1, @person.punches.finished.length
|
58
|
+
end
|
59
|
+
|
60
|
+
context "and gets punched yet again" do
|
61
|
+
setup do
|
62
|
+
assert @person.punch!.instance_of?(Punch), "Should have been able to get punched and have returned a Punch"
|
63
|
+
end
|
64
|
+
|
65
|
+
should "be pending" do
|
66
|
+
assert @person.pending?
|
67
|
+
end
|
68
|
+
|
69
|
+
should "have 1 punch by having reopened the old one" do
|
70
|
+
assert_equal 1, @person.punches.count
|
71
|
+
assert_equal 1, @person.punches.pending.length
|
72
|
+
assert_equal 0, @person.punches.finished.length
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context "and gets punched yet again 2 hours later" do
|
77
|
+
setup do
|
78
|
+
# Fake the timestamp so reopening does not get triggered
|
79
|
+
punch = @person.punches.first
|
80
|
+
punch.checked_out_at = 2.hours.ago
|
81
|
+
punch.save!
|
82
|
+
assert @person.punch!.instance_of?(Punch), "Should have been able to get punched and have returned a Punch"
|
83
|
+
end
|
84
|
+
|
85
|
+
should "be pending" do
|
86
|
+
assert @person.pending?
|
87
|
+
end
|
88
|
+
|
89
|
+
should "have 2 punches and one pending punch" do
|
90
|
+
assert_equal 2, @person.punches.count
|
91
|
+
assert_equal 1, @person.punches.pending.length
|
92
|
+
assert_equal 1, @person.punches.finished.length
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
context "that punched in yesterday" do
|
99
|
+
setup do
|
100
|
+
@person.punch!
|
101
|
+
punch = @person.pending?
|
102
|
+
punch.checked_in_at = 1.day.ago
|
103
|
+
punch.save!
|
104
|
+
end
|
105
|
+
|
106
|
+
context "and punches out today" do
|
107
|
+
setup do
|
108
|
+
@person.punch!
|
109
|
+
end
|
110
|
+
|
111
|
+
should "result in the punch out getting moved to the end of yesterday" do
|
112
|
+
checkout_time = @person.punches.finished.first.checked_out_at
|
113
|
+
assert_equal 1.day.ago.end_of_day.to_date, checkout_time.to_date
|
114
|
+
assert_equal 23, checkout_time.hour
|
115
|
+
assert_equal 59, checkout_time.min
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
metadata
ADDED
@@ -0,0 +1,215 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: punchcard
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Christoph Olszowka
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-12-08 00:00:00 +01:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: sinatra
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 1
|
30
|
+
- 0
|
31
|
+
- 0
|
32
|
+
version: 1.0.0
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: sinatra-mongoid
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
version: "0"
|
46
|
+
type: :runtime
|
47
|
+
version_requirements: *id002
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: bson_ext
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
segments:
|
57
|
+
- 0
|
58
|
+
version: "0"
|
59
|
+
type: :runtime
|
60
|
+
version_requirements: *id003
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: haml
|
63
|
+
prerelease: false
|
64
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
segments:
|
70
|
+
- 3
|
71
|
+
- 0
|
72
|
+
- 24
|
73
|
+
version: 3.0.24
|
74
|
+
type: :runtime
|
75
|
+
version_requirements: *id004
|
76
|
+
- !ruby/object:Gem::Dependency
|
77
|
+
name: sass
|
78
|
+
prerelease: false
|
79
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
80
|
+
none: false
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
segments:
|
85
|
+
- 3
|
86
|
+
- 0
|
87
|
+
- 0
|
88
|
+
version: 3.0.0
|
89
|
+
type: :runtime
|
90
|
+
version_requirements: *id005
|
91
|
+
- !ruby/object:Gem::Dependency
|
92
|
+
name: mongoid
|
93
|
+
prerelease: false
|
94
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
95
|
+
none: false
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
segments:
|
100
|
+
- 0
|
101
|
+
version: "0"
|
102
|
+
type: :runtime
|
103
|
+
version_requirements: *id006
|
104
|
+
- !ruby/object:Gem::Dependency
|
105
|
+
name: gravtastic
|
106
|
+
prerelease: false
|
107
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
108
|
+
none: false
|
109
|
+
requirements:
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
segments:
|
113
|
+
- 0
|
114
|
+
version: "0"
|
115
|
+
type: :runtime
|
116
|
+
version_requirements: *id007
|
117
|
+
- !ruby/object:Gem::Dependency
|
118
|
+
name: shoulda
|
119
|
+
prerelease: false
|
120
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - "="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
segments:
|
126
|
+
- 2
|
127
|
+
- 10
|
128
|
+
- 3
|
129
|
+
version: 2.10.3
|
130
|
+
type: :development
|
131
|
+
version_requirements: *id008
|
132
|
+
- !ruby/object:Gem::Dependency
|
133
|
+
name: rack-test
|
134
|
+
prerelease: false
|
135
|
+
requirement: &id009 !ruby/object:Gem::Requirement
|
136
|
+
none: false
|
137
|
+
requirements:
|
138
|
+
- - ">="
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
segments:
|
141
|
+
- 0
|
142
|
+
- 5
|
143
|
+
- 6
|
144
|
+
version: 0.5.6
|
145
|
+
type: :development
|
146
|
+
version_requirements: *id009
|
147
|
+
description: Simple sinatra/mongodb app for tracking time when people have been in the office
|
148
|
+
email: christoph at olszowka de
|
149
|
+
executables: []
|
150
|
+
|
151
|
+
extensions: []
|
152
|
+
|
153
|
+
extra_rdoc_files:
|
154
|
+
- LICENSE
|
155
|
+
- README.rdoc
|
156
|
+
files:
|
157
|
+
- .document
|
158
|
+
- .gitignore
|
159
|
+
- Gemfile
|
160
|
+
- Gemfile.lock
|
161
|
+
- LICENSE
|
162
|
+
- README.rdoc
|
163
|
+
- Rakefile
|
164
|
+
- VERSION
|
165
|
+
- config.ru
|
166
|
+
- lib/public/cardbg.png
|
167
|
+
- lib/public/css/blueprint.css
|
168
|
+
- lib/public/js/app.js
|
169
|
+
- lib/punchcard.rb
|
170
|
+
- lib/punchcard/mongohq.rb
|
171
|
+
- lib/punchcard/person.rb
|
172
|
+
- lib/punchcard/punch.rb
|
173
|
+
- lib/views/index.haml
|
174
|
+
- lib/views/layout.haml
|
175
|
+
- lib/views/screen.sass
|
176
|
+
- punchcard.gemspec
|
177
|
+
- test/helper.rb
|
178
|
+
- test/test_database.rb
|
179
|
+
- test/test_punchcard.rb
|
180
|
+
has_rdoc: true
|
181
|
+
homepage: http://github.com/colszowka/punchcard
|
182
|
+
licenses: []
|
183
|
+
|
184
|
+
post_install_message:
|
185
|
+
rdoc_options:
|
186
|
+
- --charset=UTF-8
|
187
|
+
require_paths:
|
188
|
+
- lib
|
189
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
190
|
+
none: false
|
191
|
+
requirements:
|
192
|
+
- - ">="
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
segments:
|
195
|
+
- 0
|
196
|
+
version: "0"
|
197
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
198
|
+
none: false
|
199
|
+
requirements:
|
200
|
+
- - ">="
|
201
|
+
- !ruby/object:Gem::Version
|
202
|
+
segments:
|
203
|
+
- 0
|
204
|
+
version: "0"
|
205
|
+
requirements: []
|
206
|
+
|
207
|
+
rubyforge_project:
|
208
|
+
rubygems_version: 1.3.7
|
209
|
+
signing_key:
|
210
|
+
specification_version: 3
|
211
|
+
summary: Simple sinatra/mongodb app for tracking time when people have been in the office
|
212
|
+
test_files:
|
213
|
+
- test/helper.rb
|
214
|
+
- test/test_database.rb
|
215
|
+
- test/test_punchcard.rb
|