Add workouts
This commit is contained in:
3
Gemfile
3
Gemfile
@@ -23,6 +23,9 @@ gem "puma"
|
||||
gem "rake"
|
||||
gem "slim"
|
||||
gem "builder"
|
||||
gem "georuby"
|
||||
gem "gnuplot"
|
||||
gem "matrix"
|
||||
|
||||
gem "httparty"
|
||||
gem "redcarpet"
|
||||
|
@@ -137,6 +137,8 @@ GEM
|
||||
ffi (>= 1.0.0)
|
||||
rake
|
||||
formatador (1.1.0)
|
||||
georuby (2.5.2)
|
||||
gnuplot (2.6.2)
|
||||
guard (2.18.0)
|
||||
formatador (>= 0.2.4)
|
||||
listen (>= 2.7, < 4.0)
|
||||
@@ -232,6 +234,7 @@ GEM
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
matrix (0.4.2)
|
||||
method_source (1.0.0)
|
||||
mime-types (3.4.1)
|
||||
mime-types-data (~> 3.2015)
|
||||
@@ -411,6 +414,8 @@ DEPENDENCIES
|
||||
dry-matcher
|
||||
dry-monads
|
||||
dry-types
|
||||
georuby
|
||||
gnuplot
|
||||
guard-puma (~> 0.8)
|
||||
hanami (~> 2.0.0)
|
||||
hanami-controller (~> 2.0.0)
|
||||
@@ -422,6 +427,7 @@ DEPENDENCIES
|
||||
httparty
|
||||
lastfm (~> 1.27)
|
||||
mail
|
||||
matrix
|
||||
ogpr
|
||||
pg
|
||||
pinboard!
|
||||
|
27
app/actions/workouts/create.rb
Normal file
27
app/actions/workouts/create.rb
Normal file
@@ -0,0 +1,27 @@
|
||||
module Adamantium
|
||||
module Actions
|
||||
module Workouts
|
||||
class Create < Action
|
||||
include Deps["geo.gpx_parser", "commands.workouts.create"]
|
||||
|
||||
def handle(req, res)
|
||||
tempfile = Tempfile.new(%w/path .gpx/)
|
||||
tempfile.write req.params[:file]
|
||||
tempfile.rewind
|
||||
|
||||
gpxfile = gpx_parser.call(file: tempfile)
|
||||
|
||||
if gpxfile.success?
|
||||
create.call(**gpxfile.value!)
|
||||
res.status = 201
|
||||
else
|
||||
res.status = 500
|
||||
end
|
||||
ensure
|
||||
tempfile.close
|
||||
tempfile.unlink
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
13
app/actions/workouts/index.rb
Normal file
13
app/actions/workouts/index.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
module Adamantium
|
||||
module Actions
|
||||
module Workouts
|
||||
class Index < Action
|
||||
include Deps["views.workouts.index"]
|
||||
|
||||
def handle(req, res)
|
||||
res.render index
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
19
app/commands/workouts/create.rb
Normal file
19
app/commands/workouts/create.rb
Normal file
@@ -0,0 +1,19 @@
|
||||
|
||||
require "securerandom"
|
||||
require "dry/monads"
|
||||
require "filemagic"
|
||||
|
||||
module Adamantium
|
||||
module Commands
|
||||
module Workouts
|
||||
class Create < Command
|
||||
include Deps["repos.workout_repo"]
|
||||
include Dry::Monads[:result]
|
||||
|
||||
def call(svg:, distance:)
|
||||
workout_repo.create(path: svg, distance: distance, published_at: Time.now)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
11
app/repos/workout_repo.rb
Normal file
11
app/repos/workout_repo.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
module Adamantium
|
||||
module Repos
|
||||
class WorkoutRepo < Adamantium::Repo[:workouts]
|
||||
commands :create, update: :by_pk
|
||||
|
||||
def list
|
||||
workouts.order(:published_at).to_a
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
12
app/templates/workouts/index.html.slim
Normal file
12
app/templates/workouts/index.html.slim
Normal file
@@ -0,0 +1,12 @@
|
||||
div class="mb-12 prose dark:prose-invert max-w-prose mx-auto text-gray-800 dark:text-gray-200"
|
||||
h1 Hikes
|
||||
|
||||
div class="max-w-prose mx-auto"
|
||||
- workouts_by_year.each do |year, workouts|
|
||||
h3= year
|
||||
h1= "#{(workouts.map{|w| w.distance }.sum / 1000).round} km"
|
||||
div class="grid grid-cols-3 gap-4 mb-4 max-w-prose mx-auto"
|
||||
- workouts.each do |workout|
|
||||
== workout.path
|
||||
|
||||
div class="max-w-screen-md mx-auto border-t-4 border-solid border-gray-400 dark:border-gray-600"
|
18
app/views/workouts/index.rb
Normal file
18
app/views/workouts/index.rb
Normal file
@@ -0,0 +1,18 @@
|
||||
module Adamantium
|
||||
module Views
|
||||
module Workouts
|
||||
class Index < View
|
||||
include Deps["repos.workout_repo"]
|
||||
|
||||
expose :workouts_by_year do
|
||||
workout_repo
|
||||
.list
|
||||
.group_by { |wo|
|
||||
wo.published_at.year
|
||||
}
|
||||
.sort
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
5
config/providers/geo.rb
Normal file
5
config/providers/geo.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
Hanami.app.register_provider :geo, namespace: true do
|
||||
start do
|
||||
register "gpx_parser", Adamantium::Geo::GpxParser.new
|
||||
end
|
||||
end
|
@@ -19,6 +19,7 @@ module Adamantium
|
||||
get "/post/top_tracks/:slug", to: "posts.top_tracks"
|
||||
get "/post/:slug", to: "posts.show"
|
||||
get "/posts", to: "posts.index"
|
||||
get "/posts/archive/:year", to: "posts.archive"
|
||||
|
||||
get "/bookmarks", to: "bookmarks.index"
|
||||
get "/bookmarks/metadata/:id", to: "bookmarks.metadata"
|
||||
@@ -28,6 +29,7 @@ module Adamantium
|
||||
get "/places", to: "places.index"
|
||||
get "/statuses", to: "statuses.index"
|
||||
|
||||
get "/tags", to: "tags.index"
|
||||
get "/tagged/:slug", to: "tags.show"
|
||||
|
||||
get "/key", to: "key.show" if Hanami.app.settings.micropub_pub_key
|
||||
@@ -35,6 +37,11 @@ module Adamantium
|
||||
get "/feeds/rss", to: "feeds.rss"
|
||||
get "/feeds/statuses_rss", to: "feeds.statuses_rss"
|
||||
|
||||
get "/more", to: "more.index"
|
||||
|
||||
get "/hikes", to: "workouts.index"
|
||||
post "/workouts", to: "workouts.create"
|
||||
|
||||
get "/:slug", to: "pages.show"
|
||||
|
||||
redirect "deploying-a-hanami-app-to-fly-io", to: "/post/deploying-a-hanami-20-app-to-flyio"
|
||||
|
12
db/migrate/20230424120318_create_workouts.rb
Normal file
12
db/migrate/20230424120318_create_workouts.rb
Normal file
@@ -0,0 +1,12 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
ROM::SQL.migration do
|
||||
change do
|
||||
create_table :workouts do
|
||||
primary_key :id
|
||||
column :path, :text, null: false
|
||||
column :distance, :float, null: false
|
||||
column :published_at, :timestamp
|
||||
end
|
||||
end
|
||||
end
|
51
lib/adamantium/geo/gpx_parser.rb
Normal file
51
lib/adamantium/geo/gpx_parser.rb
Normal file
@@ -0,0 +1,51 @@
|
||||
require "geo_ruby"
|
||||
require "geo_ruby/gpx"
|
||||
require "gnuplot"
|
||||
require "dry/monads"
|
||||
|
||||
module Adamantium
|
||||
module Geo
|
||||
class GpxParser
|
||||
include Dry::Monads[:result]
|
||||
|
||||
def call(file:)
|
||||
gpxfile = GeoRuby::Gpx4r::GpxFile.open(file.path)
|
||||
|
||||
x = gpxfile.as_line_string.points.flat_map { |p| p.x }
|
||||
y = gpxfile.as_line_string.points.flat_map { |p| p.y }
|
||||
|
||||
maxlat = y.max
|
||||
minlat = y.min
|
||||
|
||||
maxlon = x.max
|
||||
minlon = x.min
|
||||
|
||||
latdiff = maxlat - minlat
|
||||
londiff = maxlon - minlon
|
||||
|
||||
svg = Gnuplot.open do |gp|
|
||||
Gnuplot::Plot.new(gp) do |plot|
|
||||
plot.arbitrary_lines << "unset border"
|
||||
plot.arbitrary_lines << "unset xtics"
|
||||
plot.arbitrary_lines << "unset ytics"
|
||||
plot.arbitrary_lines << "set size ratio -1"
|
||||
plot.arbitrary_lines << "set yrange [#{minlat}:#{maxlat}]" if latdiff >= londiff # portrait
|
||||
plot.arbitrary_lines << "set xrange [#{minlon}:#{maxlon}]" if latdiff < londiff # landscape
|
||||
plot.arbitrary_lines << "set term svg"
|
||||
plot.data << Gnuplot::DataSet.new([x, y]) do |ds|
|
||||
ds.with = "lines"
|
||||
ds.linewidth = 4
|
||||
ds.linecolor = 'rgb "#84cc16"'
|
||||
ds.notitle
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
svg.gsub!('width="600" height="480"', 'width="100%" height="100%"')
|
||||
# svg.gsub!('viewBox="0 0 600 480"', 'viewBox="0 0 100% 100%"')
|
||||
|
||||
Success({svg: svg, distance: gpxfile.as_line_string.spherical_distance})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
13
lib/adamantium/persistence/relations/workouts.rb
Normal file
13
lib/adamantium/persistence/relations/workouts.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Adamantium
|
||||
module Persistence
|
||||
module Relations
|
||||
class Workouts < ROM::Relation[:sql]
|
||||
schema :workouts, infer: true
|
||||
|
||||
auto_struct(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Reference in New Issue
Block a user