Add docs and few fixes

This commit is contained in:
Eugene Burmakin 2025-02-07 19:17:28 +01:00
parent 073b3afc71
commit fea87b85bb
17 changed files with 2729 additions and 149 deletions

File diff suppressed because it is too large Load diff

View file

@ -107,3 +107,8 @@
transform: rotate(360deg);
}
}
.clickable-area,
.leaflet-interactive {
cursor: pointer !important;
}

View file

@ -13,8 +13,7 @@ import {
getSpeedColor
} from "../maps/polylines";
import { fetchAndDrawAreas } from "../maps/areas";
import { handleAreaCreated } from "../maps/areas";
import { fetchAndDrawAreas, handleAreaCreated } from "../maps/areas";
import { showFlashMessage, fetchAndDisplayPhotos, debounce } from "../maps/helpers";
@ -67,7 +66,7 @@ export default class extends Controller {
imperial: this.distanceUnit === 'mi',
metric: this.distanceUnit === 'km',
maxWidth: 120
}).addTo(this.map)
}).addTo(this.map);
// Add stats control
const StatsControl = L.Control.extend({
@ -107,7 +106,8 @@ export default class extends Controller {
// Create a proper Leaflet layer for fog
this.fogOverlay = createFogOverlay();
this.areasLayer = L.layerGroup(); // Initialize areas layer
this.areasLayer = L.layerGroup(); // Initialize areasLayer
this.photoMarkers = L.layerGroup();
this.setupScratchLayer(this.countryCodesMap);
@ -123,7 +123,7 @@ export default class extends Controller {
Heatmap: this.heatmapLayer,
"Fog of War": new this.fogOverlay(),
"Scratch map": this.scratchLayer,
Areas: this.areasLayer,
Areas: this.areasLayer, // Add areasLayer to the control
Photos: this.photoMarkers
};
@ -166,6 +166,20 @@ export default class extends Controller {
// Fetch and draw areas when the map is loaded
fetchAndDrawAreas(this.areasLayer, this.apiKey);
// Add a simple test circle to the map
const testCircle = L.circle([52.514568, 13.350111], {
radius: 100,
color: 'blue',
fillColor: '#30f',
fillOpacity: 0.5,
interactive: true,
zIndexOffset: 1000
}).addTo(this.map);
testCircle.on('mouseover', () => {
console.log('Mouse over test circle');
});
let fogEnabled = false;
// Hide fog by default
@ -248,10 +262,13 @@ export default class extends Controller {
}
// Store panel state before disconnecting
if (this.rightPanel) {
const finalState = document.querySelector('.leaflet-right-panel').style.display !== 'none' ? 'true' : 'false';
const panel = document.querySelector('.leaflet-right-panel');
const finalState = panel ? (panel.style.display !== 'none' ? 'true' : 'false') : 'false';
localStorage.setItem('mapPanelOpen', finalState);
}
this.map.remove();
if (this.map) {
this.map.remove();
}
}
setupSubscription() {
@ -567,17 +584,26 @@ export default class extends Controller {
},
},
},
edit: {
featureGroup: this.drawnItems
}
});
// Handle circle creation
this.map.on(L.Draw.Event.CREATED, (event) => {
this.map.on('draw:created', (event) => {
const layer = event.layer;
if (event.layerType === 'circle') {
handleAreaCreated(this.areasLayer, layer, this.apiKey);
console.log("Circle created, opening popup..."); // Add debug log
try {
// Add the layer to the map first
layer.addTo(this.map);
handleAreaCreated(this.areasLayer, layer, this.apiKey);
} catch (error) {
console.error("Error in handleAreaCreated:", error);
console.error(error.stack); // Add stack trace
}
}
this.drawnItems.addLayer(layer);
});
}

View file

@ -1,54 +1,105 @@
export function handleAreaCreated(areasLayer, layer, apiKey) {
console.log('handleAreaCreated called with apiKey:', apiKey);
const radius = layer.getRadius();
const center = layer.getLatLng();
const formHtml = `
<div class="card w-96 max-w-sm bg-content-100 shadow-xl">
<div class="card w-96 bg-base-100 shadow-xl">
<div class="card-body">
<h2 class="card-title">New Area</h2>
<form id="circle-form">
<form id="circle-form" class="space-y-4">
<div class="form-control">
<label for="circle-name" class="label">
<span class="label-text">Name</span>
<span class="label-text">Area Name</span>
</label>
<input type="text" id="circle-name" name="area[name]" class="input input-bordered input-ghost focus:input-ghost w-full max-w-xs" required>
<input type="text"
id="circle-name"
name="area[name]"
class="input input-bordered w-full"
placeholder="Enter area name"
autofocus
required>
</div>
<input type="hidden" name="area[latitude]" value="${center.lat}">
<input type="hidden" name="area[longitude]" value="${center.lng}">
<input type="hidden" name="area[radius]" value="${radius}">
<div class="card-actions justify-end mt-4">
<button type="submit" class="btn btn-primary">Save</button>
<div class="flex justify-between mt-4">
<button type="button"
class="btn btn-ghost"
onclick="this.closest('.leaflet-popup').querySelector('.leaflet-popup-close-button').click()">
Cancel
</button>
<button type="button" id="save-area-btn" class="btn btn-primary">Save Area</button>
</div>
</form>
</div>
</div>
`;
layer.bindPopup(
formHtml, {
maxWidth: "auto",
minWidth: 300
}
).openPopup();
console.log('Binding popup to layer');
layer.bindPopup(formHtml, {
maxWidth: "auto",
minWidth: 300,
closeButton: true,
closeOnClick: false,
className: 'area-form-popup'
}).openPopup();
layer.on('popupopen', () => {
const form = document.getElementById('circle-form');
if (!form) return;
form.addEventListener('submit', (e) => {
e.preventDefault();
saveArea(new FormData(form), areasLayer, layer, apiKey);
});
});
// Add the layer to the areas layer group
console.log('Adding layer to areasLayer');
areasLayer.addLayer(layer);
// Bind the event handler immediately after opening the popup
setTimeout(() => {
console.log('Setting up form handlers');
const form = document.getElementById('circle-form');
const saveButton = document.getElementById('save-area-btn');
const nameInput = document.getElementById('circle-name');
console.log('Form:', form);
console.log('Save button:', saveButton);
console.log('Name input:', nameInput);
if (!form || !saveButton || !nameInput) {
console.error('Required elements not found');
return;
}
// Focus the name input
nameInput.focus();
// Remove any existing click handlers
const newSaveButton = saveButton.cloneNode(true);
saveButton.parentNode.replaceChild(newSaveButton, saveButton);
// Add click handler
newSaveButton.addEventListener('click', (e) => {
console.log('Save button clicked');
e.preventDefault();
e.stopPropagation();
if (!nameInput.value.trim()) {
console.log('Name is empty');
nameInput.classList.add('input-error');
return;
}
console.log('Creating FormData');
const formData = new FormData(form);
formData.forEach((value, key) => {
console.log(`FormData: ${key} = ${value}`);
});
console.log('Calling saveArea');
saveArea(formData, areasLayer, layer, apiKey);
});
}, 100); // Small delay to ensure DOM is ready
}
export function saveArea(formData, areasLayer, layer, apiKey) {
console.log('saveArea called with apiKey:', apiKey);
const data = {};
formData.forEach((value, key) => {
console.log('FormData entry:', key, value);
const keys = key.split('[').map(k => k.replace(']', ''));
if (keys.length > 1) {
if (!data[keys[0]]) data[keys[0]] = {};
@ -58,18 +109,21 @@ export function saveArea(formData, areasLayer, layer, apiKey) {
}
});
console.log('Sending fetch request with data:', data);
fetch(`/api/v1/areas?api_key=${apiKey}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json'},
body: JSON.stringify(data)
})
.then(response => {
console.log('Received response:', response);
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
console.log('Area saved successfully:', data);
layer.closePopup();
layer.bindPopup(`
Name: ${data.name}<br>
@ -79,9 +133,13 @@ export function saveArea(formData, areasLayer, layer, apiKey) {
// Add event listener for the delete button
layer.on('popupopen', () => {
document.querySelector('.delete-area').addEventListener('click', () => {
deleteArea(data.id, areasLayer, layer, apiKey);
});
const deleteButton = document.querySelector('.delete-area');
if (deleteButton) {
deleteButton.addEventListener('click', (e) => {
e.preventDefault();
deleteArea(data.id, areasLayer, layer, apiKey);
});
}
});
})
.catch(error => {

View file

@ -144,7 +144,7 @@ class GoogleMaps::PhoneTakeoutParser
end
def parse_raw_array(raw_data)
raw_data.map do |data_point|
raw_data.flat_map do |data_point|
if data_point.dig('visit', 'topCandidate', 'placeLocation')
parse_visit_place_location(data_point)
elsif data_point.dig('activity', 'start') && data_point.dig('activity', 'end')
@ -152,7 +152,7 @@ class GoogleMaps::PhoneTakeoutParser
elsif data_point['timelinePath']
parse_timeline_path(data_point)
end
end.flatten.compact
end.compact
end
def parse_semantic_segments(semantic_segments)

View file

@ -16,7 +16,7 @@ class OwnTracks::Params
altitude: params[:alt],
accuracy: params[:acc],
vertical_accuracy: params[:vac],
velocity: params[:vel],
velocity: speed,
ssid: params[:SSID],
bssid: params[:BSSID],
tracker_id: params[:tid],
@ -69,4 +69,16 @@ class OwnTracks::Params
else 'unknown'
end
end
def speed
return params[:vel] unless owntracks_point?
# OwnTracks speed is in km/h, so we need to convert it to m/s
# Reference: https://owntracks.org/booklet/tech/json/
((params[:vel].to_f * 1000) / 3600).round(1).to_s
end
def owntracks_point?
params[:topic].present?
end
end

View file

@ -1,5 +1,5 @@
2024-03-01T09:03:09Z * {"bs":2,"p":100.266,"batt":94,"_type":"location","tid":"RO","topic":"owntracks/test/iPhone 12 Pro","alt":36,"lon":13.332,"vel":0,"t":"p","BSSID":"b0:f2:8:45:94:33","SSID":"Home Wifi","conn":"w","vac":4,"acc":10,"tst":1709283789,"lat":52.225,"m":1,"inrids":["5f1d1b"],"inregions":["home"],"_http":true}
2024-03-01T17:46:02Z * {"bs":1,"p":100.28,"batt":94,"_type":"location","tid":"RO","topic":"owntracks/test/iPhone 12 Pro","alt":36,"lon":13.333,"t":"p","vel":0,"BSSID":"b0:f2:8:45:94:33","conn":"w","SSID":"Home Wifi","vac":3,"cog":98,"acc":9,"tst":1709315162,"lat":52.226,"m":1,"inrids":["5f1d1b"],"inregions":["home"],"_http":true}
2024-03-01T09:03:09Z * {"bs":2,"p":100.266,"batt":94,"_type":"location","tid":"RO","topic":"owntracks/test/iPhone 12 Pro","alt":36,"lon":13.332,"vel":5,"t":"p","BSSID":"b0:f2:8:45:94:33","SSID":"Home Wifi","conn":"w","vac":4,"acc":10,"tst":1709283789,"lat":52.225,"m":1,"inrids":["5f1d1b"],"inregions":["home"],"_http":true}
2024-03-01T17:46:02Z * {"bs":1,"p":100.28,"batt":94,"_type":"location","tid":"RO","topic":"owntracks/test/iPhone 12 Pro","alt":36,"lon":13.333,"t":"p","vel":5,"BSSID":"b0:f2:8:45:94:33","conn":"w","SSID":"Home Wifi","vac":3,"cog":98,"acc":9,"tst":1709315162,"lat":52.226,"m":1,"inrids":["5f1d1b"],"inregions":["home"],"_http":true}
2024-03-01T18:26:55Z * {"lon":13.334,"acc":5,"wtst":1696359532,"event":"leave","rid":"5f1d1b","desc":"home","topic":"owntracks/test/iPhone 12 Pro/event","lat":52.227,"t":"c","tst":1709317615,"tid":"RO","_type":"transition","_http":true}
2024-03-01T18:26:55Z * {"cog":40,"batt":85,"lon":13.335,"acc":5,"bs":1,"p":100.279,"vel":3,"vac":3,"lat":52.228,"topic":"owntracks/test/iPhone 12 Pro","t":"c","conn":"m","m":1,"tst":1709317615,"alt":36,"_type":"location","tid":"RO","_http":true}
2024-03-01T18:28:30Z * {"cog":38,"batt":85,"lon":13.336,"acc":5,"bs":1,"p":100.349,"vel":3,"vac":3,"lat":52.229,"topic":"owntracks/test/iPhone 12 Pro","t":"v","conn":"m","m":1,"tst":1709317710,"alt":35,"_type":"location","tid":"RO","_http":true}

View file

@ -26,7 +26,7 @@ RSpec.describe OwnTracks::ExportParser do
'altitude' => 36,
'accuracy' => 10,
'vertical_accuracy' => 4,
'velocity' => '0',
'velocity' => '1.4',
'connection' => 'wifi',
'ssid' => 'Home Wifi',
'bssid' => 'b0:f2:8:45:94:33',
@ -51,7 +51,7 @@ RSpec.describe OwnTracks::ExportParser do
'tid' => 'RO',
'tst' => 1_709_283_789,
'vac' => 4,
'vel' => 0,
'vel' => 5,
'SSID' => 'Home Wifi',
'batt' => 94,
'conn' => 'w',
@ -64,6 +64,12 @@ RSpec.describe OwnTracks::ExportParser do
}
)
end
it 'correctly converts speed' do
parser
expect(Point.first.velocity).to eq('1.4')
end
end
end
end

View file

@ -20,7 +20,7 @@ RSpec.describe OwnTracks::Params do
altitude: 36,
accuracy: 10,
vertical_accuracy: 4,
velocity: 0,
velocity: '1.4',
ssid: 'Home Wifi',
bssid: 'b0:f2:8:45:94:33',
tracker_id: 'RO',
@ -39,7 +39,7 @@ RSpec.describe OwnTracks::Params do
'topic' => 'owntracks/test/iPhone 12 Pro',
'alt' => 36,
'lon' => 13.332,
'vel' => 0,
'vel' => 5,
't' => 'p',
'BSSID' => 'b0:f2:8:45:94:33',
'SSID' => 'Home Wifi',

View file

@ -16,10 +16,26 @@ describe 'Areas API', type: :request do
parameter name: :area, in: :body, schema: {
type: :object,
properties: {
name: { type: :string },
latitude: { type: :number },
longitude: { type: :number },
radius: { type: :number }
name: {
type: :string,
example: 'Home',
description: 'The name of the area'
},
latitude: {
type: :number,
example: 40.7128,
description: 'The latitude of the area'
},
longitude: {
type: :number,
example: -74.0060,
description: 'The longitude of the area'
},
radius: {
type: :number,
example: 100,
description: 'The radius of the area in meters'
}
},
required: %w[name latitude longitude radius]
}
@ -47,11 +63,31 @@ describe 'Areas API', type: :request do
items: {
type: :object,
properties: {
id: { type: :integer },
name: { type: :string },
latitude: { type: :number },
longitude: { type: :number },
radius: { type: :number }
id: {
type: :integer,
example: 1,
description: 'The ID of the area'
},
name: {
type: :string,
example: 'Home',
description: 'The name of the area'
},
latitude: {
type: :number,
example: 40.7128,
description: 'The latitude of the area'
},
longitude: {
type: :number,
example: -74.0060,
description: 'The longitude of the area'
},
radius: {
type: :number,
example: 100,
description: 'The radius of the area in meters'
}
},
required: %w[id name latitude longitude radius]
}

View file

@ -9,7 +9,12 @@ RSpec.describe 'Api::V1::Countries::VisitedCities', type: :request do
description 'Returns a list of visited cities and countries based on tracked points within the specified date range'
produces 'application/json'
parameter name: :api_key, in: :query, type: :string, required: true
parameter name: :api_key,
in: :query,
type: :string,
required: true,
example: 'a1b2c3d4e5f6g7h8i9j0',
description: 'Your API authentication key'
parameter name: :start_at,
in: :query,
type: :string,
@ -32,6 +37,36 @@ RSpec.describe 'Api::V1::Countries::VisitedCities', type: :request do
data: {
type: :array,
description: 'Array of countries and their visited cities',
example: [
{
country: 'Germany',
cities: [
{
city: 'Berlin',
points: 4394,
timestamp: 1_724_868_369,
stayed_for: 24_490
},
{
city: 'Munich',
points: 2156,
timestamp: 1_724_782_369,
stayed_for: 12_450
}
]
},
{
country: 'France',
cities: [
{
city: 'Paris',
points: 3267,
timestamp: 1_724_695_969,
stayed_for: 18_720
}
]
}
],
items: {
type: :object,
properties: {

View file

@ -8,6 +8,22 @@ describe 'Health API', type: :request do
tags 'Health'
produces 'application/json'
response '200', 'Healthy' do
schema type: :object,
properties: {
status: { type: :string }
}
header 'X-Dawarich-Response',
type: :string,
required: true,
example: 'Hey, I\'m alive!',
description: "Depending on the authentication status of the request, the response will be different. If the request is authenticated, the response will be 'Hey, I'm alive and authenticated!'. If the request is not authenticated, the response will be 'Hey, I'm alive!'."
header 'X-Dawarich-Version',
type: :string,
required: true,
example: '1.0.0',
description: 'The version of the application, for example: 1.0.0'
run_test!
end
end

View file

@ -26,7 +26,8 @@ describe 'Overland Batches API', type: :request do
deferred: 0,
significant_change: 'unknown',
locations_in_payload: 1,
device_id: 'Swagger',
device_id: 'iOS device #166',
unique_id: '1234567890',
wifi: 'unknown',
battery_state: 'unknown',
battery_level: 0
@ -39,36 +40,100 @@ describe 'Overland Batches API', type: :request do
parameter name: :locations, in: :body, schema: {
type: :object,
properties: {
type: { type: :string },
type: { type: :string, example: 'Feature' },
geometry: {
type: :object,
properties: {
type: { type: :string },
coordinates: { type: :array }
type: { type: :string, example: 'Point' },
coordinates: { type: :array, example: [13.356718, 52.502397] }
}
},
properties: {
type: :object,
properties: {
timestamp: { type: :string },
altitude: { type: :number },
speed: { type: :number },
horizontal_accuracy: { type: :number },
vertical_accuracy: { type: :number },
motion: { type: :array },
pauses: { type: :boolean },
activity: { type: :string },
desired_accuracy: { type: :number },
deferred: { type: :number },
significant_change: { type: :string },
locations_in_payload: { type: :number },
device_id: { type: :string },
wifi: { type: :string },
battery_state: { type: :string },
battery_level: { type: :number }
}
},
required: %w[geometry properties]
timestamp: {
type: :string,
example: '2021-06-01T12:00:00Z',
description: 'Timestamp in ISO 8601 format'
},
altitude: {
type: :number,
example: 0,
description: 'Altitude in meters'
},
speed: {
type: :number,
example: 0,
description: 'Speed in meters per second'
},
horizontal_accuracy: {
type: :number,
example: 0,
description: 'Horizontal accuracy in meters'
},
vertical_accuracy: {
type: :number,
example: 0,
description: 'Vertical accuracy in meters'
},
motion: {
type: :array,
example: %w[walking running driving cycling stationary],
description: 'Motion type, for example: automotive_navigation, fitness, other_navigation or other'
},
activity: {
type: :string,
example: 'unknown',
description: 'Activity type, for example: automotive_navigation, fitness, other_navigation or other'
},
desired_accuracy: {
type: :number,
example: 0,
description: 'Desired accuracy in meters'
},
deferred: {
type: :number,
example: 0,
description: 'the distance in meters to defer location updates'
},
significant_change: {
type: :string,
example: 'disabled',
description: 'a significant change mode, disabled, enabled or exclusive'
},
locations_in_payload: {
type: :number,
example: 1,
description: 'the number of locations in the payload'
},
device_id: {
type: :string,
example: 'iOS device #166',
description: 'the device id'
},
unique_id: {
type: :string,
example: '1234567890',
description: 'the device\'s Unique ID as set by Apple'
},
wifi: {
type: :string,
example: 'unknown',
description: 'the WiFi network name'
},
battery_state: {
type: :string,
example: 'unknown',
description: 'the battery state, unknown, unplugged, charging or full'
},
battery_level: {
type: :number,
example: 0,
description: 'the battery level percentage, from 0 to 1'
}
},
required: %w[geometry properties]
}
}
}

View file

@ -39,29 +39,29 @@ describe 'OwnTracks Points API', type: :request do
parameter name: :point, in: :body, schema: {
type: :object,
properties: {
batt: { type: :number },
lon: { type: :number },
acc: { type: :number },
bs: { type: :number },
inrids: { type: :array },
BSSID: { type: :string },
SSID: { type: :string },
vac: { type: :number },
inregions: { type: :array },
lat: { type: :number },
topic: { type: :string },
t: { type: :string },
conn: { type: :string },
m: { type: :number },
tst: { type: :number },
alt: { type: :number },
_type: { type: :string },
tid: { type: :string },
_http: { type: :boolean },
ghash: { type: :string },
isorcv: { type: :string },
isotst: { type: :string },
disptst: { type: :string }
batt: { type: :number, description: 'Device battery level (percentage)' },
lon: { type: :number, description: 'Longitude coordinate' },
acc: { type: :number, description: 'Accuracy of position in meters' },
bs: { type: :number, description: 'Battery status (0=unknown, 1=unplugged, 2=charging, 3=full)' },
inrids: { type: :array, description: 'Array of region IDs device is currently in' },
BSSID: { type: :string, description: 'Connected WiFi access point MAC address' },
SSID: { type: :string, description: 'Connected WiFi network name' },
vac: { type: :number, description: 'Vertical accuracy in meters' },
inregions: { type: :array, description: 'Array of region names device is currently in' },
lat: { type: :number, description: 'Latitude coordinate' },
topic: { type: :string, description: 'MQTT topic in format owntracks/user/device' },
t: { type: :string, description: 'Type of message (p=position, c=circle, etc)' },
conn: { type: :string, description: 'Connection type (w=wifi, m=mobile, o=offline)' },
m: { type: :number, description: 'Motion state (0=stopped, 1=moving)' },
tst: { type: :number, description: 'Timestamp in Unix epoch time' },
alt: { type: :number, description: 'Altitude in meters' },
_type: { type: :string, description: 'Internal message type (usually "location")' },
tid: { type: :string, description: 'Tracker ID used to display the initials of a user' },
_http: { type: :boolean, description: 'Whether message was sent via HTTP (true) or MQTT (false)' },
ghash: { type: :string, description: 'Geohash of location' },
isorcv: { type: :string, description: 'ISO 8601 timestamp when message was received' },
isotst: { type: :string, description: 'ISO 8601 timestamp of the location fix' },
disptst: { type: :string, description: 'Human-readable timestamp of the location fix' }
},
required: %w[owntracks/jane]
}

View file

@ -101,27 +101,73 @@ describe 'Points API', type: :request do
geometry: {
type: :object,
properties: {
type: { type: :string },
coordinates: { type: :array, items: { type: :number } }
type: {
type: :string,
example: 'Point',
description: 'the geometry type, always Point'
},
coordinates: {
type: :array,
items: {
type: :number,
example: [-122.40530871, 37.74430413],
description: 'the coordinates of the point, longitude and latitude'
}
}
}
},
properties: {
type: :object,
properties: {
timestamp: { type: :string },
horizontal_accuracy: { type: :number },
vertical_accuracy: { type: :number },
altitude: { type: :number },
speed: { type: :number },
speed_accuracy: { type: :number },
course: { type: :number },
course_accuracy: { type: :number },
track_id: { type: :string },
device_id: { type: :string }
timestamp: {
type: :string,
example: '2025-01-17T21:03:01Z',
description: 'the timestamp of the point'
},
horizontal_accuracy: {
type: :number,
example: 5,
description: 'the horizontal accuracy of the point in meters'
},
vertical_accuracy: {
type: :number,
example: -1,
description: 'the vertical accuracy of the point in meters'
},
altitude: {
type: :number,
example: 0,
description: 'the altitude of the point in meters'
},
speed: {
type: :number,
example: 92.088,
description: 'the speed of the point in meters per second'
},
speed_accuracy: {
type: :number,
example: 0,
description: 'the speed accuracy of the point in meters per second'
},
course_accuracy: {
type: :number,
example: 0,
description: 'the course accuracy of the point in degrees'
},
track_id: {
type: :string,
example: '799F32F5-89BB-45FB-A639-098B1B95B09F',
description: 'the track id of the point set by the device'
},
device_id: {
type: :string,
example: '8D5D4197-245B-4619-A88B-2049100ADE46',
description: 'the device id of the point set by the device'
}
}
}
},
required: %w[geometry properties]
},
required: %w[geometry properties]
}
}
parameter name: :api_key, in: :query, type: :string, required: true, description: 'API Key'

