implement _couchdb_get to get stored entries back
[angular-mojolicious.git] / 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
10 sub new_uuid { Time::HiRes::time * 100000 }
11
12 # based on
13 # http://docs.getangular.com/REST.Basic
14 # http://angular.getangular.com/data
15
16 our $data = {
17         'Cookbook' => {
18                 test => [
19                                 { '$id' => 1, foo => 1, bar => 2, baz => 3 },
20                                 { '$id' => 2, foo => 1                     },
21                                 { '$id' => 3,           bar => 2           },
22                                 { '$id' => 4,                     baz => 3 },
23                 ],
24         },
25         'AddressBook' => {
26                 people => [
27                         {name=>'Misko'},
28                         {name=>'Igor'},
29                         {name=>'Adam'},
30                         {name=>'Elliott'}
31                 ]
32         }
33 };
34
35 my $couchdb = 'http://localhost:5984';
36 our $couchdb_rev;
37
38 sub _couchdb_put {
39         my ( $self, $database, $entity, $id, $hash ) = @_;
40
41         my $data = clone $hash;
42         delete $data->{_id}; # CouchDB doesn't like _ prefixed attributes, and will generate it's own _id
43         $data->{'$entity'} = $entity;
44         if ( my $rev = $couchdb_rev->{$database}->{$entity}->{$id} ) {
45                 $data->{_rev} = $rev;
46         }
47
48         my $json = Mojo::JSON->new->encode( $data );
49         my $client = Mojo::Client->new;
50
51         warn "# _couchdb_put $couchdb/$database/$entity.$id = $json";
52         $client->put( "$couchdb/$database/$entity.$id" => $json => sub {
53                 my ($client,$tx) = @_;
54                 if ($tx->error) {
55                         die $tx->error;
56                 }
57                 my $response = $tx->res->json;
58                 warn "## CouchDB response ",dump($response);
59                 $couchdb_rev->{$database}->{$entity}->{$id} = $response->{rev} || die "no rev";
60         })->process;
61 }
62
63 sub _couchdb_get {
64         my ( $self, $database, $entity, $id ) = @_;
65         my $client = Mojo::Client->new;
66         my $return = $client->get( "$couchdb/$database/$entity.$id" )->res->json;
67         warn "# _couchdb_get $couchdb/$database/$entity.$id = ",dump($return);
68         return $return;
69 }
70
71 our $id2nr;
72
73
74 sub _render_jsonp {
75         my ( $self, $json ) = @_;
76 #warn "## _render_json ",dump($json);
77         my $data = $self->render( json => $json, partial => 1 );
78 warn "## _render_json $data";
79         if ( my $callback = $self->param('callback') ) {
80                 $data = "$callback($data)";
81         }
82         $self->render( data => $data, format => 'js' );
83 }
84
85 #get '/' => 'index';
86
87 get '/_replicate' => sub {
88         my $self = shift;
89
90         if ( my $from = $self->param('from') ) {
91                 my $got = $self->client->get( $from )->res->json;
92                 warn "# from $from ",dump($got);
93                 _render_jsonp( $self,  $got );
94
95                 my $database = $got->{name};
96                 my $entities = $got->{entities};
97
98                 if ( $database && $entities ) {
99                         foreach my $entity ( keys %$entities ) {
100                                 my $url = $from;
101                                 $url =~ s{/?$}{/}; # add slash at end
102                                 $url .= $entity;
103                                 my $e = $self->client->get( $url )->res->json;
104                                 warn "# replicated $url ", dump($e);
105                                 $data->{$database}->{$entity} = $e;
106                                 delete $id2nr->{$database}->{$entity};
107                         }
108                 }
109         }
110 };
111
112 get '/_data' => sub {
113         my $self = shift;
114         _render_jsonp( $self, $data )
115 };
116
117 get '/data/' => sub {
118         my $self = shift;
119         _render_jsonp( $self,  [ keys %$data ] );
120 };
121
122 get '/data/:database' => sub {
123         my $self = shift;
124         my $database = $self->param('database');
125         my $list_databases = { name => $database };
126         foreach my $entity ( keys %{ $data->{ $database }} ) {
127 warn "# entry $entity ", dump( $data->{$database}->{$entity} );
128                 my $count = $#{ $data->{$database}->{$entity} } + 1;
129                 $list_databases->{entities}->{$entity} = $count;
130                 $list_databases->{document_count} += $count;
131         }
132         warn dump($list_databases);
133         _render_jsonp( $self,  $list_databases );
134 };
135
136 get '/data/:database/:entity' => sub {
137         my $self = shift;
138         _render_jsonp( $self,  $data->{ $self->param('database') }->{ $self->param('entity' ) } );
139 };
140
141 get '/data/:database/:entity/:id' => sub {
142     my $self = shift;
143
144         my $database = $self->param('database');
145         my $entity   = $self->param('entity');
146         my $id       = $self->param('id');
147
148         my $e = $data->{$database}->{$entity};
149
150         if ( ! $e ) {
151                 $e = _couchdb_get( $self, $database, $entity, $id );
152                 return _render_jsonp( $self, $e );
153         }
154
155         if ( ! defined $id2nr->{$database}->{$entity}  ) {
156                 foreach my $i ( 0 .. $#$e ) {
157                         $id2nr->{$database}->{$entity}->{ $e->[$i]->{'$id'} } = $i;
158                 }
159         }
160
161         if ( exists $id2nr->{$database}->{$entity}->{$id} ) {
162                 my $nr = $id2nr->{$database}->{$entity}->{$id};
163                 warn "# entity $id -> $nr\n";
164                 _render_jsonp( $self,  $data->{$database}->{$entity}->[$nr] );
165         } else {
166                 die "no entity $entity $id in ", dump( $id2nr->{$database}->{$entity} );
167         }
168 };
169
170 any [ 'post' ] => '/data/:database/:entity' => sub {
171         my $self = shift;
172         my $json = $self->req->json;
173         my $id = $json->{'$id'} # XXX we don't get it back from angular.js
174                 || $json->{'_id'}  # so we use our version
175                 || new_uuid;
176         warn "## $id body ",dump($self->req->body, $json);
177         die "no data" unless $data;
178
179         $json->{'$id'} ||= $id; # angular.js doesn't resend this one
180         $json->{'_id'} = $id;   # but does this one :-)
181
182         my $database = $self->param('database');
183         my $entity   = $self->param('entity');
184
185         my $nr = $id2nr->{$database}->{$entity}->{$id};
186         if ( defined $nr ) {
187                 $data->{$database}->{$entity}->[$nr] = $json;
188                 warn "# update $nr $id ",dump($json);
189         } else {
190                 push @{ $data->{$database}->{$entity} }, $json;
191                 my $nr = $#{ $data->{$database}->{$entity} };
192                 $id2nr->{$database}->{$entity}->{$id} = $nr;
193                 warn "# added $nr $id ",dump($json);
194         }
195
196         _couchdb_put( $self, $database, $entity, $id, $json );
197
198         _render_jsonp( $self,  $json );
199 };
200
201 get '/demo/:groovy' => sub {
202         my $self = shift;
203     $self->render(text => $self->param('groovy'), layout => 'funky');
204 };
205
206 get '/' => sub { shift->redirect_to('/Cookbook') };
207 get '/Cookbook' => 'Cookbook';
208 get '/Cookbook/:example' => sub {
209         my $self = shift;
210         $self->render( "Cookbook/" . $self->param('example'), layout => 'angular' );
211 };
212
213 get '/conference/:page' => sub {
214         my $self = shift;
215         $self->render( "conference/" . $self->param('page'), layout => 'angular' );
216 };
217
218 app->start;
219 __DATA__
220
221 @@ index.html.ep
222 % layout 'funky';
223 Yea baby!
224
225 @@ layouts/funky.html.ep
226 <!doctype html><html>
227     <head><title>Funky!</title></head>
228     <body><%== content %></body>
229 </html>
230
231 @@ layouts/angular.html.ep
232 <!DOCTYPE HTML>
233 <html xmlns:ng="http://angularjs.org">
234   <head>
235    <meta charset="utf-8">
236 % my $ANGULAR_JS = $ENV{ANGULAR_JS} || ( -e 'public/angular/build/angular.js' ? '/angular/build/angular.js' : '/angular/src/angular-bootstrap.js' );
237     <script type="text/javascript"
238          src="<%== $ANGULAR_JS %>" ng:autobind></script>
239   </head>
240   <body><%== content %></body>
241 </html>