* API method to create a new time-limited shared link * API method for retrieving the current location given a shared link token * map view that refreshes the current location every 5 secondspull/34/head
@ -0,0 +1,48 @@ | |||
<?php | |||
namespace App\Http\Controllers; | |||
use Laravel\Lumen\Routing\Controller as BaseController; | |||
use Illuminate\Http\Request; | |||
use DB; | |||
class Share extends BaseController | |||
{ | |||
private function _databaseFromToken($token) { | |||
$share = DB::table('shares') | |||
->where('token', $token) | |||
->where('expires_at', '>', time()) | |||
->first(); | |||
if(!$share) return false; | |||
$database = DB::table('databases')->where('id', $share->database_id)->first(); | |||
return $database; | |||
} | |||
public function view(Request $request, $token) { | |||
$database = $this->_databaseFromToken($token); | |||
if(!$database) { | |||
return view('share-expired'); | |||
} | |||
return view('share', [ | |||
'database' => $database, | |||
'share_token' => $token, | |||
]); | |||
} | |||
public function current_location(Request $request) { | |||
$database = $this->_databaseFromToken($request->input('token')); | |||
$response = [ | |||
'data' => json_decode($database->last_location), | |||
]; | |||
return response(json_encode($response))->header('Content-Type', 'application/json'); | |||
} | |||
} |
@ -0,0 +1,33 @@ | |||
<?php | |||
use Illuminate\Database\Schema\Blueprint; | |||
use Illuminate\Database\Migrations\Migration; | |||
class Shares extends Migration | |||
{ | |||
/** | |||
* Run the migrations. | |||
* | |||
* @return void | |||
*/ | |||
public function up() | |||
{ | |||
Schema::create('shares', function (Blueprint $table) { | |||
$table->increments('id'); | |||
$table->unsignedInteger('database_id'); | |||
$table->datetime('expires_at')->nullable(); | |||
$table->string('token', 30); | |||
$table->datetime('created_at'); | |||
}); | |||
} | |||
/** | |||
* Reverse the migrations. | |||
* | |||
* @return void | |||
*/ | |||
public function down() | |||
{ | |||
Schema::drop('shares'); | |||
} | |||
} |
@ -0,0 +1,12 @@ | |||
html, body { | |||
height: 100%; | |||
} | |||
#map { | |||
height: 100%; | |||
} | |||
/* Move the map zoom controls away from the Compass logo */ | |||
.leaflet-top .leaflet-control-zoom { | |||
margin-top: 60px; | |||
margin-left: 26px; | |||
} |
@ -0,0 +1,60 @@ | |||
var map = L.map('map', { zoomControl: false }).setView([45.516, -122.660], 14, null, null, 24); | |||
L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token={accessToken}', { | |||
attribution: 'Map data © <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="https://www.mapbox.com/">Mapbox</a>', | |||
maxZoom: 18, | |||
id: 'mapbox.streets', | |||
accessToken: 'pk.eyJ1IjoiYWFyb25wayIsImEiOiI1T0tpNjdzIn0.OQjXyI3xSt8Dj8na3l90Sg' | |||
}).addTo(map); | |||
new L.Control.Zoom({ position: 'topleft' }).addTo(map); | |||
var geojsonLineOptions = { | |||
color: "#0033ff", | |||
weight: 4, | |||
opacity: 0.5 | |||
}; | |||
var startIcon = L.icon({ | |||
iconUrl: '/assets/map-pin-start.png', | |||
iconSize: [18,28], | |||
iconAnchor: [9,28] | |||
}); | |||
// Load the current location and show on the map | |||
var currentLocationMarker; | |||
function getCurrentLocation() { | |||
$.getJSON("/share/current.json?token="+$("#share_token").val(), function(data){ | |||
if(data.data) { | |||
moveMarkerToPosition(data.data); | |||
map.setView(currentLocationMarker.getLatLng()); | |||
} | |||
setTimeout(getCurrentLocation, 5000); | |||
}); | |||
} | |||
getCurrentLocation(); | |||
function moveMarkerToPosition(feature) { | |||
if(feature && feature.geometry) { | |||
var coord = pointFromGeoJSON(feature.geometry.coordinates); | |||
if(coord) { | |||
if(!currentLocationMarker) { | |||
currentLocationMarker = L.marker(coord).addTo(map); | |||
} else { | |||
currentLocationMarker.setLatLng(coord); | |||
} | |||
} | |||
} | |||
} | |||
function pointFromGeoJSON(geo) { | |||
return L.latLng(geo[1], geo[0]) | |||
} |
@ -0,0 +1,25 @@ | |||
<!doctype html> | |||
<html> | |||
<head> | |||
<meta charset="utf-8"> | |||
<title>Compass</title> | |||
<link rel="stylesheet" href="/assets/semantic/semantic.min.css"> | |||
<link rel="stylesheet" href="/assets/font-awesome-4.4.0/css/font-awesome.min.css"> | |||
<link rel="stylesheet" href="/assets/styles.css"> | |||
<meta name="viewport" content="width=device-width, initial-scale=1"> | |||
<script src="/assets/jquery-1.11.3.min.js"></script> | |||
<link rel="stylesheet" href="/assets/leaflet-0.7.3/leaflet.css" /> | |||
<link rel="stylesheet" href="/assets/share-map.css"> | |||
</head> | |||
<body> | |||
@yield('content') | |||
<script src="/assets/leaflet-0.7.3/leaflet.js"></script> | |||
<script src="/assets/leaflet-0.7.3/esri-leaflet.js"></script> | |||
<script src="/assets/semantic/semantic.min.js"></script> | |||
<script src="/assets/semantic/components/dropdown.min.js"></script> | |||
<script src="/assets/extensions.js"></script> | |||
<script src="/assets/share.js"></script> | |||
</body> | |||
</html> |
@ -0,0 +1,9 @@ | |||
@extends('layouts.master') | |||
@section('content') | |||
<div style="text-align: center; margin-top: 3em;"> | |||
This shared link has expired. | |||
</div> | |||
@endsection |
@ -0,0 +1,9 @@ | |||
@extends('layouts.share') | |||
@section('content') | |||
<div id="map"></div> | |||
<input type="hidden" id="share_token" value="{{ $share_token }}"> | |||
@endsection |