punchcard 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|