Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
852 changes: 852 additions & 0 deletions .github/skills/controllers/SKILL.md

Large diffs are not rendered by default.

479 changes: 479 additions & 0 deletions .github/skills/debugging/SKILL.md

Large diffs are not rendered by default.

738 changes: 738 additions & 0 deletions .github/skills/hotwire/SKILL.md

Large diffs are not rendered by default.

607 changes: 607 additions & 0 deletions .github/skills/jobs/SKILL.md

Large diffs are not rendered by default.

752 changes: 752 additions & 0 deletions .github/skills/models/SKILL.md

Large diffs are not rendered by default.

645 changes: 645 additions & 0 deletions .github/skills/security/SKILL.md

Large diffs are not rendered by default.

980 changes: 980 additions & 0 deletions .github/skills/styling/SKILL.md

Large diffs are not rendered by default.

960 changes: 960 additions & 0 deletions .github/skills/testing/SKILL.md

Large diffs are not rendered by default.

841 changes: 841 additions & 0 deletions .github/skills/views/SKILL.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ gem "neighbor"
gem "bcrypt", "~> 3.1.7"
gem "omniauth", "~> 2.1"
gem "omniauth-oauth2"
gem "omniauth-google-oauth2"
gem "omniauth-rails_csrf_protection"

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
Expand Down
8 changes: 7 additions & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ GEM
bindex (0.8.1)
bootsnap (1.18.6)
msgpack (~> 1.2)
brakeman (7.1.2)
brakeman (8.0.4)
racc
builder (3.3.0)
capybara (3.40.0)
Expand Down Expand Up @@ -231,6 +231,11 @@ GEM
hashie (>= 3.4.6)
rack (>= 2.2.3)
rack-protection
omniauth-google-oauth2 (1.2.1)
jwt (>= 2.9.2)
oauth2 (~> 2.0)
omniauth (~> 2.0)
omniauth-oauth2 (~> 1.8)
omniauth-oauth2 (1.8.0)
oauth2 (>= 1.4, < 3)
omniauth (~> 2.0)
Expand Down Expand Up @@ -460,6 +465,7 @@ DEPENDENCIES
kaminari
neighbor
omniauth (~> 2.1)
omniauth-google-oauth2
omniauth-oauth2
omniauth-rails_csrf_protection
propshaft
Expand Down
9 changes: 7 additions & 2 deletions app/controllers/activities_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def show
# GET /activities/new
def new
@activity = Activity.new
@week = params[:week]
# If creating from plan with specific date, pre-populate Activity records
if params[:plan_id].present? && params[:date].present?
# And provide the plan object for easy navigation back to the plan from views
Expand All @@ -31,15 +32,18 @@ def new

# GET /activities/1/edit
def edit
@week = params[:week]
@plan = @activity.plan
end

# POST /activities or /activities.json
def create
@activity = Activity.new(activity_params)
@week = params[:week]

respond_to do |format|
if @activity.save
format.html { redirect_to plan_path(@activity.plan), notice: "Activity was successfully created." }
format.html { redirect_to plan_path(@activity.plan, week: @week), notice: "Activity was successfully created." }
format.json { render :show, status: :created, location: @activity }
Comment thread
apdarr marked this conversation as resolved.
else
format.html { render :new, status: :unprocessable_entity }
Expand All @@ -50,9 +54,10 @@ def create

# PATCH/PUT /activities/1 or /activities/1.json
def update
@week = params[:week]
respond_to do |format|
if @activity.update(activity_params)
format.html { redirect_to plan_path(@activity.plan), notice: "Activity was successfully updated." }
format.html { redirect_to plan_path(@activity.plan, week: @week), notice: "Activity was successfully updated." }
format.json { render :show, status: :ok, location: @activity }
else
format.html { render :edit, status: :unprocessable_entity }
Expand Down
4 changes: 4 additions & 0 deletions app/controllers/plans_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ def index

# GET /plans/1 or /plans/1.json
def show
total_weeks = (@plan&.length || 0).to_i
max_week_index = [ total_weeks - 1, 0 ].max

@week = params.fetch(:week, 0).to_i.clamp(0, max_week_index)
end

# GET /plans/new
Expand Down
6 changes: 6 additions & 0 deletions app/controllers/profile_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ def edit

def update
@user = Current.user

unless @user.strava_connected?
redirect_to edit_profile_path, alert: "Connect your Strava account before enabling activity sync."
return
end

enable_webhooks = profile_settings_params[:enable_strava_webhooks] == "1"

begin
Expand Down
67 changes: 53 additions & 14 deletions app/controllers/sessions_controller.rb
Original file line number Diff line number Diff line change
@@ -1,33 +1,72 @@
class SessionsController < ApplicationController
allow_unauthenticated_access only: [ :new, :create ]
allow_unauthenticated_access only: [ :new, :create, :failure ]
rate_limit to: 10, within: 3.minutes, only: :create, with: -> { redirect_to root_url, alert: "Try again later." }

