| Akron | 0e1ed24 | 2018-10-11 13:22:00 +0200 | [diff] [blame] | 1 | #!/usr/bin/env perl | 
 | 2 | use Mojolicious::Lite; | 
 | 3 | use Mojo::ByteStream 'b'; | 
 | 4 | use Mojo::Date; | 
 | 5 | use Mojo::JSON qw/true false encode_json decode_json/; | 
 | 6 | use strict; | 
 | 7 | use warnings; | 
 | 8 | use Mojo::JWT; | 
| Akron | 6d49c1f | 2018-10-11 14:22:21 +0200 | [diff] [blame] | 9 | use Mojo::File qw/path/; | 
 | 10 | use Mojo::Util qw/slugify/; | 
| Akron | 0e1ed24 | 2018-10-11 13:22:00 +0200 | [diff] [blame] | 11 |  | 
 | 12 | # This is an API fake server with fixtures | 
 | 13 |  | 
 | 14 | my $secret = 's3cr3t'; | 
| Akron | 6d49c1f | 2018-10-11 14:22:21 +0200 | [diff] [blame] | 15 | my $fixture_path = path(Mojo::File->new(__FILE__)->dirname); | 
| Akron | 0e1ed24 | 2018-10-11 13:22:00 +0200 | [diff] [blame] | 16 |  | 
 | 17 | helper jwt_encode => sub { | 
 | 18 |   shift; | 
 | 19 |   return Mojo::JWT->new( | 
 | 20 |     secret => $secret, | 
 | 21 |     token_type => 'api_token', | 
 | 22 |     expires => time + (3 * 34 * 60 * 60), | 
 | 23 |     claims => { @_ } | 
 | 24 |   ); | 
 | 25 | }; | 
 | 26 |  | 
 | 27 | helper jwt_decode => sub { | 
 | 28 |   my ($c, $auth) = @_; | 
 | 29 |   $auth =~ s/\s*api_token\s+//; | 
 | 30 |   return Mojo::JWT->new(secret => $secret)->decode($auth); | 
 | 31 | }; | 
 | 32 |  | 
 | 33 |  | 
| Akron | 6d49c1f | 2018-10-11 14:22:21 +0200 | [diff] [blame] | 34 | # Load fixture responses | 
 | 35 | helper 'load_response' => sub { | 
 | 36 |   my $c = shift; | 
 | 37 |   my $q_name = shift; | 
 | 38 |   my $file = $fixture_path->child("response_$q_name.json"); | 
 | 39 |   unless (-f $file) { | 
 | 40 |     return { | 
 | 41 |       status => 500, | 
 | 42 |       json => { | 
 | 43 |         errors => [[0, 'Unable to load query response from ' . $file]] | 
 | 44 |       } | 
 | 45 |     } | 
 | 46 |   }; | 
 | 47 |   my $response = $file->slurp; | 
 | 48 |   return decode_json($response); | 
 | 49 | }; | 
 | 50 |  | 
 | 51 |  | 
| Akron | 0e1ed24 | 2018-10-11 13:22:00 +0200 | [diff] [blame] | 52 | # Base page | 
 | 53 | get '/' => sub { | 
| Akron | 6d49c1f | 2018-10-11 14:22:21 +0200 | [diff] [blame] | 54 |   shift->render(text => 'Fake server available'); | 
| Akron | 0e1ed24 | 2018-10-11 13:22:00 +0200 | [diff] [blame] | 55 | }; | 
 | 56 |  | 
