mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-10 17:21:38 -05:00
Add basic stats
This commit is contained in:
parent
0a156b0966
commit
e1193608ef
18 changed files with 175 additions and 2 deletions
1
Makefile
1
Makefile
|
|
@ -67,6 +67,7 @@ production_migrate:
|
||||||
ssh dokku_frey 'dokku run dawarich bundle exec rails db:migrate'
|
ssh dokku_frey 'dokku run dawarich bundle exec rails db:migrate'
|
||||||
|
|
||||||
build_and_push:
|
build_and_push:
|
||||||
|
git tag -l "$(version)"
|
||||||
docker build . -t dawarich:$(version) --platform=linux/amd64
|
docker build . -t dawarich:$(version) --platform=linux/amd64
|
||||||
docker tag dawarich:$(version) registry.chibi.rodeo/dawarich:$(version)
|
docker tag dawarich:$(version) registry.chibi.rodeo/dawarich:$(version)
|
||||||
docker push registry.chibi.rodeo/dawarich:$(version)
|
docker push registry.chibi.rodeo/dawarich:$(version)
|
||||||
|
|
|
||||||
7
app/controllers/stats_controller.rb
Normal file
7
app/controllers/stats_controller.rb
Normal file
|
|
@ -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
|
||||||
|
|
@ -7,4 +7,12 @@ module ApplicationHelper
|
||||||
'bg-blue-100 text-blue-700 border-blue-300'
|
'bg-blue-100 text-blue-700 border-blue-300'
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
|
||||||
5
app/models/stat.rb
Normal file
5
app/models/stat.rb
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
class Stat < ApplicationRecord
|
||||||
|
validates :year, :month, presence: true
|
||||||
|
|
||||||
|
belongs_to :user
|
||||||
|
end
|
||||||
|
|
@ -6,4 +6,5 @@ class User < ApplicationRecord
|
||||||
|
|
||||||
has_many :imports, dependent: :destroy
|
has_many :imports, dependent: :destroy
|
||||||
has_many :points, through: :imports
|
has_many :points, through: :imports
|
||||||
|
has_many :stats
|
||||||
end
|
end
|
||||||
|
|
|
||||||
56
app/services/create_stats.rb
Normal file
56
app/services/create_stats.rb
Normal file
|
|
@ -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
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
<ul tabindex="0" class="menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow bg-base-100 rounded-box w-52">
|
<ul tabindex="0" class="menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow bg-base-100 rounded-box w-52">
|
||||||
<li><%= link_to 'Imports', imports_url %></li>
|
<li><%= link_to 'Imports', imports_url %></li>
|
||||||
<li><%= link_to 'Points', points_url %></li>
|
<li><%= link_to 'Points', points_url %></li>
|
||||||
|
<li><%= link_to 'Stats', stats_url %></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<%= link_to 'Dawarich', root_path, class: 'btn btn-ghost normal-case text-xl'%>
|
<%= link_to 'Dawarich', root_path, class: 'btn btn-ghost normal-case text-xl'%>
|
||||||
|
|
@ -20,6 +21,7 @@
|
||||||
<ul class="menu menu-horizontal px-1">
|
<ul class="menu menu-horizontal px-1">
|
||||||
<li><%= link_to 'Imports', imports_url %></li>
|
<li><%= link_to 'Imports', imports_url %></li>
|
||||||
<li><%= link_to 'Points', points_url %></li>
|
<li><%= link_to 'Points', points_url %></li>
|
||||||
|
<li><%= link_to 'Stats', stats_url %></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="navbar-end">
|
<div class="navbar-end">
|
||||||
|
|
|
||||||
13
app/views/stats/_stat.html.erb
Normal file
13
app/views/stats/_stat.html.erb
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<div id="<%= dom_id stat %>" class="card w-full bg-base-200 shadow-xl">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="card-title">
|
||||||
|
<%= link_to points_url(url_time(stat)), class: 'underline hover:no-underline' do %>
|
||||||
|
<%= "#{Date::MONTHNAMES[stat.month]} of #{stat.year}" %>
|
||||||
|
<% end %>
|
||||||
|
</h2>
|
||||||
|
<p><%= stat.distance %>km</p>
|
||||||
|
<div class="card-actions justify-end">
|
||||||
|
<%= stat.toponyms.count %> countries, <%= stat.toponyms.sum { _1['cities'].count } %> cities
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
10
app/views/stats/index.html.erb
Normal file
10
app/views/stats/index.html.erb
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<div class="w-full">
|
||||||
|
<% @stats.each do |year, stats| %>
|
||||||
|
<h2 class='text-3xl font-bold mt-10'><%= year %></h2>
|
||||||
|
<div class="mt-5 grid grid-cols-1 sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-3 gap-6 p-4">
|
||||||
|
<% stats.each do |stat| %>
|
||||||
|
<%= render stat %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
Rails.application.routes.draw do
|
Rails.application.routes.draw do
|
||||||
get 'points/index'
|
|
||||||
resources :imports
|
resources :imports
|
||||||
|
resources :stats, only: :index
|
||||||
|
|
||||||
root to: 'home#index'
|
root to: 'home#index'
|
||||||
devise_for :users
|
devise_for :users
|
||||||
|
|
||||||
|
|
|
||||||
15
db/migrate/20240323160300_create_stats.rb
Normal file
15
db/migrate/20240323160300_create_stats.rb
Normal file
|
|
@ -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
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
class AddIndexToPointsTimestamp < ActiveRecord::Migration[7.1]
|
||||||
|
def change
|
||||||
|
add_index :points, :timestamp
|
||||||
|
end
|
||||||
|
end
|
||||||
5
db/migrate/20240323190039_add_user_id_to_stat.rb
Normal file
5
db/migrate/20240323190039_add_user_id_to_stat.rb
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
class AddUserIdToStat < ActiveRecord::Migration[7.1]
|
||||||
|
def change
|
||||||
|
add_reference :stats, :user, null: false, foreign_key: true
|
||||||
|
end
|
||||||
|
end
|
||||||
18
db/schema.rb
generated
18
db/schema.rb
generated
|
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# 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
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
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 ["country"], name: "index_points_on_country"
|
||||||
t.index ["import_id"], name: "index_points_on_import_id"
|
t.index ["import_id"], name: "index_points_on_import_id"
|
||||||
t.index ["latitude", "longitude"], name: "index_points_on_latitude_and_longitude"
|
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"
|
t.index ["trigger"], name: "index_points_on_trigger"
|
||||||
end
|
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|
|
create_table "users", force: :cascade do |t|
|
||||||
t.string "email", default: "", null: false
|
t.string "email", default: "", null: false
|
||||||
t.string "encrypted_password", 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
|
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
add_foreign_key "stats", "users"
|
||||||
end
|
end
|
||||||
|
|
|
||||||
8
spec/factories/stats.rb
Normal file
8
spec/factories/stats.rb
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
FactoryBot.define do
|
||||||
|
factory :stat do
|
||||||
|
year { 1 }
|
||||||
|
month { 1 }
|
||||||
|
distance { 1 }
|
||||||
|
toponyms { "" }
|
||||||
|
end
|
||||||
|
end
|
||||||
6
spec/models/stat_spec.rb
Normal file
6
spec/models/stat_spec.rb
Normal file
|
|
@ -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
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe User, type: :model do
|
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
|
end
|
||||||
|
|
|
||||||
11
spec/requests/stats_spec.rb
Normal file
11
spec/requests/stats_spec.rb
Normal file
|
|
@ -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
|
||||||
Loading…
Reference in a new issue