def new
render :new
end

def create
# Rails.logger.debug "Strava Client ID: #{ENV['STRAVA_CLIENT_ID'].inspect}"
# Rails.logger.debug "Strava Client Secret: #{ENV['STRAVA_CLIENT_SECRET'].inspect}"
auth = request.env["omniauth.auth"]
user = User.find_or_create_from_strava(auth)

if user.persisted?
session = Session.create!(
user: user,
ip_address: request.remote_ip,
user_agent: request.user_agent
)

cookies.signed[:session_id] = { value: session.id, httponly: true }
redirect_to root_path, notice: "Successfully signed in with Strava!"
if auth.blank?
redirect_to new_session_path, alert: "Authentication failed. Please try again."
return
end

if linking_strava?(auth)
link_strava_account(auth)
else
redirect_to root_path, alert: "Failed to sign in with Strava."
sign_in_user(auth)
end
Comment thread
apdarr marked this conversation as resolved.
end

def failure
redirect_to new_session_path, alert: "Authentication failed: #{params[:message]&.humanize}."
end

def destroy
terminate_session
redirect_to root_path, notice: "Successfully signed out!"
end

private

def linking_strava?(auth)
request.env["omniauth.origin"] == "link_strava" && authenticated? && auth.provider == "strava"
end

def link_strava_account(auth)
existing_user = User.find_by(strava_id: auth.uid)

if existing_user && existing_user != Current.user
redirect_to edit_profile_path, alert: "This Strava account is already linked to another user."
return
end

Current.user.link_strava!(auth)
redirect_to edit_profile_path, notice: "Strava account connected successfully!"
end

def sign_in_user(auth)
user = find_or_create_user(auth)

if user.persisted?
start_new_session_for(user)
redirect_to root_path, notice: "Successfully signed in!"
else
redirect_to root_path, alert: "Failed to sign in."
end
end

def find_or_create_user(auth)
case auth.provider
when "strava"
User.find_or_create_from_strava(auth)
when "google_oauth2"
User.find_or_create_from_google(auth)
else
raise "Unsupported OAuth provider: #{auth.provider}"
end
end
end
39 changes: 0 additions & 39 deletions app/javascript/controllers/week_selector_controller.js

This file was deleted.

31 changes: 27 additions & 4 deletions app/jobs/fetch_and_match_strava_activity_job.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
class FetchAndMatchStravaActivityJob < ApplicationJob
queue_as :default

# TODO re-do tests for this job after refactor
Comment thread
apdarr marked this conversation as resolved.
def perform(user_id, strava_activity_id)
user = User.find(user_id)

Expand All @@ -19,8 +20,7 @@ def perform(user_id, strava_activity_id)
strava_activity.save!
Rails.logger.info "Stored Strava activity #{strava_activity_id} for user #{user.id}"

# Queue matching job to find potential linked workouts
MatchStravaActivityJob.perform_later(strava_activity.id)
match_to_activity(user_id, strava_activity.id, strava_activity.distance, strava_activity.start_date_local)
rescue StandardError => e
Rails.logger.error "Error fetching Strava activity #{strava_activity_id}: #{e.message}"
raise e
Expand All @@ -30,7 +30,30 @@ def perform(user_id, strava_activity_id)
private

def fetch_from_strava(user, strava_activity_id)
client = Strava::Api::Client.new(access_token: user.access_token)
client.activity(strava_activity_id)
client = Strava::Api::Client.new(access_token: user.fresh_access_token)
activity = client.activity(strava_activity_id)

activity
end

def match_to_activity(user_id, strava_activity_id, distance, start_date_local)
# Convert Strava's default meters to miles
distance = distance.to_f / 1609.34
activity_date = start_date_local.to_date

# Match on same date and within 0.5 miles of distance
matched_activity = Activity.where(user_id: user_id)
.where(start_date_local: activity_date.beginning_of_day..activity_date.end_of_day)
.where("distance BETWEEN ? AND ?", distance - 0.5, distance + 0.5)
.first
Comment thread
apdarr marked this conversation as resolved.

strava_activity = StravaActivity.find(strava_activity_id)

if matched_activity
strava_activity.update!(activity_id: matched_activity.id, match_status: "matched")
Rails.logger.info "Matched Strava activity #{strava_activity_id} to Activity #{matched_activity.id}"
else
Rails.logger.info "No matching Activity found for Strava activity #{strava_activity_id}"
end
end
end
117 changes: 0 additions & 117 deletions app/jobs/match_strava_activity_job.rb

This file was deleted.

Loading