#!/usr/bin/perl use strict; use FCGI; use JSON; use DBI; use GD::Simple; use Data::Dumper; use warnings; $Data::Dumper::Sortkeys = 1; my $conf = load_conf("../etc/autodoc.json"); my $dbh = sqlconnect($conf->{sql}); my %map = ( api_v1_POST_documents => \&api_v1_POST_documents, api_v1_POST_documents_id_data => \&api_v1_POST_documents_id_data, api_v1_GET_documents_id_image => \&api_v1_GET_documents_id_image, api_v1_GET_pages_image => \&api_v1_GET_pages_image, api_v1_GET_documents => \&api_v1_GET_documents, api_v1_GET_documents_id => \&api_v1_GET_documents_id, api_v1_GET_pages_id => \&api_v1_GET_pages_id, api_v1_PATCH_documents_id => \&api_v1_PATCH_documents_id, ); my $request = FCGI::Request(); while($request->Accept() >= 0) { my $user = $ENV{REMOTE_USER} || 'undefined'; my $qs = parse_querystring($ENV{QUERY_STRING}); my $method = $ENV{REQUEST_METHOD}; # QS sanity check if ( exists $qs->{id} && $qs->{id} !~ /^\d+$/ ) { fatal_api_error(400,"invalid id"); } if ( exists $qs->{maxWidth} && $qs->{maxWidth} !~ /^\d+$/ ) { fatal_api_error(400,"invalid maxWidth"); } if ( exists $qs->{maxHeight} && $qs->{maxHeight} !~ /^\d+$/ ) { fatal_api_error(400,"invalid maxHeight"); } if ( exists $qs->{pageSize} && $qs->{pageSize} !~ /^\d+$/ ) { fatal_api_error(400,"invalid pageSize"); } if ( exists $qs->{pageIndex} && $qs->{pageIndex} !~ /^\d+$/ ) { fatal_api_error(400,"invalid pageIndex"); } my $path = [ split(/\//,$ENV{SCRIPT_NAME}) ] if exists $ENV{SCRIPT_NAME}; shift(@{$path}); my $post = parse_post( \*STDIN, exists $ENV{CONTENT_LENGTH} ? $ENV{CONTENT_LENGTH} : 0, exists $ENV{CONTENT_TYPE} ? $ENV{CONTENT_TYPE} : 0 ); my($code, $hdr, $type, $data) = process_query($method, $path, $qs, $post, $user); $hdr = [ ] if !defined $hdr; if ( defined $type ) { if ( $type eq 'application/json' ) { $data = to_json($data, { utf8 => 1, pretty => 1 }); } } send_response($code, $hdr, $type, $data); } sub send_response { my ($code, $hdr, $type, $data) = @_; printf("Status: %s\r\n", $code); printf("Content-type: %s\r\n", $type) if defined $type; printf("Content-length: %i\r\n", length($data)) if defined $data; foreach ( @{$hdr} ) { printf("%s\r\n",$_); } printf("\r\n"); print $data if defined $data; } sub fatal_api_error { my($code,$type,$body)=api_error(@_); send_response($code, undef, $type, $body); exit; } sub load_conf { my($file) = @_; my $x=''; open(F,"$file") || fatal_api_error(500,"Failed to load configuration file"); while() { $x.=$_; } close(F); return from_json($x); } sub process_query { my($method, $path, $qs, $post, $user) = @_; my ($api_version, $path_main, $path_id, $path_sub) = @{$path}; return api_error(404, "Unknown API version") if !defined $api_version; return api_error(405, "Unknown METHOD") if !defined $method; return api_error(404, "Unknown API function") if !defined $path_main; my $func = 'api_' . $api_version . '_' . $method . '_' . $path_main; $func .= '_id' if defined $path_id; $func .= '_'.$path_sub if defined $path_sub; return $map{$func}->($path_id, $qs, $post, $user) if exists $map{$func}; return api_error(404, "Invalid path/method combination"); } sub db_get_document_object { my($id) = @_; my $document; my @pages; my @pageids; my @tags; my $q = sqlquery($dbh, "SELECT * FROM documents WHERE id = ?", $id); while(my $hash = $q->fetchrow_hashref()) { $document = $hash; } $q = sqlquery($dbh, "SELECT * FROM pages WHERE documentId = ?", $id); while(my $hash = $q->fetchrow_hashref()) { push @pages, $hash; push @pageids, $hash->{id}; } $q = sqlquery($dbh, " SELECT tags.tag AS tag FROM documents_tags LEFT JOIN tags ON documents_tags.tagId = tags.id WHERE documentId = ? ORDER BY tag", $id); while(my ($tag) = $q->fetchrow_array()) { push @tags, $tag; } my %lang; foreach my $page ( @pages ) { $q = sqlquery($dbh, "SELECT * FROM pages_lang WHERE pageId = ?", $page->{id}); while(my $hash = $q->fetchrow_hashref()) { $lang{$hash->{language}}++; } } if ( defined $document ) { my $out = { id => $document->{id}, pageId => [ @pageids ], name => $document->{name}, created => $document->{created}, owner => $document->{owner}, status => $document->{status}, languages => [ keys %lang ], tags => [ @tags ], }; return (200, "application/json", $out); } return (404, undef, "application/json", ""); } sub db_get_page_object { my($id) = @_; my $out; my %lang; my $q = sqlquery($dbh, "SELECT * FROM pages_lang WHERE pageId = ?", $id); while(my $hash = $q->fetchrow_hashref()) { $lang{$hash->{language}}++; } $q = sqlquery($dbh, "SELECT * FROM pages WHERE documentId = ?", $id); while(my $hash = $q->fetchrow_hashref()) { $out = { id => $id, documentId => $hash->{documentId}, name => $hash->{name}, created => $hash->{created}, owner => $hash->{owner}, status => $hash->{status}, language => [ keys %lang ], }; } if ( exists $out->{id} ) { return (200, "application/json", $out); } return (404, undef, "application/json", ""); } sub get_page_image { my($id, $qs) = @_; my $wh; my $size; if ( exists $qs->{maxWidth} ) { $wh = 'w'; $size = $qs->{maxWidth}; } elsif ( exists $qs->{maxHeight} ) { $wh = 'h'; $size = $qs->{maxWidth}; } my $src; my $dst; if ( !defined $id ) { $src = sprintf("%s/%s", $conf->{path}{global}, $conf->{path}{error_img}); } else { $src = sprintf("%s/%s/%s", $conf->{path}{global}, $conf->{path}{original}, $id); } if ( defined $wh && defined $size ) { if ( defined $id ) { $dst = sprintf("%s/%s/%s-%s-%s.jpeg", $conf->{path}{global}, $conf->{path}{cache}, $id, $wh, $size); } else { $dst = sprintf("%s/%s/error_img-%s-%s.jpeg", $conf->{path}{global}, $conf->{path}{cache}, $wh, $size); } } my $imgfile; if ( !defined $dst ) { $imgfile = $src; } else { if ( ! -r $dst ) { my $cmd = sprintf("convert %s -resize %s%s %s", $src, $wh eq 'h' ? 'x' : '', $size, $dst ); print STDERR "CMD=$cmd\n"; system($cmd); } $imgfile = $dst; } my $url = $dst; $url =~ s/$conf->{path}{global}//; return $url; #my $img = ''; #open(IMG, $imgfile) || fatal_api_error(500,"Failed to generate image"); #binmode(IMG); #while() { $img.=$_; } #close(IMG); #return $img; } # create an empty document object. sub api_v1_POST_documents { my($id, $qs, $post, $user) = @_; my $q = sqlquery($dbh, " INSERT INTO documents SET owner = ?, status = 'nodata' ", $user); $q = sqlquery($dbh, "SELECT LAST_INSERT_ID()"); while(my($lastid) = $q->fetchrow_array()) { $id = $lastid; } return db_get_document_object($id); } sub api_v1_POST_documents_id_data { my($id, $qs, $post, $user) = @_; my $pageid; sqlquery($dbh, " INSERT INTO pages SET owner = ?, documentId = ?, contenttype = ?, created = NOW(), status = 'inprogress'", $user, $id, $post->{ctype}); my $q = sqlquery($dbh, "SELECT LAST_INSERT_ID()"); while(my($last) = $q->fetchrow_array()) { $pageid = $last; } my $file = $conf->{path}{global} . '/' . $conf->{path}{originals} . '/' . $pageid; open(F,'>'.$file); print F $post->{data}; close(F); return (200, undef, "application/json", { }) } # get document image/thumbnail sub api_v1_GET_documents_id_image { my($id, $qs, $post, $user) = @_; my $pageid; my $q = sqlquery($dbh, "SELECT id FROM pages WHERE documentId = ? ORDER BY id DESC LIMIT 1", $id); while(my($myid)=$q->fetchrow_array()) { $pageid = $myid; } return api_v1_GET_pages_id_image($pageid, $qs, $post, $user); } # get page image/thumbnail sub api_v1_GET_pages_id_image { my($id, $qs, $post, $user) = @_; #return (200, "image/jpeg", get_page_image($id, $qs)); my $url = get_page_image($id, $qs); return (302, [ "Location: $url" ]); } # get a list of document objects sub api_v1_GET_documents { my($id, $qs, $post, $user) = @_; $qs->{pageSize} = $conf->{query}{pageSize} if !exists $qs->{pageSize}; $qs->{pageIndex} = $conf->{query}{pageIndex} if !exists $qs->{pageIndex}; my @out; my $q = sqlquery($dbh, "SELECT id FROM documents LIMIT ?,?", $qs->{pageSize} * $qs->{pageIndex}, $qs->{pageSize}); while(my ($id) = $q->fetchrow_array()) { my ($code, $ct, $body) = db_get_document_object($id); push @out, $body; } return ( 200, undef, "application/json", \@out ); } # get a single document object sub api_v1_GET_documents_id { my($id, $qs, $post, $user) = @_; return db_get_document_object($id); } # get a specific page sub api_v1_GET_pages_id { my($id, $qs, $post, $user) = @_; return db_get_page_object($id); } # change document properties sub api_v1_PATCH_documents_id { my($id, $qs, $post, $user) = @_; if ( exists $qs->{addTags} ) { my $tags = get_array($qs->{addTags}); foreach my $tag ( @{$tags} ) { sqlquery($dbh, "CALL tag_add(?,?)", $id, $tag); } } if ( exists $qs->{deleteTags} ) { my $tags = get_array($qs->{deleteTags}); foreach my $tag ( @{$tags} ) { sqlquery($dbh, "CALL tag_del(?,?)", $id, $tag); } } if ( exists $qs->{name} ) { sqlquery($dbh, "UPDATE documents SET name = ? WHERE id = ?", $qs->{name}, $id); } return (200); } sub get_array { my($x) = @_; my @arr; if ( ref $x eq 'ARRAY' ) { @arr = @{$x}; } else { @arr = [ $x ]; } return \@arr; } sub api_error { my($code, $text)=@_; my %deftext = ( "000" => "An error produced an internal error, cascading errors over errors", "404" => "No such API path" ); $code = "000" if !defined $code; $text = $deftext{$code} if ( !defined $text || $code eq "000" ); return ( $code, undef, "text/plain", $text . "\r\n" ); } sub parse_querystring { my($in) = @_; my %out; if ( defined $in && length $in ) { foreach(split(/&/,$in)) { my($name,$value) = split(/=/); $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg; # handle arrays if ( exists $out{$name} ) { if ( ! ref $out{$name} ) { my $old = $out{$name}; $out{$name} = [ $old ]; } push @{$out{$name}}, $value; } else { $out{$name}=$value; } } } return \%out; } sub parse_post { my($fd, $len, $ct) = @_; my $data = ''; while ( $len > 0 ) { my $buf; my $read = read($fd, $buf, $len); $len -= $read; $data .= $buf; } if ( $ct eq 'application/json' ) { my $tmp = from_json($data); $data = $tmp; } return { ctype => $ct, len => $len, data => $data}; } sub sqlconnect { my($sql) = @_; my $dsn = "DBI:mysql:database=$sql->{base};host=$sql->{host}"; my $dbh = DBI->connect($dsn, $sql->{user}, $sql->{pass}) || \\ fatal_api_error(500,"Failed to connect to database"); return $dbh; } sub sqlquery { my $dbh = shift; my $query = shift; my @args = @_; my $sth = $dbh->prepare($query) || fatal_api_error(500,"Failed to execute SQL query"); $sth->execute(@args) || fatal_api_error(500,"Failed to execute SQL query"); return $sth; }