Fix GPX export timestamps and add slim version of points

This commit is contained in:
Eugene Burmakin 2024-09-24 00:10:39 +02:00
parent 2fd7c7bbf1
commit 7b876ea754
10 changed files with 180 additions and 64 deletions

View file

@ -1 +1 @@
0.14.3
0.14.4

View file

@ -5,12 +5,28 @@ 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.14.4] - 2024-09-24
### Fixed
- GPX export now has time and elevation elements for each point
### Changed
- `GET /api/v1/points` will no longer return `raw_data` attribute for each point as it's a bit too much
### Added
- "Slim" version of `GET /api/v1/points`: pass optional param `?slim=true` to it and it will return only latitude, longitude and timestamp
# [0.14.3] — 2024-09-21
### Fixed
- Optimize order of the dockerfiles to leverage layer caching by @JoeyEamigh
- Add support for alternate postgres ports and db names in docker by @JoeyEamigh
- Creating exports directory if it doesn't exist by @tetebueno
## [0.14.1] — 2024-09-16

View file

@ -11,11 +11,13 @@ class Api::V1::PointsController < ApiController
.order(:timestamp)
.page(params[:page])
.per(params[:per_page] || 100)
serialized_points = points.map { |point| point_serializer.new(point).call }
response.set_header('X-Current-Page', points.current_page.to_s)
response.set_header('X-Total-Pages', points.total_pages.to_s)
render json: points
render json: serialized_points
end
def destroy
@ -24,4 +26,10 @@ class Api::V1::PointsController < ApiController
render json: { message: 'Point deleted successfully' }
end
private
def point_serializer
params[:slim] ? SlimPointSerializer : PointSerializer
end
end

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].freeze
EXCLUDED_ATTRIBUTES = %w[created_at updated_at visit_id id import_id user_id raw_data].freeze
def initialize(point)
@point = point

View file

@ -6,9 +6,18 @@ class Points::GpxSerializer
end
def call
geojson_data = Points::GeojsonSerializer.new(points).call
gpx = GPX::GPXFile.new
GPX::GeoJSON.convert_to_gpx(geojson_data:)
points.each do |point|
gpx.waypoints << GPX::Waypoint.new(
lat: point.latitude.to_f,
lon: point.longitude.to_f,
time: point.recorded_at.strftime('%FT%R:%SZ'),
ele: point.altitude.to_f
)
end
gpx
end
private

View file

@ -0,0 +1,19 @@
# frozen_string_literal: true
class SlimPointSerializer
def initialize(point)
@point = point
end
def call
{
latitude: point.latitude,
longitude: point.longitude,
timestamp: point.timestamp
}
end
private
attr_reader :point
end

View file

@ -2,29 +2,29 @@
FactoryBot.define do
factory :point do
battery_status { 1 }
ping { 'MyString' }
battery { 1 }
topic { 'MyString' }
altitude { 1 }
longitude { 'MyString' }
velocity { 'MyString' }
trigger { 1 }
bssid { 'MyString' }
ssid { 'MyString' }
connection { 1 }
battery_status { 1 }
ping { 'MyString' }
battery { 1 }
topic { 'MyString' }
altitude { 1 }
longitude { FFaker::Geolocation.lng }
velocity { 0 }
trigger { 1 }
bssid { 'MyString' }
ssid { 'MyString' }
connection { 1 }
vertical_accuracy { 1 }
accuracy { 1 }
timestamp { 1 }
latitude { 'MyString' }
mode { 1 }
inrids { 'MyString' }
in_regions { 'MyString' }
raw_data { '' }
tracker_id { 'MyString' }
import_id { '' }
city { nil }
country { nil }
accuracy { 1 }
timestamp { 1.year.ago.to_i }
latitude { FFaker::Geolocation.lat }
mode { 1 }
inrids { 'MyString' }
in_regions { 'MyString' }
raw_data { '' }
tracker_id { 'MyString' }
import_id { '' }
city { nil }
country { nil }
user
trait :with_geodata do

View file

