RESTful API

Vessel positions search works according to specifications and responses are made in json, csv and xml. Results are paginated (see config/app). For the correct responses I created an ApiResponder class (see app/Lib). ApiResponder subclasses have their own implementation using different packages for CSV and XML to demostrate a basic Adapter Design Pattern (ApiResponderJson works with native Laravel response object). A new adapter/driver can be easily created by extending the abstract class and implementing the abstract response method.

Sample CRUD operations were made for an imaginary Vessels entity (just a test table) and in json format only (resource controller).


MySQL file

Download here.


PHP Docs

Basic generated PHP Docs can be found in /phpdocs folder.


Unit and Feature Tests

Basic unit and feature/integration tests are written in tests folder for all controllers/models.

C:\xampp\htdocs\test\laravel-mt-291017>phpunit
PHPUnit 5.7.23 by Sebastian Bergmann and contributors.

..............                                                    14 / 14 (100%)

Time: 1.85 seconds, Memory: 18.00MB

OK (14 tests, 32 assertions)

Routes

Below is a list of the registered routes:

+-----------+---------------------------+------------------+------------------------------------------------------------------------+--------------+
| Method    | URI                       | Name             | Action                                                                 | Middleware   |
+-----------+---------------------------+------------------+------------------------------------------------------------------------+--------------+
| GET|HEAD  | /                         |                  | App\Http\Controllers\IndexController@index                             | web          |
| GET|HEAD  | api/positions/search      |                  | App\Http\Controllers\Api\PositionController@search                     | api,auth:api |
| GET|HEAD  | api/user                  |                  | Closure                                                                | api,auth:api |
| POST      | api/vessels               | vessels.store    | App\Http\Controllers\Api\VesselController@store                        | api,auth:api |
| GET|HEAD  | api/vessels               | vessels.index    | App\Http\Controllers\Api\VesselController@index                        | api,auth:api |
| GET|HEAD  | api/vessels/create        | vessels.create   | App\Http\Controllers\Api\VesselController@create                       | api,auth:api |
| DELETE    | api/vessels/{vessel}      | vessels.destroy  | App\Http\Controllers\Api\VesselController@destroy                      | api,auth:api |
| GET|HEAD  | api/vessels/{vessel}      | vessels.show     | App\Http\Controllers\Api\VesselController@show                         | api,auth:api |
| PUT|PATCH | api/vessels/{vessel}      | vessels.update   | App\Http\Controllers\Api\VesselController@update                       | api,auth:api |
| GET|HEAD  | api/vessels/{vessel}/edit | vessels.edit     | App\Http\Controllers\Api\VesselController@edit                         | api,auth:api |
+-----------+---------------------------+------------------+------------------------------------------------------------------------+--------------+

Postman test cases (or use directly the browser)

- Without api_token error "Unauthenticated" with correct output format using ApiResponder without ugly if/else - see \App\Exceptions\Handler::unauthenticated()

  • https://dev-laravel-mt-291017.hallo.gr/api/positions/search
  • https://dev-laravel-mt-291017.hallo.gr/api/positions/search?format=json
  • https://dev-laravel-mt-291017.hallo.gr/api/positions/search?format=csv
  • https://dev-laravel-mt-291017.hallo.gr/api/positions/search?format=xml

- Authenticated requests (use api_token=UTUQNqMQkKyHUU4HXntrlCxrNfyB6rduuMLC0DCAKbIXbnZqihXFTmxGt3D7NLmD)

Search positions without filters

  • https://dev-laravel-mt-291017.hallo.gr/api/positions/search?api_token=UTUQNqMQkKyHUU4HXntrlCxrNfyB6rduuMLC0DCAKbIXbnZqihXFTmxGt3D7NLmD&format=json
  • https://dev-laravel-mt-291017.hallo.gr/api/positions/search?api_token=UTUQNqMQkKyHUU4HXntrlCxrNfyB6rduuMLC0DCAKbIXbnZqihXFTmxGt3D7NLmD&format=csv
  • https://dev-laravel-mt-291017.hallo.gr/api/positions/search?api_token=UTUQNqMQkKyHUU4HXntrlCxrNfyB6rduuMLC0DCAKbIXbnZqihXFTmxGt3D7NLmD&format=xml

Search positions with filter_mmsi (array - for multiple use filter_mmsi[]=aaa&filter_mmsi[]=bbb)

  • https://dev-laravel-mt-291017.hallo.gr/api/positions/search?filter_mmsi[]=311486000&api_token=UTUQNqMQkKyHUU4HXntrlCxrNfyB6rduuMLC0DCAKbIXbnZqihXFTmxGt3D7NLmD&format=json
  • https://dev-laravel-mt-291017.hallo.gr/api/positions/search?filter_mmsi[]=311486000&api_token=UTUQNqMQkKyHUU4HXntrlCxrNfyB6rduuMLC0DCAKbIXbnZqihXFTmxGt3D7NLmD&format=csv
  • https://dev-laravel-mt-291017.hallo.gr/api/positions/search?filter_mmsi[]=311486000&api_token=UTUQNqMQkKyHUU4HXntrlCxrNfyB6rduuMLC0DCAKbIXbnZqihXFTmxGt3D7NLmD&format=xml

