mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-10 17:21:38 -05:00
Move map settings to the map itself
This commit is contained in:
parent
d11cfd864f
commit
df588d1e07
15 changed files with 787 additions and 357 deletions
|
|
@ -1 +1 @@
|
|||
0.12.1
|
||||
0.12.2
|
||||
|
|
|
|||
11
CHANGELOG.md
11
CHANGELOG.md
|
|
@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [0.12.2] — 2024-08-28
|
||||
|
||||
### Added
|
||||
|
||||
- `PATCH /api/v1/settings` endpoint to update user settings with swagger docs
|
||||
- `GET /api/v1/settings` endpoint to get user settings with swagger docs
|
||||
|
||||
### Changed
|
||||
|
||||
- Map settings moved to the map itself and are available in the top right corner of the map under the gear icon.
|
||||
|
||||
## [0.12.1] — 2024-08-25
|
||||
|
||||
### Fixed
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -23,3 +23,37 @@
|
|||
.timeline-box {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
/* Style for the settings panel */
|
||||
.leaflet-settings-panel {
|
||||
background-color: white;
|
||||
padding: 10px;
|
||||
border: 1px solid #ccc;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.leaflet-settings-panel label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.leaflet-settings-panel input {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 5px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.leaflet-settings-panel button {
|
||||
padding: 5px 10px;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.leaflet-settings-panel button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
|
|
|||
36
app/controllers/api/v1/settings_controller.rb
Normal file
36
app/controllers/api/v1/settings_controller.rb
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::SettingsController < ApiController
|
||||
def index
|
||||
render json: {
|
||||
settings: current_api_user.settings,
|
||||
status: 'success'
|
||||
}, status: :ok
|
||||
end
|
||||
|
||||
def update
|
||||
settings_params.each { |key, value| current_api_user.settings[key] = value }
|
||||
|
||||
if current_api_user.save
|
||||
render json: {
|
||||
message: 'Settings updated',
|
||||
settings: current_api_user.settings,
|
||||
status: 'success'
|
||||
}, status: :ok
|
||||
else
|
||||
render json: {
|
||||
message: 'Something went wrong',
|
||||
errors: current_api_user.errors.full_messages
|
||||
}, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def settings_params
|
||||
params.require(:settings).permit(
|
||||
:meters_between_routes, :minutes_between_routes, :fog_of_war_meters,
|
||||
:time_threshold_minutes, :merge_threshold_minutes, :route_opacity
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
@ -19,8 +19,9 @@ export default class extends Controller {
|
|||
this.apiKey = this.element.dataset.api_key;
|
||||
this.markers = JSON.parse(this.element.dataset.coordinates);
|
||||
this.timezone = this.element.dataset.timezone;
|
||||
this.clearFogRadius = this.element.dataset.fog_of_war_meters;
|
||||
this.routeOpacity = parseInt(this.element.dataset.route_opacity) / 100 || 0.6;
|
||||
this.userSettings = JSON.parse(this.element.dataset.user_settings);
|
||||
this.clearFogRadius = parseInt(this.userSettings.fog_of_war_meters) || 50;
|
||||
this.routeOpacity = parseFloat(this.userSettings.route_opacity) || 0.6;
|
||||
|
||||
this.center = this.markers[this.markers.length - 1] || [52.514568, 13.350111];
|
||||
|
||||
|
|
@ -103,6 +104,8 @@ export default class extends Controller {
|
|||
this.map.removeControl(this.drawControl);
|
||||
}
|
||||
});
|
||||
|
||||
this.addSettingsButton();
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
|
|
@ -318,8 +321,8 @@ export default class extends Controller {
|
|||
createPolylinesLayer(markers, map, timezone, routeOpacity) {
|
||||
const splitPolylines = [];
|
||||
let currentPolyline = [];
|
||||
const distanceThresholdMeters = parseInt(this.element.dataset.meters_between_routes) || 500;
|
||||
const timeThresholdMinutes = parseInt(this.element.dataset.minutes_between_routes) || 60;
|
||||
const distanceThresholdMeters = parseInt(this.userSettings.meters_between_routes) || 500;
|
||||
const timeThresholdMinutes = parseInt(this.userSettings.minutes_between_routes) || 60;
|
||||
|
||||
for (let i = 0, len = markers.length; i < len; i++) {
|
||||
if (currentPolyline.length === 0) {
|
||||
|
|
@ -515,12 +518,7 @@ export default class extends Controller {
|
|||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
console.log('Fetched areas:', data); // Debugging line to check response
|
||||
|
||||
data.forEach(area => {
|
||||
// Log each area to verify the structure
|
||||
console.log('Area:', area);
|
||||
|
||||
// Check if necessary fields are present
|
||||
if (area.latitude && area.longitude && area.radius && area.name && area.id) {
|
||||
const layer = L.circle([area.latitude, area.longitude], {
|
||||
|
|
@ -535,7 +533,6 @@ export default class extends Controller {
|
|||
`);
|
||||
|
||||
this.areasLayer.addLayer(layer); // Add to areas layer group
|
||||
console.log('Added layer to areasLayer:', layer); // Debugging line to confirm addition
|
||||
|
||||
// Add event listener for the delete button
|
||||
layer.on('popupopen', () => {
|
||||
|
|
@ -555,4 +552,385 @@ export default class extends Controller {
|
|||
console.error('There was a problem with the fetch request:', error);
|
||||
});
|
||||
}
|
||||
|
||||
addSettingsButton() {
|
||||
// Define the custom control
|
||||
const SettingsControl = L.Control.extend({
|
||||
onAdd: (map) => {
|
||||
const button = L.DomUtil.create('button', 'map-settings-button');
|
||||
button.innerHTML = '⚙️'; // Gear icon
|
||||
|
||||
// Style the button
|
||||
button.style.backgroundColor = 'white';
|
||||
button.style.width = '48px';
|
||||
button.style.height = '48px';
|
||||
button.style.border = 'none';
|
||||
button.style.borderRadius = '50%';
|
||||
button.style.cursor = 'pointer';
|
||||
button.style.boxShadow = '0 1px 4px rgba(0,0,0,0.3)';
|
||||
|
||||
// Disable map interactions when clicking the button
|
||||
L.DomEvent.disableClickPropagation(button);
|
||||
|
||||
// Toggle settings menu on button click
|
||||
L.DomEvent.on(button, 'click', () => {
|
||||
this.toggleSettingsMenu();
|
||||
});
|
||||
|
||||
return button;
|
||||
},
|
||||
|
||||
onRemove: (map) => {
|
||||
// hide settings menu
|
||||
}
|
||||
});
|
||||
|
||||
// Add the control to the map
|
||||
this.map.addControl(new SettingsControl({ position: 'topright' }));
|
||||
}
|
||||
|
||||
toggleSettingsMenu() {
|
||||
// If the settings panel already exists, just show/hide it
|
||||
if (this.settingsPanel) {
|
||||
if (this.settingsPanel._map) {
|
||||
this.map.removeControl(this.settingsPanel);
|
||||
} else {
|
||||
this.map.addControl(this.settingsPanel);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the settings panel for the first time
|
||||
this.settingsPanel = L.control({ position: 'topright' });
|
||||
|
||||
this.settingsPanel.onAdd = () => {
|
||||
const div = L.DomUtil.create('div', 'leaflet-settings-panel');
|
||||
|
||||
// Form HTML
|
||||
div.innerHTML = `
|
||||
<form id="settings-form" class="w-48">
|
||||
<label for="route-opacity">Route Opacity</label>
|
||||
<div class="join">
|
||||
<input type="number" class="input input-ghost join-item focus:input-ghost input-xs input-bordered w-full max-w-xs" id="route-opacity" name="route_opacity" min="0" max="1" step="0.1" value="${this.routeOpacity}">
|
||||
<label for="route_opacity_info" class="btn-xs join-item ">?</label>
|
||||
|
||||
</div>
|
||||
|
||||
<label for="fog_of_war_meters">Fog of War radius</label>
|
||||
<div class="join">
|
||||
<input type="number" class="join-item input input-ghost focus:input-ghost input-xs input-bordered w-full max-w-xs" id="fog_of_war_meters" name="fog_of_war_meters" min="5" max="100" step="1" value="${this.clearFogRadius}">
|
||||
<label for="fog_of_war_meters_info" class="btn-xs join-item">?</label>
|
||||
</div>
|
||||
|
||||
|
||||
<label for="meters_between_routes">Meters between routes</label>
|
||||
<div class="join">
|
||||
<input type="number" class="join-item input input-ghost focus:input-ghost input-xs input-bordered w-full max-w-xs" id="meters_between_routes" name="meters_between_routes" step="1" value="${this.userSettings.meters_between_routes}">
|
||||
<label for="meters_between_routes_info" class="btn-xs join-item">?</label>
|
||||
</div>
|
||||
|
||||
|
||||
<label for="minutes_between_routes">Minutes between routes</label>
|
||||
<div class="join">
|
||||
<input type="number" class="join-item input input-ghost focus:input-ghost input-xs input-bordered w-full max-w-xs" id="minutes_between_routes" name="minutes_between_routes" step="1" value="${this.userSettings.minutes_between_routes}">
|
||||
<label for="minutes_between_routes_info" class="btn-xs join-item">?</label>
|
||||
</div>
|
||||
|
||||
|
||||
<label for="time_threshold_minutes">Time threshold minutes</label>
|
||||
<div class="join">
|
||||
<input type="number" class="join-item input input-ghost focus:input-ghost input-xs input-bordered w-full max-w-xs" id="time_threshold_minutes" name="time_threshold_minutes" step="1" value="${this.userSettings.time_threshold_minutes}">
|
||||
<label for="time_threshold_minutes_info" class="btn-xs join-item">?</label>
|
||||
</div>
|
||||
|
||||
|
||||
<label for="merge_threshold_minutes">Merge threshold minutes</label>
|
||||
<div class="join">
|
||||
<input type="number" class="join-item input input-ghost focus:input-ghost input-xs input-bordered w-full max-w-xs" id="merge_threshold_minutes" name="merge_threshold_minutes" step="1" value="${this.userSettings.merge_threshold_minutes}">
|
||||
<label for="merge_threshold_minutes_info" class="btn-xs join-item">?</label>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<button type="submit">Update</button>
|
||||
</form>
|
||||
`;
|
||||
|
||||
// Style the panel
|
||||
div.style.backgroundColor = 'white';
|
||||
div.style.padding = '10px';
|
||||
div.style.border = '1px solid #ccc';
|
||||
div.style.boxShadow = '0 1px 4px rgba(0,0,0,0.3)';
|
||||
|
||||
// Prevent map interactions when interacting with the form
|
||||
L.DomEvent.disableClickPropagation(div);
|
||||
|
||||
// Add event listener to the form submission
|
||||
div.querySelector('#settings-form').addEventListener(
|
||||
'submit', this.updateSettings.bind(this)
|
||||
);
|
||||
|
||||
return div;
|
||||
};
|
||||
|
||||
this.map.addControl(this.settingsPanel);
|
||||
}
|
||||
|
||||
updateSettings(event) {
|
||||
event.preventDefault();
|
||||
|
||||
fetch(`/api/v1/settings?api_key=${this.apiKey}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
settings: {
|
||||
route_opacity: event.target.route_opacity.value,
|
||||
fog_of_war_meters: event.target.fog_of_war_meters.value,
|
||||
meters_between_routes: event.target.meters_between_routes.value,
|
||||
minutes_between_routes: event.target.minutes_between_routes.value,
|
||||
time_threshold_minutes: event.target.time_threshold_minutes.value,
|
||||
merge_threshold_minutes: event.target.merge_threshold_minutes.value,
|
||||
},
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
if (data.status === 'success') {
|
||||
this.showFlashMessage('notice', data.message);
|
||||
this.updateMapWithNewSettings(data.settings);
|
||||
} else {
|
||||
this.showFlashMessage('error', data.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
showFlashMessage(type, message) {
|
||||
// Create the outer flash container div
|
||||
const flashDiv = document.createElement('div');
|
||||
flashDiv.setAttribute('data-controller', 'removals');
|
||||
flashDiv.className = `flex items-center fixed top-5 right-5 ${this.classesForFlash(type)} py-3 px-5 rounded-lg`;
|
||||
|
||||
// Create the message div
|
||||
const messageDiv = document.createElement('div');
|
||||
messageDiv.className = 'mr-4';
|
||||
messageDiv.innerText = message;
|
||||
|
||||
// Create the close button
|
||||
const closeButton = document.createElement('button');
|
||||
closeButton.setAttribute('type', 'button');
|
||||
closeButton.setAttribute('data-action', 'click->removals#remove');
|
||||
|
||||
// Create the SVG icon for the close button
|
||||
const closeIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
closeIcon.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
||||
closeIcon.setAttribute('class', 'h-6 w-6');
|
||||
closeIcon.setAttribute('fill', 'none');
|
||||
closeIcon.setAttribute('viewBox', '0 0 24 24');
|
||||
closeIcon.setAttribute('stroke', 'currentColor');
|
||||
|
||||
const closeIconPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||
closeIconPath.setAttribute('stroke-linecap', 'round');
|
||||
closeIconPath.setAttribute('stroke-linejoin', 'round');
|
||||
closeIconPath.setAttribute('stroke-width', '2');
|
||||
closeIconPath.setAttribute('d', 'M6 18L18 6M6 6l12 12');
|
||||
|
||||
// Append the path to the SVG
|
||||
closeIcon.appendChild(closeIconPath);
|
||||
// Append the SVG to the close button
|
||||
closeButton.appendChild(closeIcon);
|
||||
|
||||
// Append the message and close button to the flash div
|
||||
flashDiv.appendChild(messageDiv);
|
||||
flashDiv.appendChild(closeButton);
|
||||
|
||||
// Append the flash message to the body or a specific flash container
|
||||
document.body.appendChild(flashDiv);
|
||||
|
||||
// Optional: Automatically remove the flash message after 5 seconds
|
||||
setTimeout(() => {
|
||||
flashDiv.remove();
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// Helper function to get flash classes based on type
|
||||
classesForFlash(type) {
|
||||
switch (type) {
|
||||
case 'error':
|
||||
return 'bg-red-100 text-red-700 border-red-300';
|
||||
case 'notice':
|
||||
return 'bg-blue-100 text-blue-700 border-blue-300';
|
||||
default:
|
||||
return 'bg-blue-100 text-blue-700 border-blue-300';
|
||||
}
|
||||
}
|
||||
|
||||
updateMapWithNewSettings(newSettings) {
|
||||
const currentLayerStates = this.getLayerControlStates();
|
||||
|
||||
// Update local state with new settings
|
||||
this.clearFogRadius = parseInt(newSettings.fog_of_war_meters) || 50;
|
||||
this.routeOpacity = parseFloat(newSettings.route_opacity) || 0.6;
|
||||
|
||||
// Preserve existing layer instances if they exist
|
||||
const preserveLayers = {
|
||||
Points: this.markersLayer,
|
||||
Polylines: this.polylinesLayer,
|
||||
Heatmap: this.heatmapLayer,
|
||||
"Fog of War": this.fogOverlay,
|
||||
Areas: this.areasLayer,
|
||||
};
|
||||
|
||||
// Clear all layers except base layers
|
||||
this.map.eachLayer((layer) => {
|
||||
if (!(layer instanceof L.TileLayer)) {
|
||||
this.map.removeLayer(layer);
|
||||
}
|
||||
});
|
||||
|
||||
// Recreate layers only if they don't exist
|
||||
this.markersLayer = preserveLayers.Points || L.layerGroup(this.createMarkersArray(this.markers));
|
||||
this.polylinesLayer = preserveLayers.Polylines || this.createPolylinesLayer(this.markers, this.map, this.timezone, this.routeOpacity);
|
||||
this.heatmapLayer = preserveLayers.Heatmap || L.heatLayer(this.markers.map((element) => [element[0], element[1], 0.2]), { radius: 20 });
|
||||
this.fogOverlay = preserveLayers["Fog of War"] || L.layerGroup();
|
||||
this.areasLayer = preserveLayers.Areas || L.layerGroup();
|
||||
|
||||
const controlsLayer = {
|
||||
Points: this.markersLayer,
|
||||
Polylines: this.polylinesLayer,
|
||||
Heatmap: this.heatmapLayer,
|
||||
"Fog of War": this.fogOverlay,
|
||||
Areas: this.areasLayer,
|
||||
};
|
||||
|
||||
// Remove old control and add the new one
|
||||
if (this.layerControl) {
|
||||
this.map.removeControl(this.layerControl);
|
||||
}
|
||||
this.layerControl = L.control.layers(this.baseMaps(), controlsLayer).addTo(this.map);
|
||||
|
||||
// Redraw areas
|
||||
this.fetchAndDrawAreas(this.apiKey);
|
||||
|
||||
let fogEnabled = false;
|
||||
document.getElementById('fog').style.display = 'none';
|
||||
|
||||
this.map.on('overlayadd', (e) => {
|
||||
if (e.name === 'Fog of War') {
|
||||
fogEnabled = true;
|
||||
document.getElementById('fog').style.display = 'block';
|
||||
this.updateFog(this.markers, this.clearFogRadius);
|
||||
}
|
||||
});
|
||||
|
||||
this.map.on('overlayremove', (e) => {
|
||||
if (e.name === 'Fog of War') {
|
||||
fogEnabled = false;
|
||||
document.getElementById('fog').style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
this.map.on('zoomend moveend', () => {
|
||||
if (fogEnabled) {
|
||||
this.updateFog(this.markers, this.clearFogRadius);
|
||||
}
|
||||
});
|
||||
|
||||
this.addLastMarker(this.map, this.markers);
|
||||
this.addEventListeners();
|
||||
this.initializeDrawControl();
|
||||
this.updatePolylinesOpacity(this.routeOpacity);
|
||||
|
||||
this.map.on('overlayadd', (e) => {
|
||||
if (e.name === 'Areas') {
|
||||
this.map.addControl(this.drawControl);
|
||||
}
|
||||
});
|
||||
|
||||
this.map.on('overlayremove', (e) => {
|
||||
if (e.name === 'Areas') {
|
||||
this.map.removeControl(this.drawControl);
|
||||
}
|
||||
});
|
||||
|
||||
this.addSettingsButton();
|
||||
|
||||
this.applyLayerControlStates(currentLayerStates);
|
||||
}
|
||||
|
||||
getLayerControlStates() {
|
||||
const controls = {};
|
||||
|
||||
this.map.eachLayer((layer) => {
|
||||
const layerName = this.getLayerName(layer);
|
||||
console.log('Layer name:', layerName, 'Layer details:', layer);
|
||||
if (layerName) {
|
||||
controls[layerName] = this.map.hasLayer(layer);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Current layer states:', controls);
|
||||
return controls;
|
||||
}
|
||||
|
||||
getLayerName(layer) {
|
||||
const controlLayers = {
|
||||
Points: this.markersLayer,
|
||||
Polylines: this.polylinesLayer,
|
||||
Heatmap: this.heatmapLayer,
|
||||
"Fog of War": this.fogOverlay,
|
||||
Areas: this.areasLayer,
|
||||
};
|
||||
|
||||
for (const [name, val] of Object.entries(controlLayers)) {
|
||||
if (val && val.hasLayer && layer && val.hasLayer(layer)) // Check if the group layer contains the current layer
|
||||
return name;
|
||||
}
|
||||
|
||||
// Direct instance matching
|
||||
for (const [name, val] of Object.entries(controlLayers)) {
|
||||
if (val === layer) return name;
|
||||
}
|
||||
|
||||
return undefined; // Indicate no matching layer name found
|
||||
}
|
||||
|
||||
|
||||
applyLayerControlStates(states) {
|
||||
const layerControl = {
|
||||
Points: this.markersLayer,
|
||||
Polylines: this.polylinesLayer,
|
||||
Heatmap: this.heatmapLayer,
|
||||
"Fog of War": this.fogOverlay,
|
||||
Areas: this.areasLayer,
|
||||
};
|
||||
|
||||
for (const [name, isVisible] of Object.entries(states)) {
|
||||
const layer = layerControl[name];
|
||||
console.log(`Applying layer state: ${name}, visible: ${isVisible}`);
|
||||
if (isVisible) {
|
||||
if (!this.map.hasLayer(layer)) {
|
||||
console.log(`Adding layer: ${name}`);
|
||||
this.map.addLayer(layer);
|
||||
}
|
||||
} else {
|
||||
if (this.map.hasLayer(layer)) {
|
||||
console.log(`Removing layer: ${name}`);
|
||||
this.map.removeLayer(layer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the layer control reflects the current state
|
||||
this.layerControl.remove();
|
||||
this.layerControl = L.control.layers(this.baseMaps(), layerControl).addTo(this.map);
|
||||
}
|
||||
|
||||
updatePolylinesOpacity(opacity) {
|
||||
this.polylinesLayer.eachLayer((layer) => {
|
||||
if (layer instanceof L.Polyline) {
|
||||
layer.setStyle({ opacity: opacity });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
97
app/views/map/_settings_modals.html.erb
Normal file
97
app/views/map/_settings_modals.html.erb
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
<!-- Put this part before </body> tag -->
|
||||
<input type="checkbox" id="route_opacity_info" class="modal-toggle" />
|
||||
<div class="modal focus:z-99" role="dialog">
|
||||
<div class="modal-box">
|
||||
<h3 class="text-lg font-bold">Route opacity</h3>
|
||||
<p class="py-4">
|
||||
Value in percent.
|
||||
</p>
|
||||
<p class="py-4">
|
||||
This value is the opacity of the route on the map. The value is in percent, and it can be set from 0 to 100. The default value is 100, which means that the route is fully visible. If you set the value to 0, the route will be invisible.
|
||||
</p>
|
||||
</div>
|
||||
<label class="modal-backdrop" for="route_opacity_info">Close</label>
|
||||
</div>
|
||||
|
||||
<input type="checkbox" id="fog_of_war_meters_info" class="modal-toggle" />
|
||||
<div class="modal focus:z-99" role="dialog">
|
||||
<div class="modal-box">
|
||||
<h3 class="text-lg font-bold">Fog of War</h3>
|
||||
<p class="py-4">
|
||||
Value in meters.
|
||||
</p>
|
||||
<p class="py-4">
|
||||
Here you can set the radius of the "cleared" area around a point when Fog of War mode is enabled. The area around the point will be cleared, and the rest of the map will be covered with fog. The cleared area will be a circle with the point as the center and the radius as the value you set here.
|
||||
</p>
|
||||
</div>
|
||||
<label class="modal-backdrop" for="fog_of_war_meters_info">Close</label>
|
||||
</div>
|
||||
|
||||
<input type="checkbox" id="meters_between_routes_info" class="modal-toggle" />
|
||||
<div class="modal focus:z-99" role="dialog">
|
||||
<div class="modal-box">
|
||||
<h3 class="text-lg font-bold">Meters between routes</h3>
|
||||
<p class="py-4">
|
||||
Value in meters.
|
||||
</p>
|
||||
<p class="py-4">
|
||||
Points on the map are connected by lines. This value is the maximum distance between two points to be connected by a line. If the distance between two points is greater than this value, they will not be connected, and the line will not be drawn. This allows to split the route into smaller segments, and to avoid drawing lines between two points that are far from each other.
|
||||
</p>
|
||||
</div>
|
||||
<label class="modal-backdrop" for="meters_between_routes_info">Close</label>
|
||||
</div>
|
||||
|
||||
|
||||
<input type="checkbox" id="minutes_between_routes_info" class="modal-toggle" />
|
||||
<div class="modal focus:z-99" role="dialog">
|
||||
<div class="modal-box">
|
||||
<h3 class="text-lg font-bold">Minutes between routes</h3>
|
||||
<p class="py-4">
|
||||
Value in minutes.
|
||||
</p>
|
||||
<p class="py-4">
|
||||
Points on the map are connected by lines. This value is the maximum time between two points to be connected by a line. If the time between two points is greater than this value, they will not be connected. This allows to split the route into smaller segments, and to avoid drawing lines between two points that are far in time from each other.
|
||||
</p>
|
||||
</div>
|
||||
<label class="modal-backdrop" for="minutes_between_routes_info">Close</label>
|
||||
</div>
|
||||
|
||||
<input type="checkbox" id="time_threshold_minutes_info" class="modal-toggle" />
|
||||
<div class="modal focus:z-99" role="dialog">
|
||||
<div class="modal-box">
|
||||
<h3 class="text-lg font-bold">Visit time threshold</h3>
|
||||
<p class="py-4">
|
||||
Value in minutes.
|
||||
</p>
|
||||
<p class="py-4">
|
||||
This value is the threshold, based on which a visit is calculated. If the time between two consequent points is greater than this value, the visit is considered a new visit. If the time between two points is less than this value, the visit is considered as a continuation of the previous visit.
|
||||
</p>
|
||||
<p class="py-4">
|
||||
For example, if you set this value to 30 minutes, and you have four points with a time difference of 20 minutes between them, they will be considered as one visit. If the time difference between two first points is 20 minutes, and between third and fourth point is 40 minutes, the visit will be split into two visits.
|
||||
</p>
|
||||
<p class="py-4">
|
||||
Default value is 30 minutes.
|
||||
</p>
|
||||
</div>
|
||||
<label class="modal-backdrop" for="time_threshold_minutes_info">Close</label>
|
||||
</div>
|
||||
|
||||
<input type="checkbox" id="merge_threshold_minutes_info" class="modal-toggle" />
|
||||
<div class="modal focus:z-99" role="dialog">
|
||||
<div class="modal-box">
|
||||
<h3 class="text-lg font-bold">Merge threshold</h3>
|
||||
<p class="py-4">
|
||||
Value in minutes.
|
||||
</p>
|
||||
<p class="py-4">
|
||||
This value is the threshold, based on which two visits are merged into one. If the time between two consequent visits is less than this value, the visits are merged into one visit. If the time between two visits is greater than this value, the visits are considered as separate visits.
|
||||
</p>
|
||||
<p class="py-4">
|
||||
For example, if you set this value to 30 minutes, and you have two visits with a time difference of 20 minutes between them, they will be merged into one visit. If the time difference between two visits is 40 minutes, the visits will be considered as separate visits.
|
||||
</p>
|
||||
<p class="py-4">
|
||||
Default value is 15 minutes.
|
||||
</p>
|
||||
</div>
|
||||
<label class="modal-backdrop" for="merge_threshold_minutes_info">Close</label>
|
||||
</div>
|
||||
|
|
@ -42,13 +42,10 @@
|
|||
<div
|
||||
class="w-full"
|
||||
data-api_key="<%= current_user.api_key %>"
|
||||
data-route_opacity="<%= current_user.settings['route_opacity'] %>"
|
||||
data-user_settings=<%= current_user.settings.to_json %>
|
||||
data-controller="maps"
|
||||
data-coordinates="<%= @coordinates %>"
|
||||
data-timezone="<%= Rails.configuration.time_zone %>"
|
||||
data-meters_between_routes="<%= current_user.settings['meters_between_routes'] %>"
|
||||
data-minutes_between_routes="<%= current_user.settings['minutes_between_routes'] %>"
|
||||
data-fog_of_war_meters="<%= current_user.settings['fog_of_war_meters'] %>">
|
||||
data-timezone="<%= Rails.configuration.time_zone %>">
|
||||
<div data-maps-target="container" class="h-[25rem] w-auto min-h-screen">
|
||||
<div id="fog" class="fog"></div>
|
||||
</div>
|
||||
|
|
@ -60,4 +57,4 @@
|
|||
<%= render 'shared/right_sidebar' %>
|
||||
</div>
|
||||
|
||||
|
||||
<%= render 'map/settings_modals' %>
|
||||
|
|
|
|||
|
|
@ -7,163 +7,6 @@
|
|||
<div class="card flex-shrink-0 w-full max-w-sm shadow-2xl bg-base-100 px-5 py-5 mx-5">
|
||||
<h2 class="text-2xl font-bold">Edit your Dawarich settings!</h1>
|
||||
<%= form_for :settings, url: settings_path, method: :patch, data: { turbo_method: :patch, turbo: false } do |f| %>
|
||||
<div class="form-control my-2">
|
||||
<%= f.label :meters_between_routes do %>
|
||||
Meters between routes
|
||||
|
||||
<!-- The button to open modal -->
|
||||
<label for="meters_between_routes_info" class="btn">?</label>
|
||||
|
||||
<!-- Put this part before </body> tag -->
|
||||
<input type="checkbox" id="meters_between_routes_info" class="modal-toggle" />
|
||||
<div class="modal" role="dialog">
|
||||
<div class="modal-box">
|
||||
<h3 class="text-lg font-bold">Meters between routes</h3>
|
||||
<p class="py-4">
|
||||
Value in meters.
|
||||
</p>
|
||||
<p class="py-4">
|
||||
Points on the map are connected by lines. This value is the maximum distance between two points to be connected by a line. If the distance between two points is greater than this value, they will not be connected, and the line will not be drawn. This allows to split the route into smaller segments, and to avoid drawing lines between two points that are far from each other.
|
||||
</p>
|
||||
</div>
|
||||
<label class="modal-backdrop" for="meters_between_routes_info">Close</label>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= f.number_field :meters_between_routes, value: current_user.settings['meters_between_routes'], class: "input input-bordered" %>
|
||||
</div>
|
||||
|
||||
<div class="form-control my-2">
|
||||
<%= f.label :minutes_between_routes do %>
|
||||
Minutes between routes
|
||||
|
||||
<!-- The button to open modal -->
|
||||
<label for="minutes_between_routes_info" class="btn">?</label>
|
||||
|
||||
<!-- Put this part before </body> tag -->
|
||||
<input type="checkbox" id="minutes_between_routes_info" class="modal-toggle" />
|
||||
<div class="modal" role="dialog">
|
||||
<div class="modal-box">
|
||||
<h3 class="text-lg font-bold">Minutes between routes</h3>
|
||||
<p class="py-4">
|
||||
Value in minutes.
|
||||
</p>
|
||||
<p class="py-4">
|
||||
Points on the map are connected by lines. This value is the maximum time between two points to be connected by a line. If the time between two points is greater than this value, they will not be connected. This allows to split the route into smaller segments, and to avoid drawing lines between two points that are far in time from each other.
|
||||
</p>
|
||||
</div>
|
||||
<label class="modal-backdrop" for="minutes_between_routes_info">Close</label>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= f.number_field :minutes_between_routes, value: current_user.settings['minutes_between_routes'], class: "input input-bordered" %>
|
||||
</div>
|
||||
<div class="form-control my-2">
|
||||
<%= f.label :fog_of_war_meters do %>
|
||||
Fog of War meters
|
||||
|
||||
<!-- The button to open modal -->
|
||||
<label for="fog_of_war_meters_info" class="btn">?</label>
|
||||
|
||||
<!-- Put this part before </body> tag -->
|
||||
<input type="checkbox" id="fog_of_war_meters_info" class="modal-toggle" />
|
||||
<div class="modal" role="dialog">
|
||||
<div class="modal-box">
|
||||
<h3 class="text-lg font-bold">Fog of War</h3>
|
||||
<p class="py-4">
|
||||
Value in meters.
|
||||
</p>
|
||||
<p class="py-4">
|
||||
Here you can set the radius of the "cleared" area around a point when Fog of War mode is enabled. The area around the point will be cleared, and the rest of the map will be covered with fog. The cleared area will be a circle with the point as the center and the radius as the value you set here.
|
||||
</p>
|
||||
</div>
|
||||
<label class="modal-backdrop" for="fog_of_war_meters_info">Close</label>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= f.number_field :fog_of_war_meters, value: current_user.settings['fog_of_war_meters'], class: "input input-bordered" %>
|
||||
</div>
|
||||
<div class="form-control my-2">
|
||||
<%= f.label :time_threshold_minutes do %>
|
||||
Visit time threshold
|
||||
|
||||
<!-- The button to open modal -->
|
||||
<label for="time_threshold_minutes_info" class="btn">?</label>
|
||||
|
||||
<!-- Put this part before </body> tag -->
|
||||
<input type="checkbox" id="time_threshold_minutes_info" class="modal-toggle" />
|
||||
<div class="modal" role="dialog">
|
||||
<div class="modal-box">
|
||||
<h3 class="text-lg font-bold">Visit time threshold</h3>
|
||||
<p class="py-4">
|
||||
Value in minutes.
|
||||
</p>
|
||||
<p class="py-4">
|
||||
This value is the threshold, based on which a visit is calculated. If the time between two consequent points is greater than this value, the visit is considered a new visit. If the time between two points is less than this value, the visit is considered as a continuation of the previous visit.
|
||||
</p>
|
||||
<p class="py-4">
|
||||
For example, if you set this value to 30 minutes, and you have four points with a time difference of 20 minutes between them, they will be considered as one visit. If the time difference between two first points is 20 minutes, and between third and fourth point is 40 minutes, the visit will be split into two visits.
|
||||
</p>
|
||||
<p class="py-4">
|
||||
Default value is 30 minutes.
|
||||
</p>
|
||||
</div>
|
||||
<label class="modal-backdrop" for="time_threshold_minutes_info">Close</label>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= f.number_field :time_threshold_minutes, value: current_user.settings['time_threshold_minutes'], class: "input input-bordered" %>
|
||||
</div>
|
||||
<div class="form-control my-2">
|
||||
<%= f.label :merge_threshold_minutes do %>
|
||||
Merge time threshold
|
||||
|
||||
<!-- The button to open modal -->
|
||||
<label for="merge_threshold_minutes_info" class="btn">?</label>
|
||||
|
||||
<!-- Put this part before </body> tag -->
|
||||
<input type="checkbox" id="merge_threshold_minutes_info" class="modal-toggle" />
|
||||
<div class="modal" role="dialog">
|
||||
<div class="modal-box">
|
||||
<h3 class="text-lg font-bold">Merge threshold</h3>
|
||||
<p class="py-4">
|
||||
Value in minutes.
|
||||
</p>
|
||||
<p class="py-4">
|
||||
This value is the threshold, based on which two visits are merged into one. If the time between two consequent visits is less than this value, the visits are merged into one visit. If the time between two visits is greater than this value, the visits are considered as separate visits.
|
||||
</p>
|
||||
<p class="py-4">
|
||||
For example, if you set this value to 30 minutes, and you have two visits with a time difference of 20 minutes between them, they will be merged into one visit. If the time difference between two visits is 40 minutes, the visits will be considered as separate visits.
|
||||
</p>
|
||||
<p class="py-4">
|
||||
Default value is 15 minutes.
|
||||
</p>
|
||||
</div>
|
||||
<label class="modal-backdrop" for="merge_threshold_minutes_info">Close</label>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= f.number_field :merge_threshold_minutes, value: current_user.settings['merge_threshold_minutes'], class: "input input-bordered" %>
|
||||
</div>
|
||||
<div class="form-control my-2">
|
||||
<%= f.label :route_opacity do %>
|
||||
Route opacity percent
|
||||
|
||||
<!-- The button to open modal -->
|
||||
<label for="route_opacity_info" class="btn">?</label>
|
||||
|
||||
<!-- Put this part before </body> tag -->
|
||||
<input type="checkbox" id="route_opacity_info" class="modal-toggle" />
|
||||
<div class="modal" role="dialog">
|
||||
<div class="modal-box">
|
||||
<h3 class="text-lg font-bold">Route opacity</h3>
|
||||
<p class="py-4">
|
||||
Value in percent.
|
||||
</p>
|
||||
<p class="py-4">
|
||||
This value is the opacity of the route on the map. The value is in percent, and it can be set from 0 to 100. The default value is 100, which means that the route is fully visible. If you set the value to 0, the route will be invisible.
|
||||
</p>
|
||||
</div>
|
||||
<label class="modal-backdrop" for="route_opacity_info">Close</label>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= f.number_field :route_opacity, value: current_user.settings['route_opacity'], class: "input input-bordered" %>
|
||||
</div>
|
||||
<div class="form-control my-2">
|
||||
<%= f.label :immich_url %>
|
||||
<%= f.text_field :immich_url, value: current_user.settings['immich_url'], class: "input input-bordered", placeholder: 'http://192.168.0.1:2283' %>
|
||||
|
|
|
|||
|
|
@ -56,10 +56,13 @@ Rails.application.routes.draw do
|
|||
|
||||
namespace :api do
|
||||
namespace :v1 do
|
||||
resources :areas, only: %i[index create update destroy]
|
||||
resources :points, only: %i[index destroy]
|
||||
resources :visits, only: %i[update]
|
||||
resources :stats, only: :index
|
||||
patch 'settings', to: 'settings#update'
|
||||
get 'settings', to: 'settings#index'
|
||||
|
||||
resources :areas, only: %i[index create update destroy]
|
||||
resources :points, only: %i[index destroy]
|
||||
resources :visits, only: %i[update]
|
||||
resources :stats, only: :index
|
||||
|
||||
namespace :overland do
|
||||
resources :batches, only: :create
|
||||
|
|
|
|||
|
|
@ -8,6 +8,17 @@ FactoryBot.define do
|
|||
|
||||
password { SecureRandom.hex(8) }
|
||||
|
||||
settings do
|
||||
{
|
||||
route_opacity: '0.5',
|
||||
meters_between_routes: '100',
|
||||
minutes_between_routes: '100',
|
||||
fog_of_war_meters: '100',
|
||||
time_threshold_minutes: '100',
|
||||
merge_threshold_minutes: '100'
|
||||
}
|
||||
end
|
||||
|
||||
trait :admin do
|
||||
admin { true }
|
||||
end
|
||||
|
|
|
|||
48
spec/requests/api/v1/settings_spec.rb
Normal file
48
spec/requests/api/v1/settings_spec.rb
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Api::V1::Settings', type: :request do
|
||||
let!(:user) { create(:user) }
|
||||
let!(:api_key) { user.api_key }
|
||||
|
||||
describe 'PATCH /update' do
|
||||
context 'with valid request' do
|
||||
it 'returns http success' do
|
||||
patch "/api/v1/settings?api_key=#{api_key}", params: { settings: { route_opacity: 0.3 } }
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
|
||||
it 'updates the settings' do
|
||||
patch "/api/v1/settings?api_key=#{api_key}", params: { settings: { route_opacity: 0.3 } }
|
||||
|
||||
expect(user.reload.settings['route_opacity'].to_f).to eq(0.3)
|
||||
end
|
||||
|
||||
it 'returns the updated settings' do
|
||||
patch "/api/v1/settings?api_key=#{api_key}", params: { settings: { route_opacity: 0.3 } }
|
||||
|
||||
expect(response.parsed_body['settings']['route_opacity'].to_f).to eq(0.3)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid request' do
|
||||
before do
|
||||
allow_any_instance_of(User).to receive(:save).and_return(false)
|
||||
end
|
||||
|
||||
it 'returns http unprocessable entity' do
|
||||
patch "/api/v1/settings?api_key=#{api_key}", params: { settings: { route_opacity: 'invalid' } }
|
||||
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
end
|
||||
|
||||
it 'returns an error message' do
|
||||
patch "/api/v1/settings?api_key=#{api_key}", params: { settings: { route_opacity: 'invalid' } }
|
||||
|
||||
expect(response.parsed_body['message']).to eq('Something went wrong')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'swagger_helper'
|
||||
|
||||
describe 'Points API', type: :request do
|
||||
path '/api/v1/points' do
|
||||
get 'Retrieves all points' do
|
||||
tags 'Points'
|
||||
produces 'application/json'
|
||||
parameter name: :api_key, in: :query, type: :string, required: true, description: 'API Key'
|
||||
parameter name: :start_at, in: :query, type: :string,
|
||||
description: 'Start date (i.e. 2024-02-03T13:00:03Z or 2024-02-03)'
|
||||
parameter name: :end_at, in: :query, type: :string,
|
||||
description: 'End date (i.e. 2024-02-03T13:00:03Z or 2024-02-03)'
|
||||
response '200', 'points found' do
|
||||
schema type: :array,
|
||||
items: {
|
||||
type: :object,
|
||||
properties: {
|
||||
id: { type: :integer },
|
||||
battery_status: { type: :number },
|
||||
ping: { type: :number },
|
||||
battery: { type: :number },
|
||||
tracker_id: { type: :string },
|
||||
topic: { type: :string },
|
||||
altitude: { type: :number },
|
||||
longitude: { type: :number },
|
||||
velocity: { type: :number },
|
||||
trigger: { type: :string },
|
||||
bssid: { type: :string },
|
||||
ssid: { type: :string },
|
||||
connection: { type: :string },
|
||||
vertical_accuracy: { type: :number },
|
||||
accuracy: { type: :number },
|
||||
timestamp: { type: :number },
|
||||
latitude: { type: :number },
|
||||
mode: { type: :number },
|
||||
inrids: { type: :array },
|
||||
in_regions: { type: :array },
|
||||
raw_data: { type: :string },
|
||||
import_id: { type: :string },
|
||||
city: { type: :string },
|
||||
country: { type: :string },
|
||||
created_at: { type: :string },
|
||||
updated_at: { type: :string },
|
||||
user_id: { type: :integer },
|
||||
geodata: { type: :string },
|
||||
visit_id: { type: :string }
|
||||
}
|
||||
}
|
||||
|
||||
let(:user) { create(:user) }
|
||||
let(:areas) { create_list(:area, 3, user:) }
|
||||
let(:api_key) { user.api_key }
|
||||
let(:start_at) { Time.zone.now - 1.day }
|
||||
let(:end_at) { Time.zone.now }
|
||||
let(:points) { create_list(:point, 10, user:, timestamp: 2.hours.ago) }
|
||||
|
||||
run_test!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
path '/api/v1/points/{id}' do
|
||||
delete 'Deletes a point' do
|
||||
tags 'Points'
|
||||
produces 'application/json'
|
||||
parameter name: :api_key, in: :query, type: :string, required: true, description: 'API Key'
|
||||
parameter name: :id, in: :path, type: :string, required: true, description: 'Point ID'
|
||||
|
||||
response '200', 'point deleted' do
|
||||
let(:user) { create(:user) }
|
||||
let(:point) { create(:point, user:) }
|
||||
let(:api_key) { user.api_key }
|
||||
let(:id) { point.id }
|
||||
|
||||
run_test!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
72
spec/swagger/api/v1/settings_controller_spec.rb
Normal file
72
spec/swagger/api/v1/settings_controller_spec.rb
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'swagger_helper'
|
||||
|
||||
describe 'Settings API', type: :request do
|
||||
path '/api/v1/settings' do
|
||||
patch 'Updates user settings' do
|
||||
request_body_example value: {
|
||||
'settings': {
|
||||
'route_opacity': 0.3,
|
||||
'meters_between_routes': 100,
|
||||
'minutes_between_routes': 100,
|
||||
'fog_of_war_meters': 100,
|
||||
'time_threshold_minutes': 100,
|
||||
'merge_threshold_minutes': 100
|
||||
}
|
||||
}
|
||||
tags 'Settings'
|
||||
consumes 'application/json'
|
||||
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 }
|
||||
},
|
||||
optional: %w[route_opacity meters_between_routes minutes_between_routes fog_of_war_meters
|
||||
time_threshold_minutes merge_threshold_minutes]
|
||||
}
|
||||
parameter name: :api_key, in: :query, type: :string, required: true, description: 'API Key'
|
||||
response '200', 'settings updated' do
|
||||
let(:settings) { { settings: { route_opacity: 0.3 } } }
|
||||
let(:api_key) { create(:user).api_key }
|
||||
|
||||
run_test!
|
||||
end
|
||||
end
|
||||
|
||||
get 'Retrieves user settings' do
|
||||
tags 'Settings'
|
||||
produces 'application/json'
|
||||
parameter name: :api_key, in: :query, type: :string, required: true, description: 'API Key'
|
||||
response '200', 'settings found' do
|
||||
schema type: :object,
|
||||
properties: {
|
||||
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 }
|
||||
},
|
||||
required: %w[route_opacity meters_between_routes minutes_between_routes fog_of_war_meters
|
||||
time_threshold_minutes merge_threshold_minutes]
|
||||
}
|
||||
}
|
||||
|
||||
let(:user) { create(:user) }
|
||||
let(:settings) { { settings: user.settings } }
|
||||
let(:api_key) { user.api_key }
|
||||
|
||||
run_test!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -304,11 +304,11 @@ paths:
|
|||
isorcv: '2024-02-03T13:00:03Z'
|
||||
isotst: '2024-02-03T13:00:03Z'
|
||||
disptst: '2024-02-03 13:00:03'
|
||||
"/api/v1/points":
|
||||
get:
|
||||
summary: Retrieves all points
|
||||
"/api/v1/settings":
|
||||
patch:
|
||||
summary: Updates user settings
|
||||
tags:
|
||||
- Points
|
||||
- Settings
|
||||
parameters:
|
||||
- name: api_key
|
||||
in: query
|
||||
|
|
@ -316,105 +316,86 @@ paths:
|
|||
description: API Key
|
||||
schema:
|
||||
type: string
|
||||
- name: start_at
|
||||
responses:
|
||||
'200':
|
||||
description: settings updated
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
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
|
||||
optional:
|
||||
- route_opacity
|
||||
- meters_between_routes
|
||||
- minutes_between_routes
|
||||
- fog_of_war_meters
|
||||
- time_threshold_minutes
|
||||
- merge_threshold_minutes
|
||||
examples:
|
||||
'0':
|
||||
summary: Updates user settings
|
||||
value:
|
||||
settings:
|
||||
route_opacity: 0.3
|
||||
meters_between_routes: 100
|
||||
minutes_between_routes: 100
|
||||
fog_of_war_meters: 100
|
||||
time_threshold_minutes: 100
|
||||
merge_threshold_minutes: 100
|
||||
get:
|
||||
summary: Retrieves user settings
|
||||
tags:
|
||||
- Settings
|
||||
parameters:
|
||||
- name: api_key
|
||||
in: query
|
||||
description: Start date (i.e. 2024-02-03T13:00:03Z or 2024-02-03)
|
||||
schema:
|
||||
type: string
|
||||
- name: end_at
|
||||
in: query
|
||||
description: End date (i.e. 2024-02-03T13:00:03Z or 2024-02-03)
|
||||
required: true
|
||||
description: API Key
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: points found
|
||||
description: settings found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
battery_status:
|
||||
type: number
|
||||
ping:
|
||||
type: number
|
||||
battery:
|
||||
type: number
|
||||
tracker_id:
|
||||
type: string
|
||||
topic:
|
||||
type: string
|
||||
altitude:
|
||||
type: number
|
||||
longitude:
|
||||
type: number
|
||||
velocity:
|
||||
type: number
|
||||
trigger:
|
||||
type: string
|
||||
bssid:
|
||||
type: string
|
||||
ssid:
|
||||
type: string
|
||||
connection:
|
||||
type: string
|
||||
vertical_accuracy:
|
||||
type: number
|
||||
accuracy:
|
||||
type: number
|
||||
timestamp:
|
||||
type: number
|
||||
latitude:
|
||||
type: number
|
||||
mode:
|
||||
type: number
|
||||
inrids:
|
||||
type: array
|
||||
in_regions:
|
||||
type: array
|
||||
raw_data:
|
||||
type: string
|
||||
import_id:
|
||||
type: string
|
||||
city:
|
||||
type: string
|
||||
country:
|
||||
type: string
|
||||
created_at:
|
||||
type: string
|
||||
updated_at:
|
||||
type: string
|
||||
user_id:
|
||||
type: integer
|
||||
geodata:
|
||||
type: string
|
||||
visit_id:
|
||||
type: string
|
||||
"/api/v1/points/{id}":
|
||||
delete:
|
||||
summary: Deletes a point
|
||||
tags:
|
||||
- Points
|
||||
parameters:
|
||||
- name: api_key
|
||||
in: query
|
||||
required: true
|
||||
description: API Key
|
||||
schema:
|
||||
type: string
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: Point ID
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: point deleted
|
||||
type: object
|
||||
properties:
|
||||
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
|
||||
required:
|
||||
- route_opacity
|
||||
- meters_between_routes
|
||||
- minutes_between_routes
|
||||
- fog_of_war_meters
|
||||
- time_threshold_minutes
|
||||
- merge_threshold_minutes
|
||||
"/api/v1/stats":
|
||||
get:
|
||||
summary: Retrieves all stats
|
||||
|
|
|
|||
Loading…
Reference in a new issue