line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package AproJo; |
2
|
2
|
|
|
2
|
|
10851
|
use Mojo::Base 'Mojolicious'; |
|
2
|
|
|
|
|
12254
|
|
|
2
|
|
|
|
|
8
|
|
3
|
|
|
|
|
|
|
|
4
|
2
|
|
|
2
|
|
502422
|
use Data::Dumper; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
113
|
|
5
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
our $VERSION = '0.011'; |
7
|
|
|
|
|
|
|
|
8
|
2
|
|
|
2
|
|
10
|
use File::Basename 'dirname'; |
|
2
|
|
|
|
|
12
|
|
|
2
|
|
|
|
|
68
|
|
9
|
2
|
|
|
2
|
|
7
|
use File::Spec::Functions qw'rel2abs catdir'; |
|
2
|
|
|
|
|
2
|
|
|
2
|
|
|
|
|
69
|
|
10
|
2
|
|
|
2
|
|
789
|
use File::ShareDir 'dist_dir'; |
|
2
|
|
|
|
|
7559
|
|
|
2
|
|
|
|
|
120
|
|
11
|
2
|
|
|
2
|
|
10
|
use Cwd; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
1665
|
|
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
has db => sub { |
14
|
|
|
|
|
|
|
my $self = shift; |
15
|
|
|
|
|
|
|
my $schema_class = $self->config->{db_schema} |
16
|
|
|
|
|
|
|
or die "Unknown DB Schema Class"; |
17
|
|
|
|
|
|
|
eval "require $schema_class" |
18
|
|
|
|
|
|
|
or die "Could not load Schema Class ($schema_class), $@"; |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
my $db_connect = $self->config->{db_connect} |
21
|
|
|
|
|
|
|
or die "No DBI connection string provided"; |
22
|
|
|
|
|
|
|
my @db_connect = ref $db_connect ? @$db_connect : ($db_connect); |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
my $schema = $schema_class->connect(@db_connect) |
25
|
|
|
|
|
|
|
or die "Could not connect to $schema_class using $db_connect[0]"; |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
return $schema; |
28
|
|
|
|
|
|
|
}; |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
has app_debug => 0; |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
has home_path => sub { |
33
|
|
|
|
|
|
|
my $path = $ENV{MOJO_HOME} || getcwd; |
34
|
|
|
|
|
|
|
return File::Spec->rel2abs($path); |
35
|
|
|
|
|
|
|
}; |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
has config_file => sub { |
38
|
|
|
|
|
|
|
my $self = shift; |
39
|
|
|
|
|
|
|
return $ENV{APROJO_CONFIG} if $ENV{APROJO_CONFIG}; |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
return rel2abs('aprojo.conf', $self->home_path); |
42
|
|
|
|
|
|
|
}; |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
sub startup { |
45
|
1
|
|
|
1
|
1
|
37844
|
my $app = shift; |
46
|
|
|
|
|
|
|
|
47
|
1
|
|
|
|
|
5
|
$app->plugin( |
48
|
|
|
|
|
|
|
Config => { |
49
|
|
|
|
|
|
|
file => $app->config_file, |
50
|
|
|
|
|
|
|
default => { |
51
|
|
|
|
|
|
|
'db_connect' => [ |
52
|
|
|
|
|
|
|
'dbi:SQLite:dbname=' . $app->home->rel_file('aprojo.db'), |
53
|
|
|
|
|
|
|
undef, |
54
|
|
|
|
|
|
|
undef, |
55
|
|
|
|
|
|
|
{'sqlite_unicode' => 1} |
56
|
|
|
|
|
|
|
], |
57
|
|
|
|
|
|
|
'db_schema' => 'AproJo::DB::Schema', |
58
|
|
|
|
|
|
|
'secret' => '47110815' |
59
|
|
|
|
|
|
|
}, |
60
|
|
|
|
|
|
|
} |
61
|
|
|
|
|
|
|
); |
62
|
|
|
|
|
|
|
|
63
|
1
|
|
|
|
|
1356
|
$app->plugin('I18N'); |
64
|
1
|
|
|
|
|
6068
|
$app->plugin('Mojolicious::Plugin::ServerInfo'); |
65
|
1
|
|
|
|
|
1296
|
$app->plugin('Mojolicious::Plugin::DBInfo'); |
66
|
|
|
|
|
|
|
|
67
|
1
|
|
|
|
|
1061
|
$app->plugin('Mojolicious::Plugin::Form'); |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
{ |
70
|
|
|
|
|
|
|
# use content from directories under share/files or using File::ShareDir |
71
|
1
|
|
|
|
|
1015
|
my $lib_base = catdir(dirname(rel2abs(__FILE__)), '..', 'share','files'); |
|
1
|
|
|
|
|
5
|
|
72
|
|
|
|
|
|
|
|
73
|
1
|
|
|
|
|
67
|
my $public = catdir($lib_base, 'public'); |
74
|
1
|
50
|
|
|
|
19
|
$app->static->paths->[0] = -d $public ? $public : catdir(dist_dir('AproJo'), 'files','public'); |
75
|
1
|
|
|
|
|
94
|
my $static_path = $app->static->paths->[0]; |
76
|
|
|
|
|
|
|
#print STDERR '$static_path: ',$static_path,"\n"; |
77
|
|
|
|
|
|
|
|
78
|
1
|
|
|
|
|
12
|
my $templates = catdir($lib_base, 'templates'); |
79
|
1
|
50
|
|
|
|
19
|
$app->renderer->paths->[0] = -d $templates ? $templates : catdir(dist_dir('AproJo'), 'files', 'templates'); |
80
|
|
|
|
|
|
|
} |
81
|
|
|
|
|
|
|
|
82
|
1
|
|
|
|
|
103
|
push @{$app->commands->namespaces}, 'AproJo::Command'; |
|
1
|
|
|
|
|
11
|
|
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
#DEPRECATED: $app->secret( $app->config->{secret} ); |
85
|
1
|
|
|
|
|
57
|
$app->secrets([$app->config->{secret}]); |
86
|
|
|
|
|
|
|
|
87
|
1
|
|
|
3
|
|
21
|
$app->helper(schema => sub { shift->app->db }); |
|
3
|
|
|
|
|
94
|
|
88
|
|
|
|
|
|
|
|
89
|
1
|
|
|
1
|
|
19
|
$app->helper('home_page' => sub {'/'}); |
|
1
|
|
|
|
|
37
|
|
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
$app->helper( |
92
|
|
|
|
|
|
|
'auth_fail' => sub { |
93
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
94
|
0
|
|
0
|
|
|
0
|
my $message = shift || "Not Authorized"; |
95
|
0
|
|
|
|
|
0
|
$self->flash(onload_message => $message); |
96
|
0
|
|
|
|
|
0
|
$self->redirect_to($self->home_page); |
97
|
0
|
|
|
|
|
0
|
return 0; |
98
|
|
|
|
|
|
|
} |
99
|
1
|
|
|
|
|
16
|
); |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
$app->helper( |
102
|
|
|
|
|
|
|
'source_id' => sub { |
103
|
0
|
|
|
0
|
|
0
|
my ($self, $source) = @_; |
104
|
0
|
0
|
|
|
|
0
|
return undef unless $source; |
105
|
0
|
|
|
|
|
0
|
my @columns = $self->schema->source($source)->columns; |
106
|
0
|
|
|
|
|
0
|
my $table_name = $self->schema->class($source)->table; |
107
|
0
|
|
|
|
|
0
|
my $source_id = $table_name . '_id'; |
108
|
0
|
0
|
|
|
|
0
|
return $source_id if (grep {/$source_id/} @columns); |
|
0
|
|
|
|
|
0
|
|
109
|
0
|
0
|
|
|
|
0
|
return $columns[0] if (scalar @columns); |
110
|
|
|
|
|
|
|
} |
111
|
1
|
|
|
|
|
17
|
); |
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
$app->helper( |
114
|
|
|
|
|
|
|
'get_user' => sub { |
115
|
0
|
|
|
0
|
|
0
|
my ($self, $name) = @_; |
116
|
0
|
0
|
|
|
|
0
|
unless ($name) { |
117
|
0
|
|
|
|
|
0
|
$name = $self->session->{username}; |
118
|
|
|
|
|
|
|
} |
119
|
0
|
0
|
|
|
|
0
|
return undef unless $name; |
120
|
|
|
|
|
|
|
#say STDERR 'get_user: ', $name if $self->app->app_debug; |
121
|
0
|
|
|
|
|
0
|
return $self->schema->resultset('User')->single({name => $name}); |
122
|
|
|
|
|
|
|
} |
123
|
1
|
|
|
|
|
16
|
); |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
$app->helper( |
126
|
|
|
|
|
|
|
'has_role' => sub { |
127
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
128
|
0
|
|
0
|
|
|
0
|
my $user_string = shift || ''; |
129
|
0
|
|
0
|
|
|
0
|
my $role_string = shift || ''; |
130
|
0
|
|
|
|
|
0
|
my $user = $self->get_user($user_string); |
131
|
0
|
0
|
|
|
|
0
|
return undef unless $user; |
132
|
0
|
|
|
|
|
0
|
my $role = $user->roles()->single({name => $role_string}); |
133
|
|
|
|
|
|
|
#say STDERR 'has_role: ', $role->name if $self->app->app_debug; |
134
|
0
|
|
0
|
|
|
0
|
return ($role && $role->name eq $role_string); |
135
|
|
|
|
|
|
|
} |
136
|
1
|
|
|
|
|
16
|
); |
137
|
|
|
|
|
|
|
$app->helper( |
138
|
|
|
|
|
|
|
'is_admin' => sub { |
139
|
0
|
|
|
0
|
|
0
|
my ($self,$user) = @_; |
140
|
0
|
|
|
|
|
0
|
return $self->has_role($user,'admin'); |
141
|
|
|
|
|
|
|
} |
142
|
1
|
|
|
|
|
16
|
); |
143
|
|
|
|
|
|
|
|
144
|
1
|
|
|
|
|
14
|
my $routes = $app->routes; |
145
|
|
|
|
|
|
|
|
146
|
1
|
|
|
|
|
9
|
$routes->get('/')->to('front#index'); |
147
|
1
|
|
|
|
|
121
|
$routes->get('/front/*name')->to('front#page'); |
148
|
1
|
|
|
|
|
180
|
$routes->post('/save')->to('front#save'); |
149
|
1
|
|
|
|
|
147
|
$routes->post('/login')->to('user#login'); |
150
|
1
|
|
|
|
|
148
|
$routes->any('/logout')->to('user#logout'); |
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
my $if_admin = $routes->under( |
153
|
|
|
|
|
|
|
sub { |
154
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
155
|
0
|
0
|
|
|
|
0
|
return $self->auth_fail unless $self->is_admin; |
156
|
0
|
|
|
|
|
0
|
return 1; |
157
|
|
|
|
|
|
|
} |
158
|
1
|
|
|
|
|
156
|
); |
159
|
|
|
|
|
|
|
|
160
|
1
|
|
|
|
|
82
|
$if_admin->post('/admin/save/:table')->to('admin#save'); |
161
|
|
|
|
|
|
|
|
162
|
1
|
|
|
|
|
254
|
$if_admin->get('/admin/change/:table/:id')->to('admin#change'); |
163
|
1
|
|
|
|
|
213
|
$if_admin->get('/admin/show/:table')->to('admin#show'); |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
} |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
1; |
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
__END__ |
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
=head1 NAME |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
AproJo - A time recording application based on Mojolicious |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
=for html |
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
<a href="https://travis-ci.org/wollmers/AproJo"><img src="https://travis-ci.org/wollmers/AproJo.png" alt="AproJo"></a> |
178
|
|
|
|
|
|
|
<a href='https://coveralls.io/r/wollmers/AproJo?branch=master'><img src='https://coveralls.io/repos/wollmers/AproJo/badge.png?branch=master' alt='Coverage Status' /></a> |
179
|
|
|
|
|
|
|
<a href='http://cpants.cpanauthors.org/dist/AproJo'><img src='http://cpants.cpanauthors.org/dist/AproJo.png' alt='Kwalitee Score' /></a> |
180
|
|
|
|
|
|
|
<a href="http://badge.fury.io/pl/AproJo"><img src="https://badge.fury.io/pl/AproJo.svg" alt="CPAN version" height="18"></a> |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
=head1 SYNOPSIS |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
$ aprojo setup |
186
|
|
|
|
|
|
|
$ aprojo daemon |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
=head1 DESCRIPTION |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
L<AproJo> is a Perl web apllication. |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
=head1 INSTALLATION |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
L<AproJo> uses well-tested and widely-used CPAN modules, so installation should be as simple as |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
$ cpanm AproJo |
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
when using L<App::cpanminus>. Of course you can use your favorite CPAN client or install manually by cloning the L</"SOURCE REPOSITORY">. |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
=head1 SETUP |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
=head2 Environment |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
Although most of L<AproJo> is controlled by a configuration file, a few properties must be set before that file can be read. These properties are controlled by the following environment variables. |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
=over |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
=item C<APROJO_HOME> |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
This is the directory where L<AproJo> expects additional files. These include the configuration file and log files. The default value is the current working directory (C<cwd>). |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
=item C<APROJO_CONFIG> |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
This is the full path to a configuration file. The default is a file named F<aprojo.conf> in the C<APROJO_HOME> path, however this file need not actually exist, defaults may be used instead. This file need not be written by hand, it may be generated by the C<aprojo config> command. |
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
=back |
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
=head2 The F<aprojo> command line application |
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
L<AproJo> installs a command line application, C<aprojo>. It inherits from the L<mojo> command, but it provides extra functions specifically for use with AproJo. |
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
=head3 config |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
$ aprojo config [options] |
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
This command writes a configuration file in your C<APROJO_HOME> path. It uses the preset defaults for all values, except that it prompts for a secret. This can be any string, however stronger is better. You do not need to memorize it or remember it. This secret protects the cookies employed by AproJo from being tampered with on the client side. |
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
L<AproJo> does not need to be configured, however it is recommended to do so to set your application's secret. |
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
The C<--force> option may be passed to overwrite any configuration file in the current working directory. The default is to die if such a configuration file is found. |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
=head3 setup |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
$ aprojo setup |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
This step is required. Run C<aprojo setup> to setup a database. It will use the default DBI settings (SQLite) or whatever is setup in the C<APROJO_CONFIG> configuration file. |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
=head1 RUNNING THE APPLICATION |
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
$ aprojo daemon |
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
After the database is has been setup, you can run C<aprojo daemon> to start the server. |
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
You may also use L<morbo> (Mojolicious' development server) or L<hypnotoad> (Mojolicious' production server). You may even use any other server that Mojolicious supports, however for full functionality it must support websockets. When doing so you will need to know the full path to the C<aprojo> application. A useful recipe might be |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
$ hypnotoad `which aprojo` |
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
where you may replace C<hypnotoad> with your server of choice. |
249
|
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
=head2 Logging |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
Logging in L<AproJo> is the same as in L<Mojolicious|Mojolicious::Lite/Logging>. Messages will be printed to C<STDERR> unless a directory named F<log> exists in the C<APROJO_HOME> path, in which case messages will be logged to a file in that directory. |
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
=head1 TECHNOLOGIES USED |
255
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
=over |
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
=item * |
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
L<Mojolicious|http://mojolicio.us> - a next generation web framework for the Perl programming language |
261
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
=item * |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
L<DBIx::Class|http://www.dbix-class.org/> - an extensible and flexible Object/Relational Mapper written in Perl |
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
=item * |
267
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
L<Bootstrap|http://twitter.github.com/bootstrap> - the CSS/JS library from Twitter |
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
=item * |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
L<jQuery|http://jquery.com/> - jQuery |
273
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
=back |
276
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
=head1 SEE ALSO |
278
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
=over |
280
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
=item * |
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
L<Contenticious> - File-based Markdown website application |
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
=back |
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
=head1 SOURCE REPOSITORY |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
L<http://github.com/wollmers/aprojo> |
290
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
=head1 AUTHOR |
292
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
Helmut Wollmersdorfer, E<lt>helmut.wollmersdorfer@gmail.comE<gt> |
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
=for html |
296
|
|
|
|
|
|
|
<a href='http://cpants.cpanauthors.org/author/wollmers'><img src='http://cpants.cpanauthors.org/author/wollmers.png' alt='Kwalitee Score' /></a> |
297
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
299
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
Copyright (C) 2013-2015 by Helmut Wollmersdorfer |
301
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
This library is free software; you can redistribute it and/or modify |
303
|
|
|
|
|
|
|
it under the same terms as Perl itself. |
304
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
=cut |
306
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
|