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.
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:
- 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
--- /dev/null
+#!/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">>}
+
$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;
$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
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;
$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};
}
+ commit;
+
});
$client->start($tx);
<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:
--- /dev/null
+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;