Search positions with filter_lat_from / filter_lat_to (numeric - use dot for decimal)

  • https://dev-laravel-mt-291017.hallo.gr/api/positions/search?filter_lat_from=40&filter_lat_to=40.6&api_token=UTUQNqMQkKyHUU4HXntrlCxrNfyB6rduuMLC0DCAKbIXbnZqihXFTmxGt3D7NLmD&format=json
  • https://dev-laravel-mt-291017.hallo.gr/api/positions/search?filter_lat_from=40&filter_lat_to=40.6&api_token=UTUQNqMQkKyHUU4HXntrlCxrNfyB6rduuMLC0DCAKbIXbnZqihXFTmxGt3D7NLmD&format=csv
  • https://dev-laravel-mt-291017.hallo.gr/api/positions/search?filter_lat_from=40&filter_lat_to=40.6&api_token=UTUQNqMQkKyHUU4HXntrlCxrNfyB6rduuMLC0DCAKbIXbnZqihXFTmxGt3D7NLmD&format=xml

Search positions with filter_lot_from / filter_lot_to (numeric - use dot for decimal)

  • https://dev-laravel-mt-291017.hallo.gr/api/positions/search?filter_lon_from=11&filter_lon_to=12.56&api_token=UTUQNqMQkKyHUU4HXntrlCxrNfyB6rduuMLC0DCAKbIXbnZqihXFTmxGt3D7NLmD&format=json
  • https://dev-laravel-mt-291017.hallo.gr/api/positions/search?filter_lon_from=11&filter_lon_to=12.56&api_token=UTUQNqMQkKyHUU4HXntrlCxrNfyB6rduuMLC0DCAKbIXbnZqihXFTmxGt3D7NLmD&format=csv
  • https://dev-laravel-mt-291017.hallo.gr/api/positions/search?filter_lon_from=11&filter_lon_to=12.56&api_token=UTUQNqMQkKyHUU4HXntrlCxrNfyB6rduuMLC0DCAKbIXbnZqihXFTmxGt3D7NLmD&format=xml

Search positions with filter_timestamp_from / filter_timestamp_to (string - any valid date/datetime in mysql format YYYY-MM-DD HH:MM:SS)

  • https://dev-laravel-mt-291017.hallo.gr/api/positions/search?filter_timestamp_from=2017-02-18 10&filter_timestamp_to=2017-02-18 11:55:44&api_token=UTUQNqMQkKyHUU4HXntrlCxrNfyB6rduuMLC0DCAKbIXbnZqihXFTmxGt3D7NLmD&format=json
  • https://dev-laravel-mt-291017.hallo.gr/api/positions/search?filter_timestamp_from=2017-02-18 10&filter_timestamp_to=2017-02-18 11:55:44&api_token=UTUQNqMQkKyHUU4HXntrlCxrNfyB6rduuMLC0DCAKbIXbnZqihXFTmxGt3D7NLmD&format=csv
  • https://dev-laravel-mt-291017.hallo.gr/api/positions/search?filter_timestamp_from=2017-02-18 10&filter_timestamp_to=2017-02-18 11:55:44&api_token=UTUQNqMQkKyHUU4HXntrlCxrNfyB6rduuMLC0DCAKbIXbnZqihXFTmxGt3D7NLmD&format=xml

Any combination that makes sense.. For example...

  • https://dev-laravel-mt-291017.hallo.gr/api/positions/search?filter_mmsi[]=311486000&filter_timestamp_from=2017-02-18 11&filter_timestamp_to=2017-02-18 12&api_token=UTUQNqMQkKyHUU4HXntrlCxrNfyB6rduuMLC0DCAKbIXbnZqihXFTmxGt3D7NLmD&format=json

Rate Limit

For rate limit functionality, Laravel's default api middleware is used. The only change made was in Kernel.php configuration: throttle:10,60 (10 requests per 60 minutes).

Headers (normal operation)

Cache-Control: no-cache, private
Connection: Keep-Alive
Content-Type: application/xml
Date: Wed, 01 Nov 2017 01:19:21 GMT
Keep-Alive: timeout=5, max=96
Server: Apache/2.4.23 (Win32) OpenSSL/1.0.2h PHP/5.6.31
Transfer-Encoding: chunked
X-Powered-By: PHP/5.6.31
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 3

Headers (limit reached)

Cache-Control: no-cache, private
Connection: Keep-Alive
Content-Length: 18
Content-Type: text/html; charset=UTF-8
Date: Wed, 01 Nov 2017 01:20:36 GMT
Keep-Alive: timeout=5, max=95
Retry-After: 3600
Server: Apache/2.4.23 (Win32) OpenSSL/1.0.2h PHP/5.6.31
X-Powered-By: PHP/5.6.31
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1509502836