Add optional order query parameter to GET /api/v1/points

This commit is contained in:
Eugene Burmakin 2024-10-02 21:29:56 +02:00
parent 6aaab424fe
commit df430851ce
14 changed files with 60 additions and 29 deletions

View file

@ -10,6 +10,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Fixed
- 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

View file

@ -3,12 +3,13 @@
class Api::V1::PointsController < ApiController
def index
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
.tracked_points
.where(timestamp: start_at..end_at)
.order(:timestamp)
.order(timestamp: order)
.page(params[:page])
.per(params[:per_page] || 100)

View file

@ -5,9 +5,9 @@ class MapController < ApplicationController
def index
@points = points
.without_raw_data
.where('timestamp >= ? AND timestamp <= ?', start_at, end_at)
.order(timestamp: :asc)
.without_raw_data
.where('timestamp >= ? AND timestamp <= ?', start_at, end_at)
.order(timestamp: :asc)
@countries_and_cities = CountriesAndCities.new(@points).call
@coordinates =
@ -38,7 +38,7 @@ class MapController < ApplicationController
@coordinates.each_cons(2) do
@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

View file

@ -17,7 +17,7 @@ class Stat < ApplicationRecord
points.each_cons(2) do |point1, point2|
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

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true
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)
@point = point

View file

@ -31,14 +31,14 @@ class Areas::Visits::Create
def area_points(area)
area_radius =
if ::DISTANCE_UNIT.to_sym == :km
if ::DISTANCE_UNIT == :km
area.radius / 1000.0
else
area.radius / 1609.344
end
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)
# check if all points within the area are assigned to a visit

View file

@ -41,22 +41,15 @@ class CreateStats
return if points.empty?
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.daily_distance = stat.distance_by_day
stat.save
end
def distance(points)
distance = 0
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
def distance(distance_by_day)
distance_by_day.sum { |d| d[1] }
end
def toponyms(points)

View file

@ -3,4 +3,4 @@
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'
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

View file

@ -2,7 +2,7 @@
settings = {
timeout: 5,
units: DISTANCE_UNIT.to_sym,
units: DISTANCE_UNIT,
cache: Redis.new,
always_raise: :all,
cache_options: {

View file

@ -15,7 +15,7 @@ FactoryBot.define do
connection { 1 }
vertical_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 }
mode { 1 }
inrids { 'MyString' }

View file

@ -88,9 +88,31 @@ RSpec.describe 'Api::V1::Points', type: :request do
json_response = JSON.parse(response.body)
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
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

View file

@ -22,6 +22,8 @@ RSpec.describe CreateStats do
let!(:point3) { create(:point, user:, import:, latitude: 3, longitude: 4) }
context 'when units are kilometers' do
before { stub_const('DISTANCE_UNIT', :km) }
it 'creates stats' do
expect { create_stats }.to change { Stat.count }.by(1)
end
@ -29,7 +31,7 @@ RSpec.describe CreateStats do
it 'calculates distance' do
create_stats
expect(Stat.last.distance).to eq(563)
expect(user.stats.last.distance).to eq(563)
end
it 'created notifications' do
@ -52,7 +54,7 @@ RSpec.describe CreateStats do
end
context 'when units are miles' do
before { stub_const('DISTANCE_UNIT', 'mi') }
before { stub_const('DISTANCE_UNIT', :mi) }
it 'creates stats' do
expect { create_stats }.to change { Stat.count }.by(1)
@ -61,7 +63,7 @@ RSpec.describe CreateStats do
it 'calculates distance' do
create_stats
expect(Stat.last.distance).to eq(349)
expect(user.stats.last.distance).to eq(349)
end
it 'created notifications' do

View file

@ -14,6 +14,8 @@ describe 'Points API', type: :request do
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: :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
schema type: :array,
items: {

View file

@ -346,6 +346,12 @@ paths:
description: Number of points per page
schema:
type: integer
- name: order
in: query
required: false
description: Order of points, valid values are `asc` or `desc`
schema:
type: string
responses:
'200':
description: points found