diff --git a/.app_version b/.app_version
index 082b4352..158ef578 100644
--- a/.app_version
+++ b/.app_version
@@ -1 +1 @@
-0.19.7
+0.19.8
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ce3bb1f3..17870c4f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,22 @@ 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.19.8 - 2024-12-16
+
+### Added
+
+- `GET /api/v1/points/tracked_months` endpoint added to get list of tracked years and months.
+- `GET /api/v1/countries/visited_cities` endpoint added to get list of visited cities.
+
+### Fixed
+
+- A point popup is no longer closes when hovering over a polyline. #536
+
+### Changed
+
+- Months and years navigation is moved to a map panel on the right side of the map.
+- List of visited cities is now being shown in a map panel on the right side of the map.
+
# 0.19.7 - 2024-12-11
### Fixed
diff --git a/app/controllers/api/v1/countries/visited_cities_controller.rb b/app/controllers/api/v1/countries/visited_cities_controller.rb
new file mode 100644
index 00000000..85e53f7d
--- /dev/null
+++ b/app/controllers/api/v1/countries/visited_cities_controller.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class Api::V1::Countries::VisitedCitiesController < ApiController
+ before_action :validate_params
+
+ def index
+ start_at = DateTime.parse(params[:start_at]).to_i
+ end_at = DateTime.parse(params[:end_at]).to_i
+
+ points = current_api_user
+ .tracked_points
+ .where(timestamp: start_at..end_at)
+
+ render json: { data: CountriesAndCities.new(points).call }
+ end
+
+ private
+
+ def required_params
+ %i[start_at end_at]
+ end
+end
diff --git a/app/controllers/api/v1/points/tracked_months_controller.rb b/app/controllers/api/v1/points/tracked_months_controller.rb
new file mode 100644
index 00000000..cd430879
--- /dev/null
+++ b/app/controllers/api/v1/points/tracked_months_controller.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class Api::V1::Points::TrackedMonthsController < ApiController
+ def index
+ render json: current_api_user.years_tracked
+ end
+end
diff --git a/app/controllers/api_controller.rb b/app/controllers/api_controller.rb
index 8e2b43e2..934cdc6b 100644
--- a/app/controllers/api_controller.rb
+++ b/app/controllers/api_controller.rb
@@ -15,4 +15,20 @@ class ApiController < ApplicationController
def current_api_user
@current_api_user ||= User.find_by(api_key: params[:api_key])
end
+
+ def validate_params
+ missing_params = required_params.select { |param| params[param].blank? }
+
+ if missing_params.any?
+ render json: {
+ error: "Missing required parameters: #{missing_params.join(', ')}"
+ }, status: :bad_request and return
+ end
+
+ params.permit(*required_params)
+ end
+
+ def required_params
+ []
+ end
end
diff --git a/app/controllers/stats_controller.rb b/app/controllers/stats_controller.rb
index 809f1d98..2305a44e 100644
--- a/app/controllers/stats_controller.rb
+++ b/app/controllers/stats_controller.rb
@@ -17,8 +17,10 @@ class StatsController < ApplicationController
def update
current_user.years_tracked.each do |year|
- (1..12).each do |month|
- Stats::CalculatingJob.perform_later(current_user.id, year, month)
+ year[:months].each do |month|
+ Stats::CalculatingJob.perform_later(
+ current_user.id, year[:year], Date::ABBR_MONTHNAMES.index(month)
+ )
end
end
diff --git a/app/javascript/controllers/maps_controller.js b/app/javascript/controllers/maps_controller.js
index 9d255e70..2cbf4a58 100644
--- a/app/javascript/controllers/maps_controller.js
+++ b/app/javascript/controllers/maps_controller.js
@@ -32,6 +32,8 @@ export default class extends Controller {
settingsButtonAdded = false;
layerControl = null;
+ visitedCitiesCache = new Map();
+ trackedMonthsCache = null;
connect() {
console.log("Map controller connected");
@@ -171,12 +173,37 @@ export default class extends Controller {
if (this.liveMapEnabled) {
this.setupSubscription();
}
+
+ // Add the toggle panel button
+ this.addTogglePanelButton();
+
+ // Check if we should open the panel based on localStorage or URL params
+ const urlParams = new URLSearchParams(window.location.search);
+ const isPanelOpen = localStorage.getItem('mapPanelOpen') === 'true';
+ const hasDateParams = urlParams.has('start_at') && urlParams.has('end_at');
+
+ // Always create the panel first
+ this.toggleRightPanel();
+
+ // Then hide it if it shouldn't be open
+ if (!isPanelOpen && !hasDateParams) {
+ const panel = document.querySelector('.leaflet-right-panel');
+ if (panel) {
+ panel.style.display = 'none';
+ localStorage.setItem('mapPanelOpen', 'false');
+ }
+ }
}
disconnect() {
if (this.handleDeleteClick) {
document.removeEventListener('click', this.handleDeleteClick);
}
+ // Store panel state before disconnecting
+ if (this.rightPanel) {
+ const finalState = document.querySelector('.leaflet-right-panel').style.display !== 'none' ? 'true' : 'false';
+ localStorage.setItem('mapPanelOpen', finalState);
+ }
this.map.remove();
}
@@ -904,8 +931,385 @@ export default class extends Controller {
${photo.type === 'video' ? '🎥 Video' : '📷 Photo'}
`;
- marker.bindPopup(popupContent);
+ marker.bindPopup(popupContent, { autoClose: false });
this.photoMarkers.addLayer(marker);
}
+
+ addTogglePanelButton() {
+ const TogglePanelControl = L.Control.extend({
+ onAdd: (map) => {
+ const button = L.DomUtil.create('button', 'toggle-panel-button');
+ button.innerHTML = '📅';
+
+ button.style.backgroundColor = 'white';
+ button.style.width = '48px';
+ button.style.height = '48px';
+ button.style.border = 'none';
+ 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 panel on button click
+ L.DomEvent.on(button, 'click', () => {
+ this.toggleRightPanel();
+ });
+
+ return button;
+ }
+ });
+
+ // Add the control to the map
+ this.map.addControl(new TogglePanelControl({ position: 'topright' }));
+ }
+
+ toggleRightPanel() {
+ if (this.rightPanel) {
+ const panel = document.querySelector('.leaflet-right-panel');
+ if (panel) {
+ if (panel.style.display === 'none') {
+ panel.style.display = 'block';
+ localStorage.setItem('mapPanelOpen', 'true');
+ } else {
+ panel.style.display = 'none';
+ localStorage.setItem('mapPanelOpen', 'false');
+ }
+ return;
+ }
+ }
+
+ this.rightPanel = L.control({ position: 'topright' });
+
+ this.rightPanel.onAdd = () => {
+ const div = L.DomUtil.create('div', 'leaflet-right-panel');
+ const allMonths = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
+
+ // Get current date from URL query parameters
+ const urlParams = new URLSearchParams(window.location.search);
+ const startDate = urlParams.get('start_at');
+ const currentYear = startDate
+ ? new Date(startDate).getFullYear().toString()
+ : new Date().getFullYear().toString();
+ const currentMonth = startDate
+ ? allMonths[new Date(startDate).getMonth()]
+ : allMonths[new Date().getMonth()];
+
+ // Initially create select with loading state and current year if available
+ div.innerHTML = `
+
-
+
<%= form_with url: map_path(import_id: params[:import_id]), method: :get do |f| %>
@@ -58,10 +58,6 @@
-
-
- <%= render 'shared/right_sidebar' %>
-
<%= render 'map/settings_modals' %>
diff --git a/app/views/shared/_right_sidebar.html.erb b/app/views/shared/_right_sidebar.html.erb
deleted file mode 100644
index 797adaeb..00000000
--- a/app/views/shared/_right_sidebar.html.erb
+++ /dev/null
@@ -1,64 +0,0 @@
-<%= sidebar_distance(@distance) %> <%= sidebar_points(@points) %>
-
-
-
-
Select year
-
- <% current_user.years_tracked.each do |year| %>
- - <%= link_to year, map_url(year_timespan(year).merge(year: year, import_id: params[:import_id])) %>
- <% end %>
-
-
-
- <% @years.each do |year| %>
-
- <%= year %>
-
-
-
- <% (1..12).to_a.each_slice(3) do |months| %>
- <% months.each do |month_number| %>
- <% if past?(year, month_number) && points_exist?(year, month_number, current_user) %>
- <%= link_to Date::ABBR_MONTHNAMES[month_number], map_url(timespan(month_number, year).merge(import_id: params[:import_id])), class: 'btn btn-default' %>
- <% else %>
-
<%= Date::ABBR_MONTHNAMES[month_number] %>
- <% end %>
- <% end %>
- <% end %>
-
- <% end %>
-
-
-<% if REVERSE_GEOCODING_ENABLED && @countries_and_cities&.any? %>
-
-
Countries and cities
- <% @countries_and_cities.each do |country| %>
- <% next if country[:cities].empty? %>
-
-
- <%= country[:country] %> (<%= country[:cities].count %> cities)
-
-
- <% end %>
-<% end %>
diff --git a/config/routes.rb b/config/routes.rb
index 02f70e8c..4478d7db 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -79,6 +79,11 @@ Rails.application.routes.draw do
namespace :countries do
resources :borders, only: :index
+ resources :visited_cities, only: :index
+ end
+
+ namespace :points do
+ get 'tracked_months', to: 'tracked_months#index'
end
resources :photos, only: %i[index] do
diff --git a/db/migrate/[timestamp]_add_index_to_points_timestamp.rb b/db/migrate/[timestamp]_add_index_to_points_timestamp.rb
deleted file mode 100644
index 8e4bc3fa..00000000
--- a/db/migrate/[timestamp]_add_index_to_points_timestamp.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-# frozen_string_literal: true
-
-class AddIndexToPointsTimestamp < ActiveRecord::Migration[7.2]
- disable_ddl_transaction!
-
- def change
- add_index :points, %i[user_id timestamp], algorithm: :concurrently
- end
-end
diff --git a/spec/models/stat_spec.rb b/spec/models/stat_spec.rb
index ae65afd2..af8873b6 100644
--- a/spec/models/stat_spec.rb
+++ b/spec/models/stat_spec.rb
@@ -13,44 +13,6 @@ RSpec.describe Stat, type: :model do
let(:year) { 2021 }
let(:user) { create(:user) }
- describe '.year_cities_and_countries' do
- subject { described_class.year_cities_and_countries(year, user) }
-
- let(:timestamp) { DateTime.new(year, 1, 1, 0, 0, 0) }
-
- before do
- stub_const('MIN_MINUTES_SPENT_IN_CITY', 60)
- end
-
- context 'when there are points' do
- let!(:points) do
- [
- create(:point, user:, city: 'Berlin', country: 'Germany', timestamp:),
- create(:point, user:, city: 'Berlin', country: 'Germany', timestamp: timestamp + 10.minutes),
- create(:point, user:, city: 'Berlin', country: 'Germany', timestamp: timestamp + 20.minutes),
- create(:point, user:, city: 'Berlin', country: 'Germany', timestamp: timestamp + 30.minutes),
- create(:point, user:, city: 'Berlin', country: 'Germany', timestamp: timestamp + 40.minutes),
- create(:point, user:, city: 'Berlin', country: 'Germany', timestamp: timestamp + 50.minutes),
- create(:point, user:, city: 'Berlin', country: 'Germany', timestamp: timestamp + 60.minutes),
- create(:point, user:, city: 'Berlin', country: 'Germany', timestamp: timestamp + 70.minutes),
- create(:point, user:, city: 'Brugges', country: 'Belgium', timestamp: timestamp + 80.minutes),
- create(:point, user:, city: 'Brugges', country: 'Belgium', timestamp: timestamp + 90.minutes)
- ]
- end
-
- it 'returns countries and cities' do
- # User spent only 20 minutes in Brugges, so it should not be included
- expect(subject).to eq(countries: 2, cities: 1)
- end
- end
-
- context 'when there are no points' do
- it 'returns countries and cities' do
- expect(subject).to eq(countries: 0, cities: 0)
- end
- end
- end
-
describe '#distance_by_day' do
subject { stat.distance_by_day }
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 26bd7e27..a1059d0a 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -117,10 +117,8 @@ RSpec.describe User, type: :model do
describe '#years_tracked' do
let!(:points) { create_list(:point, 3, user:, timestamp: DateTime.new(2024, 1, 1, 5, 0, 0)) }
- subject { user.years_tracked }
-
it 'returns years tracked' do
- expect(subject).to eq([2024])
+ expect(user.years_tracked).to eq([{ year: 2024, months: ['Jan'] }])
end
end
end
diff --git a/spec/requests/api/v1/countries/visited_cities_spec.rb b/spec/requests/api/v1/countries/visited_cities_spec.rb
new file mode 100644
index 00000000..65dbab07
--- /dev/null
+++ b/spec/requests/api/v1/countries/visited_cities_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe 'Api::V1::Countries::VisitedCities', type: :request do
+ describe 'GET /index' do
+ let(:user) { create(:user) }
+ let(:start_at) { '2023-01-01' }
+ let(:end_at) { '2023-12-31' }
+
+ it 'returns visited cities' do
+ get "/api/v1/countries/visited_cities?api_key=#{user.api_key}&start_at=#{start_at}&end_at=#{end_at}"
+
+ expect(response).to have_http_status(:ok)
+ end
+ end
+end
diff --git a/spec/requests/api/v1/points/tracked_months_spec.rb b/spec/requests/api/v1/points/tracked_months_spec.rb
new file mode 100644
index 00000000..e654fc15
--- /dev/null
+++ b/spec/requests/api/v1/points/tracked_months_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe 'Api::V1::Points::TrackedMonths', type: :request do
+ describe 'GET /index' do
+ let(:user) { create(:user) }
+
+ it 'returns tracked months' do
+ get "/api/v1/points/tracked_months?api_key=#{user.api_key}"
+
+ expect(response).to have_http_status(:ok)
+ end
+ end
+end
diff --git a/spec/requests/stats_spec.rb b/spec/requests/stats_spec.rb
index 40c19823..224cb3b5 100644
--- a/spec/requests/stats_spec.rb
+++ b/spec/requests/stats_spec.rb
@@ -55,13 +55,13 @@ RSpec.describe '/stats', type: :request do
let(:stat) { create(:stat, user:, year: 2024) }
it 'enqueues Stats::CalculatingJob for each tracked year and month' do
- allow(user).to receive(:years_tracked).and_return([2024])
+ allow(user).to receive(:years_tracked).and_return([{ year: 2024, months: %w[Jan Feb] }])
post stats_url
- (1..12).each do |month|
- expect(Stats::CalculatingJob).to have_been_enqueued.with(user.id, 2024, month)
- end
+ expect(Stats::CalculatingJob).to have_been_enqueued.with(user.id, 2024, 1)
+ expect(Stats::CalculatingJob).to have_been_enqueued.with(user.id, 2024, 2)
+ expect(Stats::CalculatingJob).to_not have_been_enqueued.with(user.id, 2024, 3)
end
end
end
diff --git a/spec/services/countries_and_cities_spec.rb b/spec/services/countries_and_cities_spec.rb
index 99e7664f..636823e5 100644
--- a/spec/services/countries_and_cities_spec.rb
+++ b/spec/services/countries_and_cities_spec.rb
@@ -36,13 +36,18 @@ RSpec.describe CountriesAndCities do
it 'returns countries and cities' do
expect(countries_and_cities).to eq(
[
- {
- cities: [{ city: 'Berlin', points: 8, timestamp: 1609463400, stayed_for: 70 }],
- country: 'Germany'
- },
- {
- cities: [], country: 'Belgium'
- }
+ CountriesAndCities::CountryData.new(
+ country: 'Germany',
+ cities: [
+ CountriesAndCities::CityData.new(
+ city: 'Berlin', points: 8, timestamp: 1_609_463_400, stayed_for: 70
+ )
+ ]
+ ),
+ CountriesAndCities::CountryData.new(
+ country: 'Belgium',
+ cities: []
+ )
]
)
end
@@ -62,12 +67,14 @@ RSpec.describe CountriesAndCities do
it 'returns countries and cities' do
expect(countries_and_cities).to eq(
[
- {
- cities: [], country: 'Germany'
- },
- {
- cities: [], country: 'Belgium'
- }
+ CountriesAndCities::CountryData.new(
+ country: 'Germany',
+ cities: []
+ ),
+ CountriesAndCities::CountryData.new(
+ country: 'Belgium',
+ cities: []
+ )
]
)
end
diff --git a/spec/swagger/api/v1/countries/visited_cities_spec.rb b/spec/swagger/api/v1/countries/visited_cities_spec.rb
new file mode 100644
index 00000000..5d199e15
--- /dev/null
+++ b/spec/swagger/api/v1/countries/visited_cities_spec.rb
@@ -0,0 +1,96 @@
+# frozen_string_literal: true
+
+require 'swagger_helper'
+
+RSpec.describe 'Api::V1::Countries::VisitedCities', type: :request do
+ path '/api/v1/countries/visited_cities' do
+ get 'Get visited cities by date range' do
+ tags 'Countries'
+ 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: :start_at,
+ in: :query,
+ type: :string,
+ format: 'date-time',
+ required: true,
+ description: 'Start date in YYYY-MM-DD format',
+ example: '2023-01-01'
+
+ parameter name: :end_at,
+ in: :query,
+ type: :string,
+ format: 'date-time',
+ required: true,
+ description: 'End date in YYYY-MM-DD format',
+ example: '2023-12-31'
+
+ response '200', 'cities found' do
+ schema type: :object,
+ properties: {
+ data: {
+ type: :array,
+ description: 'Array of countries and their visited cities',
+ items: {
+ type: :object,
+ properties: {
+ country: {
+ type: :string,
+ example: 'Germany'
+ },
+ cities: {
+ type: :array,
+ items: {
+ type: :object,
+ properties: {
+ city: {
+ type: :string,
+ example: 'Berlin'
+ },
+ points: {
+ type: :integer,
+ example: 4394,
+ description: 'Number of points in the city'
+ },
+ timestamp: {
+ type: :integer,
+ example: 1_724_868_369,
+ description: 'Timestamp of the last point in the city in seconds since Unix epoch'
+ },
+ stayed_for: {
+ type: :integer,
+ example: 24_490,
+ description: 'Number of minutes the user stayed in the city'
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ let(:start_at) { '2023-01-01' }
+ let(:end_at) { '2023-12-31' }
+ let(:api_key) { create(:user).api_key }
+ run_test!
+ end
+
+ response '400', 'bad request - missing parameters' do
+ schema type: :object,
+ properties: {
+ error: {
+ type: :string,
+ example: 'Missing required parameters: start_at, end_at'
+ }
+ }
+
+ let(:start_at) { nil }
+ let(:end_at) { nil }
+ let(:api_key) { create(:user).api_key }
+ run_test!
+ end
+ end
+ end
+end
diff --git a/spec/swagger/api/v1/points/tracked_months_controller_spec.rb b/spec/swagger/api/v1/points/tracked_months_controller_spec.rb
new file mode 100644
index 00000000..fec4c633
--- /dev/null
+++ b/spec/swagger/api/v1/points/tracked_months_controller_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require 'swagger_helper'
+
+describe 'Points Tracked Months API', type: :request do
+ path '/api/v1/points/tracked_months' do
+ get 'Returns list of tracked years and months' do
+ tags 'Points'
+ produces 'application/json'
+ parameter name: :api_key, in: :query, type: :string, required: true, description: 'API Key'
+ response '200', 'years and months found' do
+ schema type: :array,
+ items: {
+ type: :object,
+ properties: {
+ year: { type: :integer, description: 'Year in YYYY format' },
+ months: {
+ type: :array,
+ items: { type: :string, description: 'Three-letter month abbreviation' }
+ }
+ },
+ required: %w[year months]
+ },
+ example: [{
+ year: 2024,
+ months: %w[Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec]
+ }]
+
+ let(:api_key) { create(:user).api_key }
+ run_test!
+ end
+
+ response '401', 'unauthorized' do
+ let(:api_key) { 'invalid' }
+ run_test!
+ end
+ end
+ end
+end
diff --git a/swagger/v1/swagger.yaml b/swagger/v1/swagger.yaml
index 9e6a57dd..2e6e2587 100644
--- a/swagger/v1/swagger.yaml
+++ b/swagger/v1/swagger.yaml
@@ -106,6 +106,84 @@ paths:
responses:
'200':
description: area deleted
+ "/api/v1/countries/visited_cities":
+ get:
+ summary: Get visited cities by date range
+ tags:
+ - Countries
+ description: Returns a list of visited cities and countries based on tracked
+ points within the specified date range
+ parameters:
+ - name: api_key
+ in: query
+ required: true
+ schema:
+ type: string
+ - name: start_at
+ in: query
+ format: date-time
+ required: true
+ description: Start date and time for the range (ISO 8601 format)
+ example: '2023-01-01T00:00:00Z'
+ schema:
+ type: string
+ - name: end_at
+ in: query
+ format: date-time
+ required: true
+ description: End date and time for the range (ISO 8601 format)
+ example: '2023-12-31T23:59:59Z'
+ schema:
+ type: string
+ responses:
+ '200':
+ description: cities found
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ data:
+ type: array
+ description: Array of countries and their visited cities
+ items:
+ type: object
+ properties:
+ country:
+ type: string
+ example: Germany
+ cities:
+ type: array
+ items:
+ type: object
+ properties:
+ city:
+ type: string
+ example: Berlin
+ points:
+ type: integer
+ example: 4394
+ description: Number of points in the city
+ timestamp:
+ type: integer
+ example: 1724868369
+ description: Timestamp of the last point in the city
+ in seconds since Unix epoch
+ stayed_for:
+ type: integer
+ example: 24490
+ description: Number of minutes the user stayed in
+ the city
+ '400':
+ description: bad request - missing parameters
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ error:
+ type: string
+ example: 'Missing required parameters: start_at, end_at'
"/api/v1/health":
get:
summary: Retrieves application status
@@ -460,6 +538,56 @@ paths:
- photoprism
'404':
description: photo not found
+ "/api/v1/points/tracked_months":
+ get:
+ summary: Returns list of tracked years and months
+ tags:
+ - Points
+ parameters:
+ - name: api_key
+ in: query
+ required: true
+ description: API Key
+ schema:
+ type: string
+ responses:
+ '200':
+ description: years and months found
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ type: object
+ properties:
+ year:
+ type: integer
+ description: Year in YYYY format
+ months:
+ type: array
+ items:
+ type: string
+ description: Three-letter month abbreviation
+ required:
+ - year
+ - months
+ example:
+ - year: 2024
+ months:
+ - Jan
+ - Feb
+ - Mar
+ - Apr
+ - May
+ - Jun
+ - Jul
+ - Aug
+ - Sep
+ - Oct
+ - Nov
+ - Dec
+ '401':
+ description: unauthorized
"/api/v1/points":
get:
summary: Retrieves all points