@ -7,39 +7,90 @@ RSpec.describe 'Api::V1::Points', type: :request do
let!(:points) { create_list(:point, 150, user:) }
describe 'GET /index' do
it 'renders a successful response' do
get api_v1_points_url(api_key: user.api_key)
context 'when regular version of points is requested' do
it 'renders a successful response' do
get api_v1_points_url(api_key: user.api_key)
expect(response).to be_successful
expect(response).to be_successful
end
it 'returns a list of points' do
get api_v1_points_url(api_key: user.api_key)
expect(response).to have_http_status(:ok)
json_response = JSON.parse(response.body)
expect(json_response.size).to eq(100)
end
it 'returns a list of points with pagination' do
get api_v1_points_url(api_key: user.api_key, page: 2, per_page: 10)
expect(response).to have_http_status(:ok)
json_response = JSON.parse(response.body)
expect(json_response.size).to eq(10)
end
it 'returns a list of points with pagination headers' do
get api_v1_points_url(api_key: user.api_key, page: 2, per_page: 10)
expect(response).to have_http_status(:ok)
expect(response.headers['X-Current-Page']).to eq('2')
expect(response.headers['X-Total-Pages']).to eq('15')
end
end
it 'returns a list of points' do
get api_v1_points_url(api_key: user.api_key)
context 'when slim version of points is requested' do
it 'renders a successful response' do
get api_v1_points_url(api_key: user.api_key, slim: true)
expect(response).to have_http_status(:ok)
expect(response).to be_successful
end
json_response = JSON.parse(response.body)
it 'returns a list of points' do
get api_v1_points_url(api_key: user.api_key, slim: true)
expect(json_response.size).to eq(100)
end
expect(response).to have_http_status(:ok)
it 'returns a list of points with pagination' do
get api_v1_points_url(api_key: user.api_key, page: 2, per_page: 10)
json_response = JSON.parse(response.body)
expect(response).to have_http_status(:ok)
expect(json_response.size).to eq(100)
end
json_response = JSON.parse(response.body)
it 'returns a list of points with pagination' do
get api_v1_points_url(api_key: user.api_key, slim: true, page: 2, per_page: 10)
expect(json_response.size).to eq(10)
end
expect(response).to have_http_status(:ok)
it 'returns a list of points with pagination headers' do
get api_v1_points_url(api_key: user.api_key, page: 2, per_page: 10)
json_response = JSON.parse(response.body)
expect(response).to have_http_status(:ok)
expect(json_response.size).to eq(10)
end
expect(response.headers['X-Current-Page']).to eq('2')
expect(response.headers['X-Total-Pages']).to eq('15')
it 'returns a list of points with pagination headers' do
get api_v1_points_url(api_key: user.api_key, slim: true, page: 2, per_page: 10)
expect(response).to have_http_status(:ok)
expect(response.headers['X-Current-Page']).to eq('2')
expect(response.headers['X-Total-Pages']).to eq('15')
end
it 'returns a list of points with slim attributes' do
get api_v1_points_url(api_key: user.api_key, slim: true)
expect(response).to have_http_status(:ok)
json_response = JSON.parse(response.body)
json_response.each do |point|
expect(point.keys).to eq(%w[latitude longitude timestamp])
end
end
end
end
end

View file

@ -1,17 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Points::GpxSerializer do
describe '#call' do
subject(:serializer) { described_class.new(points).call }
let(:points) { create_list(:point, 3) }
let(:geojson_data) { Points::GeojsonSerializer.new(points).call }
let(:gpx) { GPX::GeoJSON.convert_to_gpx(geojson_data:) }
it 'returns JSON' do
expect(serializer).to be_a(GPX::GPXFile)
end
end
end

View file

@ -0,0 +1,30 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Points::GpxSerializer do
describe '#call' do
subject(:serializer) { described_class.new(points).call }
let(:points) { create_list(:point, 3) }
let(:geojson_data) { Points::GeojsonSerializer.new(points).call }
let(:gpx) { GPX::GeoJSON.convert_to_gpx(geojson_data:) }
it 'returns GPX file' do
expect(serializer).to be_a(GPX::GPXFile)
end
it 'includes waypoints' do
expect(serializer.waypoints.size).to eq(3)
end
it 'includes waypoints with correct attributes' do
serializer.waypoints.each_with_index do |waypoint, index|
point = points[index]
expect(waypoint.lat).to eq(point.latitude)
expect(waypoint.lon).to eq(point.longitude)
expect(waypoint.time).to eq(point.recorded_at.strftime('%FT%R:%SZ'))
end
end
end
end