Merge branch 'master' of github.com:dpavlin/angular-mojolicious
authorDobrica Pavlinusic <dpavlin@rot13.org>
Thu, 9 Dec 2010 16:38:20 +0000 (17:38 +0100)
committerDobrica Pavlinusic <dpavlin@rot13.org>
Thu, 9 Dec 2010 16:38:20 +0000 (17:38 +0100)
Conflicts:
public/app/casopisi2010/upitnik.html

README
couchdb-external-kinosearch.pl [new file with mode: 0755]
couchdb-trigger.pl
public/app/casopisi2010/upitnik.html
trigger/KinoSearch.pm [new file with mode: 0644]

diff --git a/README b/README
index afebd1b..b7af6eb 100644 (file)
--- a/README
+++ b/README
@@ -26,14 +26,23 @@ At it's current stage it provides support for angular $resource get, query and $
 http://angularjs.org/Service:$resource
 
 
+Data can also be serve static json files stored in:
+
+       public/json/:database/:entity/:key
+
+which can be used to provide data using external stand-alone scripts.
+
+
+
 Replication of data between instances using angular REST API can be done with:
 
        # create local CouchDB database
-       curl -X PUT http://localhost:5984/test
+       $ curl -X PUT http://localhost:5984/demo
+       {"ok":true}
 
-       ./angular-replicate.pl \
+       ./angular-replicate.pl \
                http://dpavlin.getangular.com/data/conference \
-               http://localhost:3000/data/test
+               http://localhost:3000/data/demo
 
 Replication is currently good only for initial import of data since it doesn't
 support incremental replication and dies if data is allready present.
@@ -41,18 +50,18 @@ support incremental replication and dies if data is allready present.
 
 Installation:
 
-       git submodule init
-       git submodule update
+       git submodule init
+       git submodule update
 
 Optionally build angular to get single file download
 
-       cd public/angular
-       rake compile
-       cd -
+       cd public/angular
+       rake compile
+       cd -
 
 Start it with:
 
-       ./angular-server.pl daemon --reload  
+       ./angular-server.pl daemon --reload  
 
 
 Angular examples available:
@@ -61,12 +70,47 @@ Angular examples available:
 - template/conferece - conference submission example using mojolicious REST API server
 - public/app/conference - new application layout with latest example confernce submission
 
-       curl -X PUT http://localhost:5984/conference/_design/symposium \
+       curl -X PUT http://localhost:5984/conference/_design/symposium \
                -d @public/app/conference/_design/symposium
 
 
+
+CouchDB examples:
+
+       couchdb-changes.pl - simple _changes feed watcher using Mojo::Client documented at
+
+               http://wiki.apache.org/couchdb/HTTP_database_API#Changes
+
+
+       couchdb-trigger.pl - FSM document with hook for user-defiend triggers
+
+               trigger/shell.pm - execute shell commands
+               trigger/email.pm - skeleton for sending e-email
+               trigger/KinoSearch.pm - full-text search
+
+       $ curl -X PUT http://localhost:5984/demo
+       {"ok":true}
+
+       $ ./couchdb-trigger.pl http://localhost:5984/demo trigger/shell.pm
+
+       $ curl -X PUT http://localhost:5984/demo/t1 -d '{"trigger":{"command":"notify-send CouchDB t1"}}'
+
+
+       couchdb-external-kinosearch.pl - external searcher for KinoSearch indexes
+               (configuration for CouchDB is included at end of file)
+
+       $ ./couchdb-trigger.pl http://localhost:5984/demo trigger/KinoSearch.pm
+
+       $ curl -X PUT http://localhost:5984/demo/text -d '{"text":"foobar bla bla"}'
+       {"ok":true,"id":"text","rev":"1-cf9bb608f93af7f4e5e40656a6e50096"}
+
+       $ curl 'http://localhost:5984/demo/_kinosearch?include_docs=true;q=foobar'
+
+
+
 Roadmap:
 
 + implement angular-server.pl which implements REST API supported by $resource in angular
 + persistency to local CouchDB, and use views to query data
++ implement CouchDB _changes and FSM inside document as base for queue or triggers
 - tests
