mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-10 01:01:39 -05:00
Implement custom map tiles and user settings with default values
This commit is contained in:
parent
b8c69c2a76
commit
0b362168c9
18 changed files with 347 additions and 25 deletions
15
CHANGELOG.md
15
CHANGELOG.md
|
|
@ -4,7 +4,20 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
# 0.24.0 - 2025-02-09
|
||||
# 0.24.1 - 2025-02-10
|
||||
|
||||
## Custom map tiles
|
||||
|
||||
In the user settings, you can now set a custom tile URL for the map. This is useful if you want to use a custom map tile provider or if you want to use a map tile provider that is not listed in the dropdown.
|
||||
|
||||
To set a custom tile URL, go to the user settings and set the `Maps` section to your liking. Be mindful that currently, only raster tiles are supported. The URL should be a valid tile URL, like `https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png`. You, as the user, are responsible for any extra costs that may occur due to using a custom tile URL.
|
||||
|
||||
### Added
|
||||
|
||||
- Safe settings for user with default values.
|
||||
- In the user settings, you can now set a custom tile URL for the map. #429 #715
|
||||
|
||||
# 0.24.0 - 2025-02-10
|
||||
|
||||
## Points speed units
|
||||
|
||||
|
|
|
|||
22
app/controllers/settings/maps_controller.rb
Normal file
22
app/controllers/settings/maps_controller.rb
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Settings::MapsController < ApplicationController
|
||||
before_action :authenticate_user!
|
||||
|
||||
def index
|
||||
@maps = current_user.safe_settings.maps
|
||||
end
|
||||
|
||||
def update
|
||||
current_user.settings['maps'] = settings_params
|
||||
current_user.save!
|
||||
|
||||
redirect_to settings_maps_path, notice: 'Settings updated'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def settings_params
|
||||
params.require(:maps).permit(:name, :url)
|
||||
end
|
||||
end
|
||||
67
app/javascript/controllers/map_preview_controller.js
Normal file
67
app/javascript/controllers/map_preview_controller.js
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import { Controller } from "@hotwired/stimulus"
|
||||
import L from "leaflet"
|
||||
import { showFlashMessage } from "../maps/helpers"
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ["urlInput", "mapContainer", "saveButton"]
|
||||
|
||||
DEFAULT_TILE_URL = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
|
||||
|
||||
connect() {
|
||||
console.log("Controller connected!")
|
||||
// Wait for the next frame to ensure the DOM is ready
|
||||
requestAnimationFrame(() => {
|
||||
// Force container height
|
||||
this.mapContainerTarget.style.height = '500px'
|
||||
this.initializeMap()
|
||||
})
|
||||
}
|
||||
|
||||
initializeMap() {
|
||||
console.log("Initializing map...")
|
||||
if (!this.map) {
|
||||
this.map = L.map(this.mapContainerTarget).setView([51.505, -0.09], 13)
|
||||
// Invalidate size after initialization
|
||||
setTimeout(() => {
|
||||
this.map.invalidateSize()
|
||||
}, 0)
|
||||
this.updatePreview()
|
||||
}
|
||||
}
|
||||
|
||||
updatePreview() {
|
||||
console.log("Updating preview...")
|
||||
const url = this.urlInputTarget.value || this.DEFAULT_TILE_URL
|
||||
|
||||
// Only animate if save button target exists
|
||||
if (this.hasSaveButtonTarget) {
|
||||
this.saveButtonTarget.classList.add('btn-animate')
|
||||
setTimeout(() => {
|
||||
this.saveButtonTarget.classList.remove('btn-animate')
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
if (this.currentLayer) {
|
||||
this.map.removeLayer(this.currentLayer)
|
||||
}
|
||||
|
||||
try {
|
||||
this.currentLayer = L.tileLayer(url, {
|
||||
maxZoom: 19,
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
}).addTo(this.map)
|
||||
} catch (e) {
|
||||
console.error('Invalid tile URL:', e)
|
||||
showFlashMessage('error', 'Invalid tile URL. Reverting to OpenStreetMap.')
|
||||
|
||||
// Reset input to default OSM URL
|
||||
this.urlInputTarget.value = this.DEFAULT_TILE_URL
|
||||
|
||||
// Create default layer
|
||||
this.currentLayer = L.tileLayer(this.DEFAULT_TILE_URL, {
|
||||
maxZoom: 19,
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
}).addTo(this.map)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -385,8 +385,7 @@ export default class extends Controller {
|
|||
|
||||
baseMaps() {
|
||||
let selectedLayerName = this.userSettings.preferred_map_layer || "OpenStreetMap";
|
||||
|
||||
return {
|
||||
let maps = {
|
||||
OpenStreetMap: osmMapLayer(this.map, selectedLayerName),
|
||||
"OpenStreetMap.HOT": osmHotMapLayer(this.map, selectedLayerName),
|
||||
OPNV: OPNVMapLayer(this.map, selectedLayerName),
|
||||
|
|
@ -397,6 +396,33 @@ export default class extends Controller {
|
|||
esriWorldImagery: esriWorldImageryMapLayer(this.map, selectedLayerName),
|
||||
esriWorldGrayCanvas: esriWorldGrayCanvasMapLayer(this.map, selectedLayerName)
|
||||
};
|
||||
|
||||
// Add custom map if it exists in settings
|
||||
if (this.userSettings.maps && this.userSettings.maps.url) {
|
||||
const customLayer = L.tileLayer(this.userSettings.maps.url, {
|
||||
maxZoom: 19,
|
||||
attribution: "© OpenStreetMap contributors"
|
||||
});
|
||||
|
||||
// If this is the preferred layer, add it to the map immediately
|
||||
if (selectedLayerName === this.userSettings.maps.name) {
|
||||
customLayer.addTo(this.map);
|
||||
// Remove any other base layers that might be active
|
||||
Object.values(maps).forEach(layer => {
|
||||
if (this.map.hasLayer(layer)) {
|
||||
this.map.removeLayer(layer);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
maps[this.userSettings.maps.name] = customLayer;
|
||||
} else {
|
||||
// If no custom map is set, ensure a default layer is added
|
||||
const defaultLayer = maps[selectedLayerName] || maps["OpenStreetMap"];
|
||||
defaultLayer.addTo(this.map);
|
||||
}
|
||||
|
||||
return maps;
|
||||
}
|
||||
|
||||
removeEventListeners() {
|
||||
|
|
|
|||
|
|
@ -16,13 +16,18 @@ class User < ApplicationRecord
|
|||
has_many :trips, dependent: :destroy
|
||||
|
||||
after_create :create_api_key
|
||||
before_save :strip_trailing_slashes
|
||||
before_save :sanitize_input
|
||||
|
||||
validates :email, presence: true
|
||||
|
||||
validates :reset_password_token, uniqueness: true, allow_nil: true
|
||||
|
||||
attribute :admin, :boolean, default: false
|
||||
|
||||
def safe_settings
|
||||
Users::SafeSettings.new(settings)
|
||||
end
|
||||
|
||||
def countries_visited
|
||||
stats.pluck(:toponyms).flatten.map { _1['country'] }.uniq.compact
|
||||
end
|
||||
|
|
@ -99,8 +104,9 @@ class User < ApplicationRecord
|
|||
save
|
||||
end
|
||||
|
||||
def strip_trailing_slashes
|
||||
def sanitize_input
|
||||
settings['immich_url']&.gsub!(%r{/+\z}, '')
|
||||
settings['photoprism_url']&.gsub!(%r{/+\z}, '')
|
||||
settings['maps']['url']&.strip!
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ class Areas::Visits::Create
|
|||
def initialize(user, areas)
|
||||
@user = user
|
||||
@areas = areas
|
||||
@time_threshold_minutes = 30 || user.settings['time_threshold_minutes']
|
||||
@merge_threshold_minutes = 15 || user.settings['merge_threshold_minutes']
|
||||
@time_threshold_minutes = 30 || user.safe_settings.time_threshold_minutes
|
||||
@merge_threshold_minutes = 15 || user.safe_settings.merge_threshold_minutes
|
||||
end
|
||||
|
||||
def call
|
||||
|
|
|
|||
|
|
@ -5,15 +5,15 @@ class Immich::RequestPhotos
|
|||
|
||||
def initialize(user, start_date: '1970-01-01', end_date: nil)
|
||||
@user = user
|
||||
@immich_api_base_url = URI.parse("#{user.settings['immich_url']}/api/search/metadata")
|
||||
@immich_api_key = user.settings['immich_api_key']
|
||||
@immich_api_base_url = URI.parse("#{user.safe_settings.immich_url}/api/search/metadata")
|
||||
@immich_api_key = user.safe_settings.immich_api_key
|
||||
@start_date = start_date
|
||||
@end_date = end_date
|
||||
end
|
||||
|
||||
def call
|
||||
raise ArgumentError, 'Immich API key is missing' if immich_api_key.blank?
|
||||
raise ArgumentError, 'Immich URL is missing' if user.settings['immich_url'].blank?
|
||||
raise ArgumentError, 'Immich URL is missing' if user.safe_settings.immich_url.blank?
|
||||
|
||||
data = retrieve_immich_data
|
||||
|
||||
|
|
|
|||
|
|
@ -9,14 +9,14 @@ class Photoprism::RequestPhotos
|
|||
|
||||
def initialize(user, start_date: '1970-01-01', end_date: nil)
|
||||
@user = user
|
||||
@photoprism_api_base_url = URI.parse("#{user.settings['photoprism_url']}/api/v1/photos")
|
||||
@photoprism_api_key = user.settings['photoprism_api_key']
|
||||
@photoprism_api_base_url = URI.parse("#{user.safe_settings.photoprism_url}/api/v1/photos")
|
||||
@photoprism_api_key = user.safe_settings.photoprism_api_key
|
||||
@start_date = start_date
|
||||
@end_date = end_date
|
||||
end
|
||||
|
||||
def call
|
||||
raise ArgumentError, 'Photoprism URL is missing' if user.settings['photoprism_url'].blank?
|
||||
raise ArgumentError, 'Photoprism URL is missing' if user.safe_settings.photoprism_url.blank?
|
||||
raise ArgumentError, 'Photoprism API key is missing' if photoprism_api_key.blank?
|
||||
|
||||
data = retrieve_photoprism_data
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Photos::Thumbnail
|
||||
SUPPORTED_SOURCES = %w[immich photoprism].freeze
|
||||
|
||||
def initialize(user, source, id)
|
||||
@user = user
|
||||
@source = source
|
||||
|
|
@ -8,6 +10,8 @@ class Photos::Thumbnail
|
|||
end
|
||||
|
||||
def call
|
||||
raise unsupported_source_error unless SUPPORTED_SOURCES.include?(source)
|
||||
|
||||
HTTParty.get(request_url, headers: headers)
|
||||
end
|
||||
|
||||
|
|
@ -16,11 +20,11 @@ class Photos::Thumbnail
|
|||
attr_reader :user, :source, :id
|
||||
|
||||
def source_url
|
||||
user.settings["#{source}_url"]
|
||||
user.safe_settings.public_send("#{source}_url")
|
||||
end
|
||||
|
||||
def source_api_key
|
||||
user.settings["#{source}_api_key"]
|
||||
user.safe_settings.public_send("#{source}_api_key")
|
||||
end
|
||||
|
||||
def source_path
|
||||
|
|
@ -30,8 +34,6 @@ class Photos::Thumbnail
|
|||
when 'photoprism'
|
||||
preview_token = Rails.cache.read("#{Photoprism::CachePreviewToken::TOKEN_CACHE_KEY}_#{user.id}")
|
||||
"/api/v1/t/#{id}/#{preview_token}/tile_500"
|
||||
else
|
||||
raise "Unsupported source: #{source}"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -48,4 +50,8 @@ class Photos::Thumbnail
|
|||
|
||||
request_headers
|
||||
end
|
||||
|
||||
def unsupported_source_error
|
||||
raise ArgumentError, "Unsupported source: #{source}"
|
||||
end
|
||||
end
|
||||
|
|
|
|||
91
app/services/users/safe_settings.rb
Normal file
91
app/services/users/safe_settings.rb
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Users::SafeSettings
|
||||
attr_reader :settings
|
||||
|
||||
def initialize(settings)
|
||||
@settings = settings
|
||||
end
|
||||
|
||||
# rubocop:disable Metrics/MethodLength
|
||||
def config
|
||||
{
|
||||
fog_of_war_meters: fog_of_war_meters,
|
||||
meters_between_routes: meters_between_routes,
|
||||
preferred_map_layer: preferred_map_layer,
|
||||
speed_colored_routes: speed_colored_routes,
|
||||
points_rendering_mode: points_rendering_mode,
|
||||
minutes_between_routes: minutes_between_routes,
|
||||
time_threshold_minutes: time_threshold_minutes,
|
||||
merge_threshold_minutes: merge_threshold_minutes,
|
||||
live_map_enabled: live_map_enabled,
|
||||
route_opacity: route_opacity,
|
||||
immich_url: immich_url,
|
||||
immich_api_key: immich_api_key,
|
||||
photoprism_url: photoprism_url,
|
||||
photoprism_api_key: photoprism_api_key,
|
||||
maps: maps
|
||||
}
|
||||
end
|
||||
# rubocop:enable Metrics/MethodLength
|
||||
|
||||
def fog_of_war_meters
|
||||
settings['fog_of_war_meters'] || 50
|
||||
end
|
||||
|
||||
def meters_between_routes
|
||||
settings['meters_between_routes'] || 500
|
||||
end
|
||||
|
||||
def preferred_map_layer
|
||||
settings['preferred_map_layer'] || 'OpenStreetMap'
|
||||
end
|
||||
|
||||
def speed_colored_routes
|
||||
settings['speed_colored_routes'] || false
|
||||
end
|
||||
|
||||
def points_rendering_mode
|
||||
settings['points_rendering_mode'] || 'raw'
|
||||
end
|
||||
|
||||
def minutes_between_routes
|
||||
settings['minutes_between_routes'] || 30
|
||||
end
|
||||
|
||||
def time_threshold_minutes
|
||||
settings['time_threshold_minutes'] || 30
|
||||
end
|
||||
|
||||
def merge_threshold_minutes
|
||||
settings['merge_threshold_minutes'] || 15
|
||||
end
|
||||
|
||||
def live_map_enabled
|
||||
settings['live_map_enabled'] || true
|
||||
end
|
||||
|
||||
def route_opacity
|
||||
settings['route_opacity'] || 0.6
|
||||
end
|
||||
|
||||
def immich_url
|
||||
settings['immich_url']
|
||||
end
|
||||
|
||||
def immich_api_key
|
||||
settings['immich_api_key']
|
||||
end
|
||||
|
||||
def photoprism_url
|
||||
settings['photoprism_url']
|
||||
end
|
||||
|
||||
def photoprism_api_key
|
||||
settings['photoprism_api_key']
|
||||
end
|
||||
|
||||
def maps
|
||||
settings['maps'] || {}
|
||||
end
|
||||
end
|
||||
|
|
@ -5,12 +5,12 @@
|
|||
<h1 class="font-bold text-4xl">Imports</h1>
|
||||
<%= link_to "New import", new_import_path, class: "rounded-lg py-3 px-5 bg-blue-600 text-white block font-medium" %>
|
||||
|
||||
<% if current_user.settings['immich_url'] && current_user.settings['immich_api_key'] %>
|
||||
<% if current_user.safe_settings.immich_url && current_user.safe_settings.immich_api_key %>
|
||||
<%= link_to 'Import Immich data', settings_background_jobs_path(job_name: 'start_immich_import'), method: :post, data: { confirm: 'Are you sure?', turbo_confirm: 'Are you sure?', turbo_method: :post }, class: 'rounded-lg py-3 px-5 bg-blue-600 text-white block font-medium' %>
|
||||
<% else %>
|
||||
<a href='' class="rounded-lg py-3 px-5 bg-blue-900 text-gray block font-medium tooltip cursor-not-allowed" data-tip="You need to provide your Immich instance data in the Settings">Import Immich data</a>
|
||||
<% end %>
|
||||
<% if current_user.settings['photoprism_url'] && current_user.settings['photoprism_api_key'] %>
|
||||
<% if current_user.safe_settings.photoprism_url && current_user.safe_settings.photoprism_api_key %>
|
||||
<%= link_to 'Import Photoprism data', settings_background_jobs_path(job_name: 'start_photoprism_import'), method: :post, data: { confirm: 'Are you sure?', turbo_confirm: 'Are you sure?', turbo_method: :post }, class: 'rounded-lg py-3 px-5 bg-blue-600 text-white block font-medium' %>
|
||||
<% else %>
|
||||
<a href='' class="rounded-lg py-3 px-5 bg-blue-900 text-gray block font-medium tooltip cursor-not-allowed" data-tip="You need to provide your Photoprism instance data in the Settings">Import Photoprism data</a>
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@
|
|||
data-points-target="map"
|
||||
data-distance_unit="<%= DISTANCE_UNIT %>"
|
||||
data-api_key="<%= current_user.api_key %>"
|
||||
data-user_settings=<%= current_user.settings.to_json %>
|
||||
data-user_settings='<%= current_user.settings.to_json.html_safe %>'
|
||||
data-coordinates="<%= @coordinates %>"
|
||||
data-distance="<%= @distance %>"
|
||||
data-points_number="<%= @points_number %>"
|
||||
|
|
|
|||
|
|
@ -4,4 +4,5 @@
|
|||
<%= link_to 'Users', settings_users_path, role: 'tab', class: "tab #{active_tab?(settings_users_path)}" %>
|
||||
<%= link_to 'Background Jobs', settings_background_jobs_path, role: 'tab', class: "tab #{active_tab?(settings_background_jobs_path)}" %>
|
||||
<% end %>
|
||||
<%= link_to 'Map', settings_maps_path, role: 'tab', class: "tab #{active_tab?(settings_maps_path)}" %>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -9,20 +9,20 @@
|
|||
<%= form_for :settings, url: settings_path, method: :patch, data: { turbo_method: :patch, turbo: false } do |f| %>
|
||||
<div class="form-control my-2">
|
||||
<%= f.label :immich_url %>
|
||||
<%= f.text_field :immich_url, value: current_user.settings['immich_url'], class: "input input-bordered", placeholder: 'http://192.168.0.1:2283' %>
|
||||
<%= f.text_field :immich_url, value: current_user.safe_settings.immich_url, class: "input input-bordered", placeholder: 'http://192.168.0.1:2283' %>
|
||||
</div>
|
||||
<div class="form-control my-2">
|
||||
<%= f.label :immich_api_key %>
|
||||
<%= f.text_field :immich_api_key, value: current_user.settings['immich_api_key'], class: "input input-bordered", placeholder: 'xxxxxxxxxxxxxx' %>
|
||||
<%= f.text_field :immich_api_key, value: current_user.safe_settings.immich_api_key, class: "input input-bordered", placeholder: 'xxxxxxxxxxxxxx' %>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<div class="form-control my-2">
|
||||
<%= f.label :photoprism_url %>
|
||||
<%= f.text_field :photoprism_url, value: current_user.settings['photoprism_url'], class: "input input-bordered", placeholder: 'http://192.168.0.1:2342' %>
|
||||
<%= f.text_field :photoprism_url, value: current_user.safe_settings.photoprism_url, class: "input input-bordered", placeholder: 'http://192.168.0.1:2342' %>
|
||||
</div>
|
||||
<div class="form-control my-2">
|
||||
<%= f.label :photoprism_api_key %>
|
||||
<%= f.text_field :photoprism_api_key, value: current_user.settings['photoprism_api_key'], class: "input input-bordered", placeholder: 'xxxxxxxxxxxxxx' %>
|
||||
<%= f.text_field :photoprism_api_key, value: current_user.safe_settings.photoprism_api_key, class: "input input-bordered", placeholder: 'xxxxxxxxxxxxxx' %>
|
||||
</div>
|
||||
|
||||
<div class="form-control my-2">
|
||||
|
|
|
|||
45
app/views/settings/maps/index.html.erb
Normal file
45
app/views/settings/maps/index.html.erb
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
<% content_for :title, "Background jobs" %>
|
||||
|
||||
<div class="min-h-content w-full my-5">
|
||||
<%= render 'settings/navigation' %>
|
||||
|
||||
<div class="flex justify-between items-center mt-5">
|
||||
<h1 class="font-bold text-4xl">Maps settings</h1>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-5" data-controller="map-preview">
|
||||
<%= form_for :maps,
|
||||
url: settings_maps_path,
|
||||
method: :patch,
|
||||
autocomplete: "off",
|
||||
data: { turbo_method: :patch, turbo: false },
|
||||
class: "lg:col-span-1" do |f| %>
|
||||
<div class="form-control my-2">
|
||||
<%= f.label :name %>
|
||||
<%= f.text_field :name, value: @maps['name'], placeholder: 'Example: OpenStreetMap', class: "input input-bordered" %>
|
||||
</div>
|
||||
|
||||
<div class="form-control my-2">
|
||||
<%= f.label :url, 'URL' %>
|
||||
<%= f.text_field :url,
|
||||
value: @maps['url'],
|
||||
autocomplete: "off",
|
||||
placeholder: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
|
||||
class: "input input-bordered",
|
||||
data: {
|
||||
map_preview_target: "urlInput",
|
||||
action: "input->map-preview#updatePreview"
|
||||
} %>
|
||||
</div>
|
||||
|
||||
<%= f.submit 'Save', class: "btn btn-primary", data: { map_preview_target: "saveButton" } %>
|
||||
<% end %>
|
||||
|
||||
<div class="lg:col-span-2" style="height: 500px;">
|
||||
<div
|
||||
data-map-preview-target="mapContainer"
|
||||
class="w-full h-full rounded-lg border"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -20,6 +20,8 @@ Rails.application.routes.draw do
|
|||
namespace :settings do
|
||||
resources :background_jobs, only: %i[index create destroy]
|
||||
resources :users, only: %i[index create destroy edit update]
|
||||
resources :maps, only: %i[index]
|
||||
patch 'maps', to: 'maps#update'
|
||||
end
|
||||
|
||||
patch 'settings', to: 'settings#update'
|
||||
|
|
|
|||
43
spec/requests/settings/maps_spec.rb
Normal file
43
spec/requests/settings/maps_spec.rb
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'settings/maps', type: :request do
|
||||
before do
|
||||
stub_request(:any, 'https://api.github.com/repos/Freika/dawarich/tags')
|
||||
.to_return(status: 200, body: '[{"name": "1.0.0"}]', headers: {})
|
||||
end
|
||||
|
||||
context 'when user is authenticated' do
|
||||
let!(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
sign_in user
|
||||
end
|
||||
|
||||
describe 'GET /index' do
|
||||
it 'returns a success response' do
|
||||
get settings_maps_url
|
||||
|
||||
expect(response).to be_successful
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PATCH /update' do
|
||||
it 'returns a success response' do
|
||||
patch settings_maps_path, params: { maps: { name: 'Test', url: 'https://test.com' } }
|
||||
|
||||
expect(response).to redirect_to(settings_maps_path)
|
||||
expect(user.settings['maps']).to eq({ 'name' => 'Test', 'url' => 'https://test.com' })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is not authenticated' do
|
||||
it 'redirects to the sign in page' do
|
||||
get settings_maps_path
|
||||
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -70,7 +70,7 @@ RSpec.describe Photos::Thumbnail do
|
|||
let(:source) { 'unsupported' }
|
||||
|
||||
it 'raises an error' do
|
||||
expect { subject }.to raise_error(RuntimeError, 'Unsupported source: unsupported')
|
||||
expect { subject }.to raise_error(ArgumentError, 'Unsupported source: unsupported')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in a new issue