mojolicious perl server
[angular-drzb] / angular-server.pl
1 #!/usr/bin/env perl
2
3 use lib 'common/mojo/lib';
4
5 use Mojolicious::Lite;
6 use Data::Dump qw(dump);
7 use Time::HiRes;
8 use Clone qw(clone);
9 use Mojo::UserAgent;
10
11 sub new_uuid { Time::HiRes::time * 100000 }
12
13 # based on
14 # http://docs.getangular.com/REST.Basic
15 # http://angular.getangular.com/data
16
17 push @{app->static->paths}, 'app';
18
19
20 my $couchdb = $ENV{COUCHDB} || 'http://localhost:5984';
21 my $client = Mojo::UserAgent->new;
22
23 sub _couchdb_put {
24         my ( $url, $data ) = @_;
25
26         $data->{'$entity'} = $1 if $url =~ m{/(\w+)\.\d+/$/};
27
28         my $json = Mojo::JSON->new->encode( $data );
29
30         my $rev;
31
32         warn "# _couchdb_put $url = $json";
33         return $client->put( "$couchdb/$url" => $json)->res->json;
34 }
35
36 sub _couchdb_get {
37         my ( $url ) = @_;
38         my $return = $client->get( "$couchdb/$url" )->res->json;
39         warn "# _couchdb_get $url = ",dump($return);
40         return $return;
41 }
42
43
44 our $id2nr;
45
46
47 sub _render_jsonp {
48         my ( $self, $json ) = @_;
49 #warn "## _render_json ",dump($json);
50         my $data = $self->render( json => $json, partial => 1 );
51 warn "## _render_json $data";
52         if ( my $callback = $self->param('callback') ) {
53                 $data = "$callback($data)";
54         }
55         $self->render( data => $data, format => 'js' );
56 }
57
58 #get '/' => 'index';
59
60
61 get '/data/' => sub {
62         my $self = shift;
63         _render_jsonp( $self, _couchdb_get('/_all_dbs') );
64 };
65
66 get '/data/:database' => sub {
67         my $self = shift;
68         my $database = $self->param('database');
69
70         my $list_databases = { name => $database };
71
72         my $counts = _couchdb_get("/$database/_design/entity/_view/counts?group=true");
73         if ( exists $counts->{error} ) {
74                 warn "creating CouchDB view because of ", dump($counts);
75                 _couchdb_put "/$database/_design/entity", {
76                         _id => '_design/entity',
77                         language => 'javascript',
78                         views => {
79                                 counts => {
80                                         map    => q| function(doc) { emit(doc._id.split('.')[0],1); } |,
81                                         reduce => q| function(keys,values,rereduce) { return sum(values); } |,
82                                 }
83                         }
84                 };
85                 $counts = _couchdb_get("/$database/_design/entity/_view/counts?group=true")
86                 || die "give up!";
87         }
88
89         warn "# counts ",dump($counts);
90
91         foreach my $row ( @{ $counts->{rows} } ) {
92                 my $n = $row->{value};
93                 $list_databases->{entities}->{ $row->{key} } = $n;
94                 $list_databases->{document_counts} += $n;
95         }
96         warn dump($list_databases);
97         _render_jsonp( $self,  $list_databases );
98 };
99
100 get '/data/:database/:entity' => sub {
101         my $self = shift;
102
103         my $database = $self->param('database');
104         my $entity   = $self->param('entity');
105
106         my $endkey = $entity;
107         $endkey++;
108
109         my $counts = _couchdb_get qq|/$database/_all_docs?startkey="$entity";endkey="$endkey";include_docs=true|;
110         warn "# counts ",dump($counts);
111
112         _render_jsonp( $self, [ map { $_->{doc} } @{ $counts->{rows} } ] )
113 };
114
115 get '/data/:database/:entity/:id' => sub {
116     my $self = shift;
117
118         my $database = $self->param('database');
119         my $entity   = $self->param('entity');
120         my $id       = $self->param('id');
121
122         _render_jsonp( $self, _couchdb_get( "/$database/$entity.$id" ) );
123 };
124
125 any [ 'post' ] => '/data/:database/:entity' => sub {
126         my $self = shift;
127         my $database = $self->param('database');
128         my $entity   = $self->param('entity');
129         my $json = $self->req->json;
130         my $id = $json->{'$id'} # XXX we don't get it back from angular.js
131                 || new_uuid;
132         warn "## $database $entity $id body ",dump($self->req->body, $json);
133
134         $json->{'$id'} ||= $id; # make sure $id is in there
135
136         my $new = _couchdb_put "/$database/$entity.$id" => $json;
137         warn "new: ",dump($new);
138         if ( $new->{ok} ) {
139                 $json->{'_'.$_} = $new->{$_} foreach ( 'rev','id' );
140         } else {
141                 warn "ERROR: ",dump($new);
142                 $json->{error} = $new;
143         }
144
145         _render_jsonp( $self,  $json );
146 };
147
148
149 #get '/' => sub { shift->redirect_to('/app/') };
150
151 # CouchDB proxy for _design _view
152
153 get '/:database/_design/:design/_view/:view' => sub {
154         my $self = shift;
155         my $url = join('/', $self->param('database'),'_design',$self->param('design'),'_view',$self->param('view') );
156         my $param = $self->req->url->query->clone->remove('callback')->to_string;
157         $url .= '?' . $param if $param;
158         warn "CouchDB proxy $url";
159         _render_jsonp( $self, _couchdb_get($url));
160 };
161
162 # static JSON files from public/json/database/entity/json
163
164 get '/json' => sub {
165         _render_jsonp( shift, [ map { s{public/json/}{}; $_ } glob 'public/json/*' ] );
166 };
167
168 get '/json/:database' => sub {
169         my $self = shift;
170         my $database = $self->param('database');
171
172         my $status = {
173                 document_counts => 0,
174                 name => $database,
175         };
176
177         foreach my $path ( glob "public/json/$database/*" ) {
178                 my @entities = glob "$path/*";
179                 $path =~ s{public/json/$database/}{};
180                 $status->{entities}->{$path} = scalar @entities;
181                 $status->{document_counts}++;
182         }
183
184         _render_jsonp( $self, $status );
185 };
186
187 get '/json/:database/:entity' => sub {
188         my $self = shift;
189
190         my $database = $self->param('database');
191         my $entity   = $self->param('entity');
192
193         my $path = "public/json/$database/$entity";
194         die "$path: $!" unless -d $path;
195
196         my $docs;
197         foreach my $path ( sort glob "$path/*" ) {
198                 open(my $fh, '<', $path) || die $!;
199                 local $/ = undef;
200                 my $str = <$fh>;
201                 warn "# $path $str";
202                 my $data = Mojo::JSON->new->decode( $str );
203                 $data->{_key} = $1 if $path =~ m{/([^/]+$)};
204                 push @$docs, $data;
205         }
206
207         _render_jsonp( $self, $docs )
208 };
209
210 get '/_utils/script/(*url)' => sub { $_[0]->proxy_to( "$couchdb/_utils/script/" . $_[0]->param('url') , with_query_params => 1 ) };
211
212 app->start;
213 __DATA__
214
215 @@ index.html.ep
216 % layout 'funky';
217 Yea baby!
218
219 @@ layouts/funky.html.ep
220 <!doctype html><html>
221     <head><title>Funky!</title></head>
222     <body><%== content %></body>
223 </html>
224
225 @@ layouts/angular.html.ep
226 <!DOCTYPE HTML>
227 <html xmlns:ng="http://angularjs.org">
228   <head>
229    <meta charset="utf-8">
230 % my $ANGULAR_JS = $ENV{ANGULAR_JS} || ( -e 'public/angular/build/angular.js' ? '/angular/build/angular.js' : '/angular/src/angular-bootstrap.js' );
231     <script type="text/javascript"
232          src="<%== $ANGULAR_JS %>" ng:autobind></script>
233   </head>
234   <body><%== content %></body>
235 </html>