diff --git a/couchdb-external-kinosearch.pl b/couchdb-external-kinosearch.pl
new file mode 100755 (executable)
index 0000000..b455bfd
--- /dev/null
@@ -0,0 +1,70 @@
+#!/usr/bin/perl
+use warnings;
+use strict;
+
+# http://wiki.apache.org/couchdb/ExternalProcesses
+#
+# curl 'http://localhost:5984/drzb2011/_kinosearch?q=a&include_docs=true'
+
+use KinoSearch::Search::IndexSearcher;
+use Mojo::JSON;
+use Data::Dump qw(dump);
+
+$|=1;
+
+our $json = Mojo::JSON->new;
+
+open(my $log, '>>', '/tmp/couchdb-external-kinosearch.log');
+
+while(<STDIN>) {
+       warn "# $_\n";
+       my $request = $json->decode($_);
+       print $log "<<< $_\n"; 
+
+       my $response = {
+               code => 200,
+#              json => {},
+       };
+
+       if ( my $q = $request->{query}->{q} ) {
+
+               my $searcher = KinoSearch::Search::IndexSearcher->new( 
+                       index => '/tmp/kinosearch.' . $request->{info}->{db_name},
+               );
+
+               my $hits = $searcher->hits( query => $q );
+
+               $response->{json}->{total_hits} = $hits->total_hits;
+
+               while ( my $hit = $hits->next ) {
+                       my $r = {
+                               _id => $hit->{_id},
+                               _rev => $hit->{_rev},
+                               score => $hit->get_score,
+                       };
+                       $r->{doc} = $json->decode( $hit->{doc} ) if exists $request->{query}->{include_docs};
+                       push @{ $response->{json}->{hits} }, $r;
+               }
+
+       } else {
+               $response->{json}->{error} = "no query found";
+       }
+
+       my $send = $json->encode($response);
+       print $send, $/;
+       print $log ">>> $send\n";
+}
+
+
+__END__
+; insert following into /etc/couchdb/local.ini:
+
+[log]
+level = debug
+
+[external]
+kinosearch = /srv/angular-mojolicious/couchdb-external-kinosearch.pl
+
+[httpd_db_handlers]
+_kinosearch = {couch_httpd_external, handle_external_req, <<"kinosearch">>}
+
index 02f5c32..1724b67 100755 (executable)
@@ -25,6 +25,9 @@ my ( $url, $trigger_path ) = @ARGV;
 $url          ||= 'http://localhost:5984/monitor';
 $trigger_path ||= 'trigger/shell.pm' ;
 
+our $database = $1 if $url =~ m{/(\w+)/?$};
+
+sub commit { warn "# commit ignored\n"; }
 require $trigger_path if -e $trigger_path;
 
 my $seq = 0;
