Skip to content

Commit

Permalink
Merge pull request #42 from Freika/api-key
Browse files Browse the repository at this point in the history
Api Key authentication
  • Loading branch information
Freika authored May 25, 2024
2 parents add1eb2 + 390b6c3 commit fdf7d6f
Show file tree
Hide file tree
Showing 51 changed files with 351 additions and 100 deletions.
2 changes: 1 addition & 1 deletion .app_version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.3.2
0.4.0
30 changes: 30 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,36 @@ 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.4.0] — 2024-05-25

**BREAKING CHANGES**:

- `/api/v1/points` is still working, but will be **deprecated** in nearest future. Please use `/api/v1/owntracks/points` instead.
- All existing points recorded directly to the database via Owntracks or Overland will be attached to the user with id 1.

### Added

- Each user now have an api key, which is required to make requests to the API. You can find your api key in your profile settings.
- You can re-generate your api key in your profile settings.
- In your user profile settings you can now see the instructions on how to use the API with your api key for both OwnTracks and Overland.
- Added docs on how to use the API with your api key. Refer to `/api-docs` for more information.
- `POST /api/v1/owntracks/points` endpoint.
- Points are now being attached to a user directly, so you can only see your own points and no other users of your applications can see your points.

### Changed

- `/api/v1/overland/batches` endpoint now requires an api key to be passed in the url. You can find your api key in your profile settings.
- All existing points recorded directly to the database will be attached to the user with id 1.
- All stats and maps are now being calculated and rendered based on the user's points only.
- Default `TIME_ZONE` environment variable is now set to 'UTC' in the `docker-compose.yml` file.

### Fixed

- Fixed a bug where marker on the map was rendering timestamp without considering the timezone.

---


## [0.3.2] — 2024-05-23

### Added
Expand Down
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
ruby '3.2.3'
gem 'bootsnap', require: false
gem 'chartkick'
gem 'data_migrate'
gem 'devise'
gem 'geocoder'
gem 'importmap-rails'
Expand Down
4 changes: 4 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ GEM
rexml
crass (1.0.6)
csv (3.3.0)
data_migrate (9.4.0)
activerecord (>= 6.1)
railties (>= 6.1)
date (3.3.4)
debug (1.9.2)
irb (~> 1.10)
Expand Down Expand Up @@ -391,6 +394,7 @@ PLATFORMS
DEPENDENCIES
bootsnap
chartkick
data_migrate
debug
devise
dotenv-rails
Expand Down
2 changes: 1 addition & 1 deletion app/assets/builds/tailwind.css

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions app/controllers/api/v1/overland/batches_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@

class Api::V1::Overland::BatchesController < ApplicationController
skip_forgery_protection
before_action :authenticate_api_key

def create
Overland::BatchCreatingJob.perform_later(batch_params)
Overland::BatchCreatingJob.perform_later(batch_params, current_api_user.id)

render json: { result: 'ok' }, status: :created
end

private

def batch_params
params.permit(locations: [:type, geometry: {}, properties: {}], batch: {})
params.permit(locations: [:type, { geometry: {}, properties: {} }], batch: {})
end
end
18 changes: 18 additions & 0 deletions app/controllers/api/v1/owntracks/points_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

class Api::V1::Owntracks::PointsController < ApplicationController
skip_forgery_protection
before_action :authenticate_api_key

def create
Owntracks::PointCreatingJob.perform_later(point_params, current_api_user.id)

render json: {}, status: :ok
end

private

def point_params
params.permit!
end
end
10 changes: 3 additions & 7 deletions app/controllers/api/v1/points_controller.rb
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
# frozen_string_literal: true

# TODO: Deprecate in 1.0

class Api::V1::PointsController < ApplicationController
skip_forgery_protection

def create
Rails.logger.info 'This endpoint will be deprecated in 1.0. Use /api/v1/owntracks/points instead'
Owntracks::PointCreatingJob.perform_later(point_params)

render json: {}, status: :ok
end

def destroy
@point = Point.find(params[:id])
@point.destroy

head :no_content
end

private

def point_params
Expand Down
14 changes: 14 additions & 0 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
# frozen_string_literal: true

class ApplicationController < ActionController::Base
include Pundit::Authorization

protected

def authenticate_api_key
return head :unauthorized unless current_api_user

true
end

