diff --git a/Makefile b/Makefile
index 551969cc..1393abf6 100644
--- a/Makefile
+++ b/Makefile
@@ -67,6 +67,7 @@ production_migrate:
ssh dokku_frey 'dokku run dawarich bundle exec rails db:migrate'
build_and_push:
+ git tag -l "$(version)"
docker build . -t dawarich:$(version) --platform=linux/amd64
docker tag dawarich:$(version) registry.chibi.rodeo/dawarich:$(version)
docker push registry.chibi.rodeo/dawarich:$(version)
diff --git a/app/controllers/stats_controller.rb b/app/controllers/stats_controller.rb
new file mode 100644
index 00000000..7e06173b
--- /dev/null
+++ b/app/controllers/stats_controller.rb
@@ -0,0 +1,7 @@
+class StatsController < ApplicationController
+ before_action :authenticate_user!
+
+ def index
+ @stats = current_user.stats.group_by(&:year).sort_by { _1 }.reverse
+ end
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index b2ddb195..42ffb140 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -7,4 +7,12 @@ module ApplicationHelper
'bg-blue-100 text-blue-700 border-blue-300'
end
end
+
+ def url_time(stat)
+ month = DateTime.new(stat.year, stat.month).in_time_zone(Time.zone)
+ start_at = month.beginning_of_month.to_time.strftime('%Y-%m-%dT%H:%M')
+ end_at = month.end_of_month.to_time.strftime('%Y-%m-%dT%H:%M')
+
+ { start_at:, end_at: }
+ end
end
diff --git a/app/models/stat.rb b/app/models/stat.rb
new file mode 100644
index 00000000..7fd717d9
--- /dev/null
+++ b/app/models/stat.rb
@@ -0,0 +1,5 @@
+class Stat < ApplicationRecord
+ validates :year, :month, presence: true
+
+ belongs_to :user
+end
diff --git a/app/models/user.rb b/app/models/user.rb
index c58a7639..50a5ee56 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -6,4 +6,5 @@ class User < ApplicationRecord
has_many :imports, dependent: :destroy
has_many :points, through: :imports
+ has_many :stats
end
diff --git a/app/services/create_stats.rb b/app/services/create_stats.rb
new file mode 100644
index 00000000..a985dd71
--- /dev/null
+++ b/app/services/create_stats.rb
@@ -0,0 +1,56 @@
+
+class CreateStats
+ attr_reader :years, :months, :user
+
+ def initialize(user_id)
+ @user = User.find(user_id)
+ @years = (1970..Time.current.year).to_a
+ @months = (1..12).to_a
+ end
+
+ def call
+ years.flat_map do |year|
+ months.map do |month|
+ 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)
+
+ next if points.empty?
+
+ Stat.create(
+ year: year,
+ month: month,
+ distance: distance(points),
+ toponyms: toponyms(points),
+ user: user
+ )
+ end
+ end.compact
+ end
+
+ private
+
+ def points(beginning_of_month_timestamp, end_of_month_timestamp)
+ Point
+ .where(timestamp: beginning_of_month_timestamp..end_of_month_timestamp)
+ .order(:timestamp)
+ .select(:latitude, :longitude, :timestamp, :city, :country)
+ end
+
+ def distance(points)
+ km = 0
+
+ points.each_cons(2) do
+ km += Geocoder::Calculations.distance_between(
+ [_1.latitude, _1.longitude], [_2.latitude, _2.longitude], units: :km
+ )
+ end
+
+ km
+ end
+
+ def toponyms(points)
+ CountriesAndCities.new(points).call
+ end
+end
diff --git a/app/views/shared/_navbar.html.erb b/app/views/shared/_navbar.html.erb
index f73d3f7b..39b49ac2 100644
--- a/app/views/shared/_navbar.html.erb
+++ b/app/views/shared/_navbar.html.erb
@@ -7,6 +7,7 @@
- <%= link_to 'Imports', imports_url %>
- <%= link_to 'Points', points_url %>
+ - <%= link_to 'Stats', stats_url %>
<%= link_to 'Dawarich', root_path, class: 'btn btn-ghost normal-case text-xl'%>
@@ -20,6 +21,7 @@
diff --git a/app/views/stats/_stat.html.erb b/app/views/stats/_stat.html.erb
new file mode 100644
index 00000000..9a39d96a
--- /dev/null
+++ b/app/views/stats/_stat.html.erb
@@ -0,0 +1,13 @@
+
+
+
+ <%= link_to points_url(url_time(stat)), class: 'underline hover:no-underline' do %>
+ <%= "#{Date::MONTHNAMES[stat.month]} of #{stat.year}" %>
+ <% end %>
+
+
<%= stat.distance %>km
+
+ <%= stat.toponyms.count %> countries, <%= stat.toponyms.sum { _1['cities'].count } %> cities
+
+
+
diff --git a/app/views/stats/index.html.erb b/app/views/stats/index.html.erb
new file mode 100644
index 00000000..0f293576
--- /dev/null
+++ b/app/views/stats/index.html.erb
@@ -0,0 +1,10 @@
+
+ <% @stats.each do |year, stats| %>
+
<%= year %>
+
+ <% stats.each do |stat| %>
+ <%= render stat %>
+ <% end %>
+
+ <% end %>
+
diff --git a/config/routes.rb b/config/routes.rb
index 28e7cb3a..d68e7ae9 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,6 +1,7 @@
Rails.application.routes.draw do
- get 'points/index'
resources :imports
+ resources :stats, only: :index
+
root to: 'home#index'
devise_for :users
diff --git a/db/migrate/20240323160300_create_stats.rb b/db/migrate/20240323160300_create_stats.rb
new file mode 100644
index 00000000..b2b9f77d
--- /dev/null
+++ b/db/migrate/20240323160300_create_stats.rb
@@ -0,0 +1,15 @@
+class CreateStats < ActiveRecord::Migration[7.1]
+ def change
+ create_table :stats do |t|
+ t.integer :year, null: false
+ t.integer :month, null: false
+ t.integer :distance, null: false
+ t.jsonb :toponyms
+
+ t.timestamps
+ end
+ add_index :stats, :year
+ add_index :stats, :month
+ add_index :stats, :distance
+ end
+end
diff --git a/db/migrate/20240323161049_add_index_to_points_timestamp.rb b/db/migrate/20240323161049_add_index_to_points_timestamp.rb
new file mode 100644
index 00000000..606a4662
--- /dev/null
+++ b/db/migrate/20240323161049_add_index_to_points_timestamp.rb
@@ -0,0 +1,5 @@
+class AddIndexToPointsTimestamp < ActiveRecord::Migration[7.1]
+ def change
+ add_index :points, :timestamp
+ end
+end
diff --git a/db/migrate/20240323190039_add_user_id_to_stat.rb b/db/migrate/20240323190039_add_user_id_to_stat.rb
new file mode 100644
index 00000000..b0b2a018
--- /dev/null
+++ b/db/migrate/20240323190039_add_user_id_to_stat.rb
@@ -0,0 +1,5 @@
+class AddUserIdToStat < ActiveRecord::Migration[7.1]
+ def change
+ add_reference :stats, :user, null: false, foreign_key: true
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 1cc17c4b..07868dec 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[7.1].define(version: 2024_03_23_125126) do
+ActiveRecord::Schema[7.1].define(version: 2024_03_23_190039) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -60,9 +60,24 @@ ActiveRecord::Schema[7.1].define(version: 2024_03_23_125126) do
t.index ["country"], name: "index_points_on_country"
t.index ["import_id"], name: "index_points_on_import_id"
t.index ["latitude", "longitude"], name: "index_points_on_latitude_and_longitude"
+ t.index ["timestamp"], name: "index_points_on_timestamp"
t.index ["trigger"], name: "index_points_on_trigger"
end
+ create_table "stats", force: :cascade do |t|
+ t.integer "year", null: false
+ t.integer "month", null: false
+ t.integer "distance", null: false
+ t.jsonb "toponyms"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.bigint "user_id", null: false
+ t.index ["distance"], name: "index_stats_on_distance"
+ t.index ["month"], name: "index_stats_on_month"
+ t.index ["user_id"], name: "index_stats_on_user_id"
+ t.index ["year"], name: "index_stats_on_year"
+ end
+
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
@@ -75,4 +90,5 @@ ActiveRecord::Schema[7.1].define(version: 2024_03_23_125126) do
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
+ add_foreign_key "stats", "users"
end
diff --git a/spec/factories/stats.rb b/spec/factories/stats.rb
new file mode 100644
index 00000000..fdf1ed19
--- /dev/null
+++ b/spec/factories/stats.rb
@@ -0,0 +1,8 @@
+FactoryBot.define do
+ factory :stat do
+ year { 1 }
+ month { 1 }
+ distance { 1 }
+ toponyms { "" }
+ end
+end
diff --git a/spec/models/stat_spec.rb b/spec/models/stat_spec.rb
new file mode 100644
index 00000000..6280a126
--- /dev/null
+++ b/spec/models/stat_spec.rb
@@ -0,0 +1,6 @@
+require 'rails_helper'
+
+RSpec.describe Stat, type: :model do
+ it { is_expected.to validate_presence_of(:year) }
+ it { is_expected.to validate_presence_of(:month) }
+end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index ab0da922..b12bcf29 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -1,4 +1,7 @@
require 'rails_helper'
RSpec.describe User, type: :model do
+ it { is_expected.to have_many(:imports).dependent(:destroy) }
+ it { is_expected.to have_many(:points).through(:imports) }
+ it { is_expected.to have_many(:stats) }
end
diff --git a/spec/requests/stats_spec.rb b/spec/requests/stats_spec.rb
new file mode 100644
index 00000000..ac3a7849
--- /dev/null
+++ b/spec/requests/stats_spec.rb
@@ -0,0 +1,11 @@
+require 'rails_helper'
+
+RSpec.describe "/stats", type: :request do
+
+ describe "GET /index" do
+ it "renders a successful response" do
+ get stats_url
+ expect(response.status).to eq(302)
+ end
+ end
+end