| Akron | 3239663 | 2018-10-11 17:08:37 +0200 | [diff] [blame] | 57 |  | 
| Akron | 0e1ed24 | 2018-10-11 13:22:00 +0200 | [diff] [blame] | 58 | # Search fixtures | 
 | 59 | get '/search' => sub { | 
 | 60 |   my $c = shift; | 
 | 61 |   my $v = $c->validation; | 
 | 62 |   $v->optional('q'); | 
 | 63 |   $v->optional('page'); | 
 | 64 |   $v->optional('ql'); | 
 | 65 |   $v->optional('count'); | 
 | 66 |   $v->optional('context'); | 
 | 67 |  | 
| Akron | 3239663 | 2018-10-11 17:08:37 +0200 | [diff] [blame] | 68 |   $c->app->log->debug('Receive request'); | 
 | 69 |  | 
| Akron | 0e1ed24 | 2018-10-11 13:22:00 +0200 | [diff] [blame] | 70 |   # Response q=x&ql=cosmas3 | 
 | 71 |   if ($v->param('ql') && $v->param('ql') eq 'cosmas3') { | 
 | 72 |     return $c->render( | 
 | 73 |       status => 400, | 
 | 74 |       json => { | 
 | 75 |         "\@context" => "http://korap.ids-mannheim.de/ns/koral/0.3/context.jsonld", | 
 | 76 |         "errors" => [[307,"cosmas3 is not a supported query language!"]] | 
 | 77 |       }); | 
 | 78 |   }; | 
 | 79 |  | 
| Akron | 6d49c1f | 2018-10-11 14:22:21 +0200 | [diff] [blame] | 80 |   if (!$v->param('q')) { | 
 | 81 |     return $c->render(%{$c->load_response('no_query')}); | 
| Akron | 0e1ed24 | 2018-10-11 13:22:00 +0200 | [diff] [blame] | 82 |   }; | 
 | 83 |  | 
| Akron | 6d49c1f | 2018-10-11 14:22:21 +0200 | [diff] [blame] | 84 |   # Get response based on query parameter | 
 | 85 |   my $response = $c->load_response(slugify($v->param('q'))); | 
| Akron | 0e1ed24 | 2018-10-11 13:22:00 +0200 | [diff] [blame] | 86 |  | 
 | 87 |   # Check authentification | 
 | 88 |   if (my $auth = $c->req->headers->header('Authorization')) { | 
 | 89 |     if (my $jwt = $c->jwt_decode($auth)) { | 
| Akron | 6d49c1f | 2018-10-11 14:22:21 +0200 | [diff] [blame] | 90 |       $response->{json}->{meta}->{authorized} = $jwt->{username} if $jwt->{username}; | 
| Akron | 0e1ed24 | 2018-10-11 13:22:00 +0200 | [diff] [blame] | 91 |     }; | 
 | 92 |   }; | 
 | 93 |  | 
| Akron | 6d49c1f | 2018-10-11 14:22:21 +0200 | [diff] [blame] | 94 |   # Set page parameter | 
| Akron | 0e1ed24 | 2018-10-11 13:22:00 +0200 | [diff] [blame] | 95 |   if ($v->param('page')) { | 
| Akron | 6d49c1f | 2018-10-11 14:22:21 +0200 | [diff] [blame] | 96 |     $response->{json}->{meta}->{startIndex} = $v->param("startIndex"); | 
| Akron | 0e1ed24 | 2018-10-11 13:22:00 +0200 | [diff] [blame] | 97 |   }; | 
 | 98 |  | 
| Akron | 0e1ed24 | 2018-10-11 13:22:00 +0200 | [diff] [blame] | 99 |   # Simple search fixture | 
| Akron | 3239663 | 2018-10-11 17:08:37 +0200 | [diff] [blame] | 100 |   $c->render(%$response); | 
 | 101 |  | 
 | 102 |   $c->app->log->debug('Rendered result'); | 
 | 103 |  | 
 | 104 |   return 1; | 
| Akron | 0e1ed24 | 2018-10-11 13:22:00 +0200 | [diff] [blame] | 105 | }; | 
 | 106 |  | 
 | 107 |  | 
| Akron | b80341d | 2018-10-15 19:46:23 +0200 | [diff] [blame] | 108 | # Matchinfo fixtures | 
 | 109 | get '/corpus/:corpusId/:docId/:textId/:matchId/matchInfo' => sub { | 
 | 110 |   my $c = shift; | 
 | 111 |  | 
 | 112 |   my $file = join('_', ( | 
 | 113 |     'matchinfo', | 
 | 114 |     $c->stash('corpusId'), | 
 | 115 |     $c->stash('docId'), | 
 | 116 |     $c->stash('textId'), | 
 | 117 |     $c->stash('matchId') | 
 | 118 |   )); | 
 | 119 |  | 
| Akron | b8d0b40 | 2018-10-18 23:51:52 +0200 | [diff] [blame^] | 120 |   my $slug = slugify($file); | 
 | 121 |  | 
| Akron | b80341d | 2018-10-15 19:46:23 +0200 | [diff] [blame] | 122 |   # Get response based on query parameter | 
| Akron | b8d0b40 | 2018-10-18 23:51:52 +0200 | [diff] [blame^] | 123 |   my $response = $c->load_response($slug); | 
| Akron | b80341d | 2018-10-15 19:46:23 +0200 | [diff] [blame] | 124 |   return $c->render(%$response); | 
 | 125 | }; | 
 | 126 |  | 