def current_api_user
@current_api_user ||= User.find_by(api_key: params[:api_key])
end
end
4 changes: 2 additions & 2 deletions app/controllers/export_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def filename
end

def start_at
first_point_timestamp = Point.order(timestamp: :asc)&.first&.timestamp
first_point_timestamp = current_user.tracked_points.order(timestamp: :asc)&.first&.timestamp

@start_at ||=
if params[:start_at].nil? && first_point_timestamp.present?
Expand All @@ -37,7 +37,7 @@ def start_at
end

def end_at
last_point_timestamp = Point.order(timestamp: :desc)&.last&.timestamp
last_point_timestamp = current_user.tracked_points.order(timestamp: :desc)&.last&.timestamp

@end_at ||=
if params[:end_at].nil? && last_point_timestamp.present?
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/home_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ class HomeController < ApplicationController
def index
redirect_to map_url if current_user

@points = current_user.points.without_raw_data if current_user
@points = current_user.tracked_points.without_raw_data if current_user
end
end
2 changes: 1 addition & 1 deletion app/controllers/map_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ class MapController < ApplicationController
before_action :authenticate_user!

def index
@points = Point.without_raw_data.where('timestamp >= ? AND timestamp <= ?', start_at, end_at).order(timestamp: :asc)
@points = current_user.tracked_points.without_raw_data.where('timestamp >= ? AND timestamp <= ?', start_at, end_at).order(timestamp: :asc)

@countries_and_cities = CountriesAndCities.new(@points).call
@coordinates =
Expand Down
7 changes: 4 additions & 3 deletions app/controllers/points_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ class PointsController < ApplicationController

def index
@points =
Point
current_user
.tracked_points
.without_raw_data
.where('timestamp >= ? AND timestamp <= ?', start_at, end_at)
.order(timestamp: :asc)
Expand All @@ -16,9 +17,9 @@ def index
end

def bulk_destroy
Point.where(id: params[:point_ids].compact).destroy_all
current_user.tracked_points.where(id: params[:point_ids].compact).destroy_all

redirect_to points_url, notice: "Points were successfully destroyed.", status: :see_other
redirect_to points_url, notice: 'Points were successfully destroyed.', status: :see_other
end

private
Expand Down
6 changes: 6 additions & 0 deletions app/controllers/settings_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,10 @@ def theme

redirect_back(fallback_location: root_path)
end

def generate_api_key
current_user.update(api_key: SecureRandom.hex)

redirect_back(fallback_location: root_path)
end
end
12 changes: 6 additions & 6 deletions app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,24 +37,24 @@ def header_colors
%w[info success warning error accent secondary primary]
end

def countries_and_cities_stat(year)
data = Stat.year_cities_and_countries(year)
def countries_and_cities_stat(year, user)
data = Stat.year_cities_and_countries(year, user)
countries = data[:countries]
cities = data[:cities]

"#{countries} countries, #{cities} cities"
end

def year_distance_stat_in_km(year)
Stat.year_distance(year).sum { _1[1] }
def year_distance_stat_in_km(year, user)
Stat.year_distance(year, user).sum { _1[1] }
end

def past?(year, month)
DateTime.new(year, month).past?
end