View file

@ -20,12 +20,26 @@ describe 'Settings API', type: :request do
parameter name: :settings, in: :body, schema: {
type: :object,
properties: {
route_opacity: { type: :number },
meters_between_routes: { type: :number },
minutes_between_routes: { type: :number },
fog_of_war_meters: { type: :number },
time_threshold_minutes: { type: :number },
merge_threshold_minutes: { type: :number }
route_opacity: {
type: :number,
example: 0.3,
description: 'the opacity of the route, float between 0 and 1'
},
meters_between_routes: {
type: :number,
example: 100,
description: 'the distance between routes in meters'
},
minutes_between_routes: {
type: :number,
example: 100,
description: 'the time between routes in minutes'
},
fog_of_war_meters: {
type: :number,
example: 100,
description: 'the fog of war distance in meters'
}
},
optional: %w[route_opacity meters_between_routes minutes_between_routes fog_of_war_meters
time_threshold_minutes merge_threshold_minutes]
@ -49,12 +63,26 @@ describe 'Settings API', type: :request do
settings: {
type: :object,
properties: {
route_opacity: { type: :string },
meters_between_routes: { type: :string },
minutes_between_routes: { type: :string },
fog_of_war_meters: { type: :string },
time_threshold_minutes: { type: :string },
merge_threshold_minutes: { type: :string }
route_opacity: {
type: :string,
example: 0.3,
description: 'the opacity of the route, float between 0 and 1'
},
meters_between_routes: {
type: :string,
example: 100,
description: 'the distance between routes in meters'
},
minutes_between_routes: {
type: :string,
example: 100,
description: 'the time between routes in minutes'
},
fog_of_war_meters: {
type: :string,
example: 100,
description: 'the fog of war distance in meters'
}
},
required: %w[route_opacity meters_between_routes minutes_between_routes fog_of_war_meters
time_threshold_minutes merge_threshold_minutes]

View file

@ -29,12 +29,20 @@ paths:
properties:
name:
type: string
example: Home
description: The name of the area
latitude:
type: number
example: 40.7128
description: The latitude of the area
longitude:
type: number
example: -74.006
description: The longitude of the area
radius:
type: number
example: 100
description: The radius of the area in meters
required:
- name
- latitude
@ -71,14 +79,24 @@ paths:
properties:
id:
type: integer
example: 1
description: The ID of the area
name:
type: string
example: Home
description: The name of the area
latitude:
type: number
example: 40.7128
description: The latitude of the area
longitude:
type: number
example: -74.006
description: The longitude of the area
radius:
type: number
example: 100
description: The radius of the area in meters
required:
- id
- name
@ -117,6 +135,8 @@ paths:
- name: api_key
in: query
required: true
example: a1b2c3d4e5f6g7h8i9j0
description: Your API authentication key
schema:
type: string
- name: start_at
@ -146,6 +166,23 @@ paths:
data:
type: array
description: Array of countries and their visited cities
example:
- country: Germany
cities:
- city: Berlin
points: 4394
timestamp: 1724868369
stayed_for: 24490
- city: Munich
points: 2156
timestamp: 1724782369
stayed_for: 12450
- country: France
cities:
- city: Paris
points: 3267
timestamp: 1724695969
stayed_for: 18720
items:
type: object
properties:
@ -192,6 +229,27 @@ paths:
responses:
'200':
description: Healthy
headers:
X-Dawarich-Response:
type: string
required: true
example: Hey, I'm alive!
description: Depending on the authentication status of the request,
the response will be different. If the request is authenticated, the
response will be 'Hey, I'm alive and authenticated!'. If the request
is not authenticated, the response will be 'Hey, I'm alive!'.
X-Dawarich-Version:
type: string
required: true
example: 1.0.0
description: 'The version of the application, for example: 1.0.0'
content:
application/json:
schema:
type: object
properties:
status:
type: string
"/api/v1/overland/batches":
post:
summary: Creates a batch of points
@ -217,51 +275,97 @@ paths:
properties:
type:
type: string
example: Feature
geometry:
type: object
properties:
type:
type: string
example: Point
coordinates:
type: array
example:
- 13.356718
- 52.502397
properties:
type: object
properties:
timestamp:
type: string
example: '2021-06-01T12:00:00Z'
description: Timestamp in ISO 8601 format
altitude:
type: number
example: 0
description: Altitude in meters
speed:
type: number
example: 0
description: Speed in meters per second
horizontal_accuracy:
type: number
example: 0
description: Horizontal accuracy in meters
vertical_accuracy:
type: number
example: 0
description: Vertical accuracy in meters
motion:
type: array
pauses:
type: boolean
example:
- walking
- running
- driving
- cycling
- stationary
description: 'Motion type, for example: automotive_navigation,
fitness, other_navigation or other'
activity:
type: string
example: unknown
description: 'Activity type, for example: automotive_navigation,
fitness, other_navigation or other'
desired_accuracy:
type: number
example: 0
description: Desired accuracy in meters
deferred:
type: number
example: 0
description: the distance in meters to defer location updates
significant_change:
type: string
example: disabled
description: a significant change mode, disabled, enabled or
exclusive
locations_in_payload:
type: number
example: 1
description: the number of locations in the payload
device_id:
type: string
example: 'iOS device #166'
description: the device id
unique_id:
type: string
example: '1234567890'
description: the device's Unique ID as set by Apple
wifi:
type: string
example: unknown
description: the WiFi network name
battery_state:
type: string
example: unknown
description: the battery state, unknown, unplugged, charging
or full
battery_level:
type: number
required:
- geometry
- properties
example: 0
description: the battery level percentage, from 0 to 1
required:
- geometry
- properties
examples:
'0':
summary: Creates a batch of points
@ -286,7 +390,8 @@ paths:
deferred: 0
significant_change: unknown
locations_in_payload: 1
device_id: Swagger
device_id: 'iOS device #166'
unique_id: '1234567890'
wifi: unknown
battery_state: unknown
battery_level: 0
@ -315,50 +420,74 @@ paths:
properties:
batt:
type: number
description: Device battery level (percentage)
lon:
type: number
description: Longitude coordinate
acc:
type: number
description: Accuracy of position in meters
bs:
type: number
description: Battery status (0=unknown, 1=unplugged, 2=charging,
3=full)
inrids:
type: array
description: Array of region IDs device is currently in
BSSID:
type: string
description: Connected WiFi access point MAC address
SSID:
type: string
description: Connected WiFi network name
vac:
type: number
description: Vertical accuracy in meters
inregions:
type: array
description: Array of region names device is currently in
lat:
type: number
description: Latitude coordinate
topic:
type: string
description: MQTT topic in format owntracks/user/device
t:
type: string
description: Type of message (p=position, c=circle, etc)
conn:
type: string
description: Connection type (w=wifi, m=mobile, o=offline)
m:
type: number
description: Motion state (0=stopped, 1=moving)
tst:
type: number
description: Timestamp in Unix epoch time
alt:
type: number
description: Altitude in meters
_type:
type: string
description: Internal message type (usually "location")
tid:
type: string
description: Tracker ID used to display the initials of a user
_http:
type: boolean
description: Whether message was sent via HTTP (true) or MQTT (false)
ghash:
type: string
description: Geohash of location
isorcv:
type: string
description: ISO 8601 timestamp when message was received
isotst:
type: string
description: ISO 8601 timestamp of the location fix
disptst:
type: string
description: Human-readable timestamp of the location fix
required:
- owntracks/jane
examples:
@ -725,36 +854,58 @@ paths:
properties:
type:
type: string
example: Point
description: the geometry type, always Point
coordinates:
type: array
items:
type: number
example:
- -122.40530871
- 37.74430413
description: the coordinates of the point, longitude and latitude
properties:
type: object
properties:
timestamp:
type: string
example: '2025-01-17T21:03:01Z'
description: the timestamp of the point
horizontal_accuracy:
type: number
example: 5
description: the horizontal accuracy of the point in meters
vertical_accuracy:
type: number
example: -1
description: the vertical accuracy of the point in meters
altitude:
type: number
example: 0
description: the altitude of the point in meters
speed:
type: number
example: 92.088
description: the speed of the point in meters per second
speed_accuracy:
type: number
course:
type: number
example: 0
description: the speed accuracy of the point in meters per second
course_accuracy:
type: number
example: 0
description: the course accuracy of the point in degrees
track_id:
type: string
example: 799F32F5-89BB-45FB-A639-098B1B95B09F
description: the track id of the point set by the device
device_id:
type: string
required:
- geometry
- properties
example: 8D5D4197-245B-4619-A88B-2049100ADE46
description: the device id of the point set by the device
required:
- geometry
- properties
examples:
'0':
summary: Creates a batch of points
@ -821,16 +972,20 @@ paths:
properties:
route_opacity:
type: number
example: 0.3
description: the opacity of the route, float between 0 and 1
meters_between_routes:
type: number
example: 100
description: the distance between routes in meters
minutes_between_routes:
type: number
example: 100
description: the time between routes in minutes
fog_of_war_meters:
type: number
time_threshold_minutes:
type: number
merge_threshold_minutes:
type: number
example: 100
description: the fog of war distance in meters
optional:
- route_opacity
- meters_between_routes
@ -873,16 +1028,21 @@ paths:
properties:
route_opacity:
type: string
example: 0.3
description: the opacity of the route, float between 0 and
1
meters_between_routes:
type: string
example: 100
description: the distance between routes in meters
minutes_between_routes:
type: string
example: 100
description: the time between routes in minutes
fog_of_war_meters:
type: string
time_threshold_minutes:
type: string
merge_threshold_minutes:
type: string
example: 100
description: the fog of war distance in meters
required:
- route_opacity
- meters_between_routes