| Akron | 0e1ed24 | 2018-10-11 13:22:00 +0200 | [diff] [blame] | 127 |  | 
 | 128 | ############ | 
 | 129 | # Auth API # | 
 | 130 | ############ | 
 | 131 |  | 
 | 132 | # Request API token | 
 | 133 | get '/auth/logout' => sub { | 
 | 134 |   my $c = shift; | 
 | 135 |  | 
 | 136 |   if (my $auth = $c->req->headers->header('Authorization')) { | 
 | 137 |     if (my $jwt = $c->jwt_decode($auth)) { | 
 | 138 |       my $user = $jwt->{username} if $jwt->{username}; | 
 | 139 |  | 
 | 140 |       $c->app->log->debug('Server-Logout: ' . $user); | 
 | 141 |       return $c->render(json => { msg => [[0, 'Fine!']]}); | 
 | 142 |     }; | 
 | 143 |   }; | 
 | 144 |  | 
 | 145 |   return $c->render(status => 400, json => { error => [[0, 'No!']]}); | 
 | 146 | }; | 
 | 147 |  | 
 | 148 |  | 
 | 149 | # Request API token | 
 | 150 | get '/auth/apiToken' => sub { | 
 | 151 |   my $c = shift; | 
 | 152 |  | 
 | 153 |   # Get auth header | 
 | 154 |   my $auth = $c->req->headers->authorization; | 
 | 155 |  | 
 | 156 |   # Authorization missing or not basic | 
 | 157 |   if (!$auth || $auth !~ s/\s*Basic\s+//gi) { | 
 | 158 |     return $c->render( | 
 | 159 |       json => { | 
 | 160 |         error => [[2, 'x']] | 
 | 161 |       } | 
 | 162 |     ); | 
 | 163 |   }; | 
 | 164 |  | 
 | 165 |   # Decode header | 
 | 166 |   my ($username, $pwd) = @{b($auth)->b64_decode->split(':')->to_array}; | 
 | 167 |  | 
 | 168 |   # the password is 'pass' | 
 | 169 |   if ($pwd) { | 
 | 170 |  | 
 | 171 |     # the password is 'pass' | 
 | 172 |     if ($pwd eq 'pass') { | 
 | 173 |  | 
 | 174 |       # Render info with token | 
 | 175 |       my $jwt = $c->jwt_encode(username => $username); | 
 | 176 |  | 
 | 177 |       # Render in the Kustvakt fashion: | 
 | 178 |       return $c->render( | 
 | 179 |         format => 'html', | 
 | 180 |         text => encode_json({ | 
 | 181 |           %{$jwt->claims}, | 
 | 182 |           expires    => $jwt->expires, | 
 | 183 |           token      => $jwt->encode, | 
 | 184 |           token_type => 'api_token' | 
 | 185 |         }) | 
 | 186 |       ); | 
 | 187 |     }; | 
 | 188 |  | 
 | 189 |     return $c->render( | 
 | 190 |       json => { | 
 | 191 |         error => [[2004, undef]] | 
 | 192 |       } | 
 | 193 |     ); | 
 | 194 |   }; | 
 | 195 |  | 
 | 196 |   return $c->render( | 
 | 197 |     json => { | 
 | 198 |       error => [[2004, undef]] | 
 | 199 |     } | 
 | 200 |   ); | 
 | 201 | }; | 
 | 202 |  | 
 | 203 | app->start; | 
 | 204 |  | 
 | 205 |  | 
 | 206 | __END__ | 
 | 207 |  | 
 | 208 |  | 
 | 209 |   # Temporary: | 
 | 210 |   my $collection_query = { | 
 | 211 |     '@type' => "koral:docGroup", | 
 | 212 |     "operation" => "operation:or", | 
 | 213 |     "operands" => [ | 
 | 214 |       { | 
 | 215 | 	'@type' => "koral:docGroup", | 
 | 216 | 	"operation" => "operation:and", | 
 | 217 | 	"operands" => [ | 
 | 218 | 	  { | 
 | 219 | 	    '@type' => "koral:doc", | 
 | 220 | 	    "key" => "title", | 
 | 221 | 	    "match" => "match:eq", | 
 | 222 | 	    "value" => "Der Birnbaum", | 
 | 223 | 	    "type" => "type:string" | 
 | 224 | 	  }, | 
 | 225 | 	  { | 
 | 226 | 	    '@type' => "koral:doc", | 
 | 227 | 	    "key" => "pubPlace", | 
 | 228 | 	    "match" => "match:eq", | 
 | 229 | 	    "value" => "Mannheim", | 
 | 230 | 	    "type" => "type:string" | 
 | 231 | 	  }, | 
 | 232 | 	  { | 
 | 233 | 	    '@type' => "koral:docGroup", | 
 | 234 | 	    "operation" => "operation:or", | 
 | 235 | 	    "operands" => [ | 
 | 236 | 	      { | 
 | 237 | 		'@type' => "koral:doc", | 
 | 238 | 		"key" => "subTitle", | 
 | 239 | 		"match" => "match:eq", | 
 | 240 | 		"value" => "Aufzucht oder Pflege", | 
 | 241 | 		"type" => "type:string" | 
 | 242 | 	      }, | 
 | 243 | 	      { | 
 | 244 | 		'@type' => "koral:doc", | 
 | 245 | 		"key" => "subTitle", | 
 | 246 | 		"match" => "match:eq", | 
 | 247 | 		"value" => "Gedichte", | 
 | 248 | 		"type" => "type:string" | 
 | 249 | 	      } | 
 | 250 | 	    ] | 
 | 251 | 	  } | 
 | 252 | 	] | 
 | 253 |       }, | 
 | 254 |       { | 
 | 255 | 	'@type' => "koral:doc", | 
 | 256 | 	"key" => "pubDate", | 
 | 257 | 	"match" => "match:geq", | 
 | 258 | 	"value" => "2015-03-05", | 
 | 259 | 	"type" => "type:date" | 
 | 260 |       } | 
 | 261 |     ] | 
 | 262 |   }; |