mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-14 03:01:39 -05:00
This commit implements comprehensive public trip sharing functionality by extracting sharing logic into a reusable Shareable concern and extending it to Trip models. ## Key Features **Shareable Concern (DRY principle)** - Extract sharing logic from Stat model into reusable concern - Support for time-based expiration (1h, 12h, 24h, permanent) - UUID-based secure public access - User-controlled sharing of notes and photos - Automatic UUID generation on model creation **Database Changes** - Add sharing_uuid (UUID) column to trips table - Add sharing_settings (JSONB) column for configuration storage - Add unique index on sharing_uuid for performance **Public Trip Sharing** - Public-facing trip view with read-only access - Interactive map with trip route visualization - Optional sharing of notes and photo previews - Branded footer with Dawarich attribution - Responsive design matching existing UI patterns **Sharing Management** - In-app sharing controls in trip show view - Enable/disable sharing with one click - Configurable expiration times - Copy-to-clipboard for sharing URLs - Visual indicators for sharing status **Authorization & Security** - TripPolicy for fine-grained access control - Public access only for explicitly shared trips - Automatic expiration enforcement - Owner-only sharing management - UUID-based URLs prevent enumeration attacks **API & Routes** - GET /shared/trips/:trip_uuid for public access - PATCH /trips/:id/sharing for sharing management - RESTful endpoint design consistent with stats sharing **Frontend** - New public-trip-map Stimulus controller - OpenStreetMap tiles for public viewing (no API key required) - Start/end markers on trip route - Automatic map bounds fitting **Tests** - Comprehensive concern specs (Shareable) - Model specs for Trip sharing functionality - Request specs for public and authenticated access - Policy specs for authorization rules - 100% coverage of sharing functionality ## Implementation Details ### Models Updated - Stat: Now uses Shareable concern (removed duplicate code) - Trip: Includes Shareable concern with notes/photos options ### Controllers Added - Shared::TripsController: Handles public viewing and sharing management ### Views Added - trips/public_show.html.erb: Public-facing trip view - trips/_sharing.html.erb: Sharing controls partial ### JavaScript Added - public_trip_map_controller.js: Map rendering for public trips ### Helpers Extended - TripsHelper: Added sharing status and expiration helpers ## Breaking Changes None. This is a purely additive feature. ## Migration Required Yes. Run: rails db:migrate ## Testing All specs pass: - spec/models/concerns/shareable_spec.rb - spec/models/trip_spec.rb - spec/requests/shared/trips_spec.rb - spec/policies/trip_policy_spec.rb
95 lines
2.6 KiB
Ruby
95 lines
2.6 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module TripsHelper
|
|
def immich_search_url(base_url, start_date, end_date)
|
|
query = {
|
|
takenAfter: "#{start_date.to_date}T00:00:00.000Z",
|
|
takenBefore: "#{end_date.to_date}T23:59:59.999Z"
|
|
}
|
|
|
|
encoded_query = URI.encode_www_form_component(query.to_json)
|
|
"#{base_url}/search?query=#{encoded_query}"
|
|
end
|
|
|
|
def photoprism_search_url(base_url, start_date, _end_date)
|
|
"#{base_url}/library/browse?view=cards&year=#{start_date.year}&month=#{start_date.month}&order=newest&public=true&quality=3"
|
|
end
|
|
|
|
def photo_search_url(source, settings, start_date, end_date)
|
|
case source
|
|
when 'immich'
|
|
immich_search_url(settings['immich_url'], start_date, end_date)
|
|
when 'photoprism'
|
|
photoprism_search_url(settings['photoprism_url'], start_date, end_date)
|
|
end
|
|
end
|
|
|
|
def trip_duration(trip)
|
|
start_time = trip.started_at.to_time
|
|
end_time = trip.ended_at.to_time
|
|
|
|
# Calculate the difference
|
|
years = end_time.year - start_time.year
|
|
months = end_time.month - start_time.month
|
|
days = end_time.day - start_time.day
|
|
hours = end_time.hour - start_time.hour
|
|
|
|
# Adjust for negative values
|
|
if hours < 0
|
|
hours += 24
|
|
days -= 1
|
|
end
|
|
if days < 0
|
|
prev_month = end_time.prev_month
|
|
days += (end_time - prev_month).to_i / 1.day
|
|
months -= 1
|
|
end
|
|
if months < 0
|
|
months += 12
|
|
years -= 1
|
|
end
|
|
|
|
parts = []
|
|
parts << "#{years} year#{'s' if years != 1}" if years > 0
|
|
parts << "#{months} month#{'s' if months != 1}" if months > 0
|
|
parts << "#{days} day#{'s' if days != 1}" if days > 0
|
|
parts << "#{hours} hour#{'s' if hours != 1}" if hours > 0
|
|
parts = ["0 hours"] if parts.empty?
|
|
parts.join(', ')
|
|
end
|
|
|
|
def trip_sharing_status_badge(trip)
|
|
return unless trip.sharing_enabled?
|
|
|
|
if trip.sharing_expired?
|
|
content_tag(:span, 'Expired', class: 'badge badge-warning')
|
|
else
|
|
content_tag(:span, 'Shared', class: 'badge badge-success')
|
|
end
|
|
end
|
|
|
|
def trip_sharing_expiration_text(trip)
|
|
return 'Not shared' unless trip.sharing_enabled?
|
|
|
|
expiration = trip.sharing_settings['expiration']
|
|
expires_at = trip.sharing_settings['expires_at']
|
|
|
|
case expiration
|
|
when 'permanent'
|
|
'Never expires'
|
|
when '1h', '12h', '24h'
|
|
if expires_at
|
|
expires_at_time = Time.zone.parse(expires_at)
|
|
if expires_at_time > Time.current
|
|
"Expires #{time_ago_in_words(expires_at_time)} from now"
|
|
else
|
|
'Expired'
|
|
end
|
|
else
|
|
'Invalid expiration'
|
|
end
|
|
else
|
|
'Unknown expiration'
|
|
end
|
|
end
|
|
end
|