#!/usr/bin/perl use strict; use FCGI; use JSON; use DBI; 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}; 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, $type, $data) = process_query($method, $path, $qs, $post, $user); if ( $type eq 'application/json' ) { $data = to_json($data); } printf("Status: %s\r\n", $code); printf("Content-type: %s\r\n", $type); printf("Content-length: %i\r\n", length($data)); printf("\r\n"); print $data; } 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}; my $func = sprintf("api_%s_%s_%s", defined $api_version ? $api_version : "unknown", defined $method ? $method : "unknown", defined $path_main ? $path_main : "unknown" ); $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); } sub db_get_document_object { my($id) = @_; my $document; my @pages; my @pageids; 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}; } 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}}++; } } my $out = { id => $document->{id}, pageId => [ @pageids ], name => $document->{name}, created => $document->{created}, owner => $document->{owner}, status => $document->{status}, languages => [ keys %lang ], }; return (200, "application/json", $out); } # 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 { return api_error(501,"Not yet implemented"); } sub api_v1_GET_documents_id_image { return api_error(501,"Not yet implemented"); } sub api_v1_GET_pages_image { return api_error(501,"Not yet implemented"); } # 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, "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); } sub api_v1_GET_pages_id { return api_error(501,"Not yet implemented"); } sub api_v1_PATCH_documents_id { return api_error(501,"Not yet implemented"); } sub fatal_api_error { my($code,$type,$body)=api_error(@_); printf("Status: %s\r\n", $code); printf("Content-type: %s\r\n", $type); printf("Content-length: %i\r\n", length($body)); printf("\r\n"); print $body; exit; } 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, "text/plain", $text ); } 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; $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 $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; }