@@ -45,12 +48,9 @@ while( ! $error ) {
        $tx->res->body(sub{
                my ( $content, $body ) = @_;
 
-               debug 'BODY' => $body;
+               return if length($body) == 0; # empty chunk, heartbeat?
 
-               if ( length($body) == 0 ) {
-                       warn "# empty chunk, heartbeat?\n";
-                       return;
-               }
+               debug 'BODY' => $body;
 
                foreach ( split(/\r?\n/, $body) ) { # we can get multiple documents in one chunk
 
@@ -83,7 +83,7 @@ while( ! $error ) {
                                                                if ( $tx->res->code == 409 ) {
                                                                        info "TRIGGER ABORTED started on another worker? ", $tx->error;
                                                                } else {
-                                                                       info "ERROR ", $tx->error;
+                                                                       info "ERROR $url/$id ", $tx->error;
                                                                }
                                                        } else {
                                                                my $res = $tx->res->json;
@@ -97,7 +97,7 @@ while( ! $error ) {
                                                                $client->put( "$url/$id" => $json->encode( $change->{doc} ) => sub {
                                                                        my ($client,$tx) = @_;
                                                                        if ($tx->error) {
-                                                                               info "ERROR", $tx->error;
+                                                                               info "ERROR $url/$id", $tx->error;
                                                                        } else {
                                                                                my $res = $tx->res->json;
                                                                                $change->{doc}->{_rev} = $res->{rev};
@@ -114,6 +114,8 @@ while( ! $error ) {
 
                }
 
+               commit;
+
        });
        $client->start($tx);
 
index 584e6f5..2c24b05 100644 (file)
@@ -44,7 +44,10 @@ Ako želite, upišite svoju e-mail adresu:
        <input class="text1" name="casopis.naslov" ng:required>
        <a href="" title="Brisanje naslova." ng:click="upitnik.casopisi.$remove(casopis)">X</a>
 </div>
-<a href="" title="Dodavanje prostora za unos još jednog naslova." ng:click="upitnik.casopisi.$add({naslov:''})">dodaj još jedno polje</a>
+<a href="" title="Dodavanje prostora za unos još jednog naslova."
+       ng:click="upitnik.casopisi.$add({naslov:''})"
+       ng:show="upitnik.casopisi.$count() < 10"
+>dodaj još jedno polje</a>
 
 <h2>
 2. Odaberite i obrazložite svoj izbor:
diff --git a/trigger/KinoSearch.pm b/trigger/KinoSearch.pm
new file mode 100644 (file)
index 0000000..6866c35
--- /dev/null
@@ -0,0 +1,81 @@
+use KinoSearch::Index::Indexer;
+use KinoSearch::Plan::Schema;
+use KinoSearch::Analysis::PolyAnalyzer;
+use KinoSearch::Plan::FullTextType;
+
+# Create a Schema which defines index fields.
+my $schema = KinoSearch::Plan::Schema->new;
+my $polyanalyzer = KinoSearch::Analysis::PolyAnalyzer->new( 
+       language => 'en',
+);
+my $type = KinoSearch::Plan::FullTextType->new(
+       analyzer => $polyanalyzer,
+);
+my $blob_type = KinoSearch::Plan::BlobType->new( stored => 1 );
+my $string_type = KinoSearch::Plan::StringType->new;
+$schema->spec_field( name => '_id',   type => $string_type );
+$schema->spec_field( name => '_rev', type => $string_type );
+$schema->spec_field( name => 'doc', type => $blob_type );
+
+# Create the index and add documents.
+our $indexer;
+
+
+sub _indexer {
+       $indexer ||= KinoSearch::Index::Indexer->new(
+               schema => $schema,   
+               index  => "/tmp/kinosearch.$database", 
+               create => 1,
+       );
+};
+
+sub flatten {
+       my ($flat,$data,$prefix) = @_;
+       if ( ref $data eq '' ) {
+               $$flat->{$prefix} .= "\n" . $data;
+               $$flat->{$prefix} =~ s/^\n//; # strip first
+       } elsif ( ref $data eq 'HASH' ) {
+               foreach my $key ( keys %$data ) {
+                       my $full_prefix = $prefix ? $prefix . '.' : '';
+                       $full_prefix .= $key;
+                       flatten( $flat, $data->{$key}, $full_prefix );
+               }
+       } elsif ( ref $data eq 'ARRAY' ) {
+               foreach my $el ( @$data ) {
+                       flatten( $flat, $el, $prefix );
+               }
+       } elsif ( ref $data eq 'Mojo::JSON::_Bool' ) {
+               $$flat->{$prefix} = $data;
+       } else {
+               die "unsupported ",ref($data)," from ",dump($data);
+       }
+}
+
+sub filter {
+       my $change = shift;
+       my $doc = $change->{doc} || next;
+
+       _indexer->delete_by_term( field => '_id', term => $doc->{_id} );
+       return 0 if $doc->{_deleted};
+
+       my $flat;
+       flatten( \$flat, $doc, '' );
+       foreach my $field ( keys %$flat ) {
+               next if $schema->fetch_type($field);
+               $schema->spec_field( name => $field, type => $type );
+               warn "# +++ $field\n";
+       }
+       $flat->{doc} = $json->encode($doc);
+       warn "# add_doc ",dump($flat);
+       _indexer->add_doc($flat);
+       return 0;
+}
+
+sub commit {
+       return unless $indexer;
+       $indexer->commit;
+       undef $indexer;
+       warn "# commit index done\n";
+}
+
+1;