mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-11 01:31:39 -05:00
Add optional order query parameter to GET /api/v1/points
This commit is contained in:
parent
6aaab424fe
commit
df430851ce
14 changed files with 60 additions and 29 deletions
|
|
@ -10,6 +10,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Now you can use http protocol for the Photon API host if you don't have SSL certificate for it
|
- Now you can use http protocol for the Photon API host if you don't have SSL certificate for it
|
||||||
|
- For stats, total distance per month might have been not equal to the sum of distances per day. Now it's fixed and values are equal
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `GET /api/v1/points` can now accept optional `?order=asc` query parameter to return points in ascending order by timestamp. `?order=desc` is still available to return points in descending order by timestamp.
|
||||||
|
|
||||||
# [0.14.6] - 2024-29-30
|
# [0.14.6] - 2024-29-30
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,13 @@
|
||||||
class Api::V1::PointsController < ApiController
|
class Api::V1::PointsController < ApiController
|
||||||
def index
|
def index
|
||||||
start_at = params[:start_at]&.to_datetime&.to_i
|
start_at = params[:start_at]&.to_datetime&.to_i
|
||||||
end_at = params[:end_at]&.to_datetime&.to_i || Time.zone.now.to_i
|
end_at = params[:end_at]&.to_datetime&.to_i || Time.zone.now.to_i
|
||||||
|
order = params[:order] || 'desc'
|
||||||
|
|
||||||
points = current_api_user
|
points = current_api_user
|
||||||
.tracked_points
|
.tracked_points
|
||||||
.where(timestamp: start_at..end_at)
|
.where(timestamp: start_at..end_at)
|
||||||
.order(:timestamp)
|
.order(timestamp: order)
|
||||||
.page(params[:page])
|
.page(params[:page])
|
||||||
.per(params[:per_page] || 100)
|
.per(params[:per_page] || 100)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@ class MapController < ApplicationController
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@points = points
|
@points = points
|
||||||
.without_raw_data
|
.without_raw_data
|
||||||
.where('timestamp >= ? AND timestamp <= ?', start_at, end_at)
|
.where('timestamp >= ? AND timestamp <= ?', start_at, end_at)
|
||||||
.order(timestamp: :asc)
|
.order(timestamp: :asc)
|
||||||
|
|
||||||
@countries_and_cities = CountriesAndCities.new(@points).call
|
@countries_and_cities = CountriesAndCities.new(@points).call
|
||||||
@coordinates =
|
@coordinates =
|
||||||
|
|
@ -38,7 +38,7 @@ class MapController < ApplicationController
|
||||||
|
|
||||||
@coordinates.each_cons(2) do
|
@coordinates.each_cons(2) do
|
||||||
@distance += Geocoder::Calculations.distance_between(
|
@distance += Geocoder::Calculations.distance_between(
|
||||||
[_1[0], _1[1]], [_2[0], _2[1]], units: DISTANCE_UNIT.to_sym
|
[_1[0], _1[1]], [_2[0], _2[1]], units: DISTANCE_UNIT
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ class Stat < ApplicationRecord
|
||||||
|
|
||||||
points.each_cons(2) do |point1, point2|
|
points.each_cons(2) do |point1, point2|
|
||||||
distance = Geocoder::Calculations.distance_between(
|
distance = Geocoder::Calculations.distance_between(
|
||||||
[point1.latitude, point1.longitude], [point2.latitude, point2.longitude]
|
point1.to_coordinates, point2.to_coordinates, units: ::DISTANCE_UNIT
|
||||||
)
|
)
|
||||||
|
|
||||||
data[:distance] += distance
|
data[:distance] += distance
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class PointSerializer
|
class PointSerializer
|
||||||
EXCLUDED_ATTRIBUTES = %w[created_at updated_at visit_id id import_id user_id raw_data].freeze
|
EXCLUDED_ATTRIBUTES = %w[created_at updated_at visit_id import_id user_id raw_data].freeze
|
||||||
|
|
||||||
def initialize(point)
|
def initialize(point)
|
||||||
@point = point
|
@point = point
|
||||||
|
|
|
||||||
|
|
@ -31,14 +31,14 @@ class Areas::Visits::Create
|
||||||
|
|
||||||
def area_points(area)
|
def area_points(area)
|
||||||
area_radius =
|
area_radius =
|
||||||
if ::DISTANCE_UNIT.to_sym == :km
|
if ::DISTANCE_UNIT == :km
|
||||||
area.radius / 1000.0
|
area.radius / 1000.0
|
||||||
else
|
else
|
||||||
area.radius / 1609.344
|
area.radius / 1609.344
|
||||||
end
|
end
|
||||||
|
|
||||||
points = Point.where(user_id: user.id)
|
points = Point.where(user_id: user.id)
|
||||||
.near([area.latitude, area.longitude], area_radius, units: DISTANCE_UNIT.to_sym)
|
.near([area.latitude, area.longitude], area_radius, units: DISTANCE_UNIT)
|
||||||
.order(timestamp: :asc)
|
.order(timestamp: :asc)
|
||||||
|
|
||||||
# check if all points within the area are assigned to a visit
|
# check if all points within the area are assigned to a visit
|
||||||
|
|
|
||||||
|
|
@ -41,22 +41,15 @@ class CreateStats
|
||||||
return if points.empty?
|
return if points.empty?
|
||||||
|
|
||||||
stat = Stat.find_or_initialize_by(year:, month:, user:)
|
stat = Stat.find_or_initialize_by(year:, month:, user:)
|
||||||
stat.distance = distance(points)
|
distance_by_day = stat.distance_by_day
|
||||||
|
stat.daily_distance = distance_by_day
|
||||||
|
stat.distance = distance(distance_by_day)
|
||||||
stat.toponyms = toponyms(points)
|
stat.toponyms = toponyms(points)
|
||||||
stat.daily_distance = stat.distance_by_day
|
|
||||||
stat.save
|
stat.save
|
||||||
end
|
end
|
||||||
|
|
||||||
def distance(points)
|
def distance(distance_by_day)
|
||||||
distance = 0
|
distance_by_day.sum { |d| d[1] }
|
||||||
|
|
||||||
points.each_cons(2) do
|
|
||||||
distance += Geocoder::Calculations.distance_between(
|
|
||||||
[_1.latitude, _1.longitude], [_2.latitude, _2.longitude], units: DISTANCE_UNIT.to_sym
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
distance
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def toponyms(points)
|
def toponyms(points)
|
||||||
|
|
|
||||||
|
|
@ -3,4 +3,4 @@
|
||||||
MIN_MINUTES_SPENT_IN_CITY = ENV.fetch('MIN_MINUTES_SPENT_IN_CITY', 60).to_i
|
MIN_MINUTES_SPENT_IN_CITY = ENV.fetch('MIN_MINUTES_SPENT_IN_CITY', 60).to_i
|
||||||
REVERSE_GEOCODING_ENABLED = ENV.fetch('REVERSE_GEOCODING_ENABLED', 'true') == 'true'
|
REVERSE_GEOCODING_ENABLED = ENV.fetch('REVERSE_GEOCODING_ENABLED', 'true') == 'true'
|
||||||
PHOTON_API_HOST = ENV.fetch('PHOTON_API_HOST', nil)
|
PHOTON_API_HOST = ENV.fetch('PHOTON_API_HOST', nil)
|
||||||
DISTANCE_UNIT = ENV.fetch('DISTANCE_UNIT', 'km')
|
DISTANCE_UNIT = ENV.fetch('DISTANCE_UNIT', 'km').to_sym
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
settings = {
|
settings = {
|
||||||
timeout: 5,
|
timeout: 5,
|
||||||
units: DISTANCE_UNIT.to_sym,
|
units: DISTANCE_UNIT,
|
||||||
cache: Redis.new,
|
cache: Redis.new,
|
||||||
always_raise: :all,
|
always_raise: :all,
|
||||||
cache_options: {
|
cache_options: {
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ FactoryBot.define do
|
||||||
connection { 1 }
|
connection { 1 }
|
||||||
vertical_accuracy { 1 }
|
vertical_accuracy { 1 }
|
||||||
accuracy { 1 }
|
accuracy { 1 }
|
||||||
timestamp { 1.year.ago.to_i }
|
timestamp { DateTime.new(2024, 5, 1).to_i + rand(1_000).minutes }
|
||||||
latitude { FFaker::Geolocation.lat }
|
latitude { FFaker::Geolocation.lat }
|
||||||
mode { 1 }
|
mode { 1 }
|
||||||
inrids { 'MyString' }
|
inrids { 'MyString' }
|
||||||
|
|
|
||||||
|
|
@ -88,9 +88,31 @@ RSpec.describe 'Api::V1::Points', type: :request do
|
||||||
json_response = JSON.parse(response.body)
|
json_response = JSON.parse(response.body)
|
||||||
|
|
||||||
json_response.each do |point|
|
json_response.each do |point|
|
||||||
expect(point.keys).to eq(%w[latitude longitude timestamp])
|
expect(point.keys).to eq(%w[id latitude longitude timestamp])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when order param is provided' do
|
||||||
|
it 'returns points in ascending order' do
|
||||||
|
get api_v1_points_url(api_key: user.api_key, order: 'asc')
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
|
||||||
|
json_response = JSON.parse(response.body)
|
||||||
|
|
||||||
|
expect(json_response.first['timestamp']).to be < json_response.last['timestamp']
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns points in descending order' do
|
||||||
|
get api_v1_points_url(api_key: user.api_key, order: 'desc')
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
|
||||||
|
json_response = JSON.parse(response.body)
|
||||||
|
|
||||||
|
expect(json_response.first['timestamp']).to be > json_response.last['timestamp']
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ RSpec.describe CreateStats do
|
||||||
let!(:point3) { create(:point, user:, import:, latitude: 3, longitude: 4) }
|
let!(:point3) { create(:point, user:, import:, latitude: 3, longitude: 4) }
|
||||||
|
|
||||||
context 'when units are kilometers' do
|
context 'when units are kilometers' do
|
||||||
|
before { stub_const('DISTANCE_UNIT', :km) }
|
||||||
|
|
||||||
it 'creates stats' do
|
it 'creates stats' do
|
||||||
expect { create_stats }.to change { Stat.count }.by(1)
|
expect { create_stats }.to change { Stat.count }.by(1)
|
||||||
end
|
end
|
||||||
|
|
@ -29,7 +31,7 @@ RSpec.describe CreateStats do
|
||||||
it 'calculates distance' do
|
it 'calculates distance' do
|
||||||
create_stats
|
create_stats
|
||||||
|
|
||||||
expect(Stat.last.distance).to eq(563)
|
expect(user.stats.last.distance).to eq(563)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'created notifications' do
|
it 'created notifications' do
|
||||||
|
|
@ -52,7 +54,7 @@ RSpec.describe CreateStats do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when units are miles' do
|
context 'when units are miles' do
|
||||||
before { stub_const('DISTANCE_UNIT', 'mi') }
|
before { stub_const('DISTANCE_UNIT', :mi) }
|
||||||
|
|
||||||
it 'creates stats' do
|
it 'creates stats' do
|
||||||
expect { create_stats }.to change { Stat.count }.by(1)
|
expect { create_stats }.to change { Stat.count }.by(1)
|
||||||
|
|
@ -61,7 +63,7 @@ RSpec.describe CreateStats do
|
||||||
it 'calculates distance' do
|
it 'calculates distance' do
|
||||||
create_stats
|
create_stats
|
||||||
|
|
||||||
expect(Stat.last.distance).to eq(349)
|
expect(user.stats.last.distance).to eq(349)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'created notifications' do
|
it 'created notifications' do
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ describe 'Points API', type: :request do
|
||||||
description: 'End date (i.e. 2024-02-03T13:00:03Z or 2024-02-03)'
|
description: 'End date (i.e. 2024-02-03T13:00:03Z or 2024-02-03)'
|
||||||
parameter name: :page, in: :query, type: :integer, required: false, description: 'Page number'
|
parameter name: :page, in: :query, type: :integer, required: false, description: 'Page number'
|
||||||
parameter name: :per_page, in: :query, type: :integer, required: false, description: 'Number of points per page'
|
parameter name: :per_page, in: :query, type: :integer, required: false, description: 'Number of points per page'
|
||||||
|
parameter name: :order, in: :query, type: :string, required: false,
|
||||||
|
description: 'Order of points, valid values are `asc` or `desc`'
|
||||||
response '200', 'points found' do
|
response '200', 'points found' do
|
||||||
schema type: :array,
|
schema type: :array,
|
||||||
items: {
|
items: {
|
||||||
|
|
|
||||||
|
|
@ -346,6 +346,12 @@ paths:
|
||||||
description: Number of points per page
|
description: Number of points per page
|
||||||
schema:
|
schema:
|
||||||
type: integer
|
type: integer
|
||||||
|
- name: order
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: Order of points, valid values are `asc` or `desc`
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: points found
|
description: points found
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue