Add workouts

This commit is contained in:
2023-04-25 15:56:41 +10:00
parent 971e0007dd
commit c89b5b0998
13 changed files with 197 additions and 0 deletions

View File

@@ -23,6 +23,9 @@ gem "puma"
gem "rake"
gem "slim"
gem "builder"
gem "georuby"
gem "gnuplot"
gem "matrix"
gem "httparty"
gem "redcarpet"

View File

@@ -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!

View 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

View 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

View 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
View 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

View 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"

View 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
View File

@@ -0,0 +1,5 @@
Hanami.app.register_provider :geo, namespace: true do
start do
register "gpx_parser", Adamantium::Geo::GpxParser.new
end
end

View File

@@ -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"

View 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

View 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

View 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