def points_exist?(year, month)
Point.where(
def points_exist?(year, month, user)
user.tracked_points.where(
timestamp: DateTime.new(year, month).beginning_of_month..DateTime.new(year, month).end_of_month
).exists?
end
Expand Down
12 changes: 3 additions & 9 deletions app/javascript/controllers/maps_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,15 +96,9 @@ export default class extends Controller {
formatDate(timestamp) {
let date = new Date(timestamp * 1000); // Multiply by 1000 because JavaScript works with milliseconds

// Extracting date components
let year = date.getFullYear();
let month = ('0' + (date.getMonth() + 1)).slice(-2); // Adding 1 because getMonth() returns zero-based month
let day = ('0' + date.getDate()).slice(-2);
let hours = ('0' + date.getHours()).slice(-2);
let minutes = ('0' + date.getMinutes()).slice(-2);
let seconds = ('0' + date.getSeconds()).slice(-2);

return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
let timezone = this.element.dataset.timezone;

return date.toLocaleString('en-GB', { timeZone: timezone });
}

addTileLayer(map) {
Expand Down
2 changes: 1 addition & 1 deletion app/jobs/import_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ def perform(user_id, import_id)
user = User.find(user_id)
import = user.imports.find(import_id)

result = parser(import.source).new(import).call
result = parser(import.source).new(import, user_id).call

import.update(
raw_points: result[:raw_points], doubles: result[:doubles], processed: result[:processed]
Expand Down
4 changes: 2 additions & 2 deletions app/jobs/overland/batch_creating_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
class Overland::BatchCreatingJob < ApplicationJob
queue_as :default

def perform(params)
def perform(params, user_id)
data = Overland::Params.new(params).call

data.each do |location|
Point.create!(location)
Point.create!(location.merge(user_id:))
end
end
end
5 changes: 3 additions & 2 deletions app/jobs/owntracks/point_creating_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
class Owntracks::PointCreatingJob < ApplicationJob
queue_as :default

def perform(point_params)
# TODO: after deprecation of old endpoint, make user_id required
def perform(point_params, user_id = nil)
parsed_params = OwnTracks::Params.new(point_params).call

Point.create(parsed_params)
Point.create!(parsed_params.merge(user_id:))
end
end
1 change: 1 addition & 0 deletions app/models/point.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ class Point < ApplicationRecord
# self.ignored_columns = %w[raw_data]

belongs_to :import, optional: true
belongs_to :user, optional: true

validates :latitude, :longitude, :timestamp, presence: true

Expand Down
10 changes: 5 additions & 5 deletions app/models/stat.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def distance_by_day
end_of_day = day.end_of_day.to_i

# We have to filter by user as well
points = Point.without_raw_data.where(timestamp: beginning_of_day..end_of_day)
points = user.tracked_points.without_raw_data.where(timestamp: beginning_of_day..end_of_day)

data = { day: index, distance: 0 }

Expand All @@ -27,8 +27,8 @@ def distance_by_day
end
end

def self.year_distance(year)
stats = where(year: year).order(:month)
def self.year_distance(year, user)
stats = where(year:, user:).order(:month)

(1..12).to_a.map do |month|
month_stat = stats.select { |stat| stat.month == month }.first
Expand All @@ -40,8 +40,8 @@ def self.year_distance(year)
end
end

def self.year_cities_and_countries(year)
points = Point.where(timestamp: DateTime.new(year).beginning_of_year..DateTime.new(year).end_of_year)
def self.year_cities_and_countries(year, user)
points = user.tracked_points.where(timestamp: DateTime.new(year).beginning_of_year..DateTime.new(year).end_of_year)

data = CountriesAndCities.new(points).call

Expand Down
1 change: 1 addition & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class User < ApplicationRecord
has_many :imports, dependent: :destroy
has_many :points, through: :imports
has_many :stats, dependent: :destroy
has_many :tracked_points, class_name: 'Point', dependent: :destroy

after_create :create_api_key

Expand Down
7 changes: 4 additions & 3 deletions app/services/create_stats.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def call
beginning_of_month_timestamp = DateTime.new(year, month).beginning_of_month.to_i
end_of_month_timestamp = DateTime.new(year, month).end_of_month.to_i

points = points(beginning_of_month_timestamp, end_of_month_timestamp)
points = points(user, beginning_of_month_timestamp, end_of_month_timestamp)
next if points.empty?

stat = Stat.find_or_initialize_by(year:, month:, user:)
Expand All @@ -31,8 +31,9 @@ def call

private

def points(beginning_of_month_timestamp, end_of_month_timestamp)
Point
def points(user, beginning_of_month_timestamp, end_of_month_timestamp)
user
.tracked_points
.without_raw_data
.where(timestamp: beginning_of_month_timestamp..end_of_month_timestamp)
.order(:timestamp)
Expand Down
8 changes: 5 additions & 3 deletions app/services/google_maps/semantic_history_parser.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# frozen_string_literal: true

class GoogleMaps::SemanticHistoryParser
attr_reader :import
attr_reader :import, :user_id

def initialize(import)
def initialize(import, user_id)
@import = import
@user_id = user_id
end

def call
Expand All @@ -22,7 +23,8 @@ def call
raw_data: point_data[:raw_data],
topic: 'Google Maps Timeline Export',
tracker_id: 'google-maps-timeline-export',
import_id: import.id
import_id: import.id,
user_id:
)

points += 1
Expand Down
Loading

0 comments on commit fdf7d6f

Please sign in to comment.