mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-11 09:41:40 -05:00
Merge pull request #675 from Freika/feature/self-hosted-mode
Self-hosted mode
This commit is contained in:
commit
2c39ebcaa9
32 changed files with 617 additions and 356 deletions
13
CHANGELOG.md
13
CHANGELOG.md
|
|
@ -4,6 +4,19 @@ 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.2 - 2025-02-15
|
||||
|
||||
## Fixed
|
||||
|
||||
- Fixed a bug where background jobs to import Immich and Photoprism geolocation data data could not be created by non-admin users.
|
||||
- Fixed a bug where upon point deletion there was an error it was not being removed from the map, while it was actually deleted from the database. #883
|
||||
|
||||
### Changed
|
||||
|
||||
- Restrict access to Sidekiq in non self-hosted mode.
|
||||
- Restrict access to background jobs in non self-hosted mode.
|
||||
- Restrict access to users management in non self-hosted mode.
|
||||
|
||||
# 0.24.1 - 2025-02-13
|
||||
|
||||
## Custom map tiles
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
class ApplicationController < ActionController::Base
|
||||
include Pundit::Authorization
|
||||
|
||||
before_action :unread_notifications
|
||||
before_action :unread_notifications, :set_self_hosted_status
|
||||
|
||||
protected
|
||||
|
||||
|
|
@ -18,4 +18,16 @@ class ApplicationController < ActionController::Base
|
|||
|
||||
redirect_to root_path, notice: 'You are not authorized to perform this action.', status: :see_other
|
||||
end
|
||||
|
||||
def authenticate_self_hosted!
|
||||
return if DawarichSettings.self_hosted?
|
||||
|
||||
redirect_to root_path, notice: 'You are not authorized to perform this action.', status: :see_other
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_self_hosted_status
|
||||
@self_hosted = DawarichSettings.self_hosted?
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Settings::BackgroundJobsController < ApplicationController
|
||||
before_action :authenticate_user!
|
||||
before_action :authenticate_admin!
|
||||
before_action :authenticate_self_hosted!
|
||||
before_action :authenticate_admin!, unless: lambda {
|
||||
%w[start_immich_import start_photoprism_import].include?(params[:job_name])
|
||||
}
|
||||
|
||||
def index
|
||||
@queues = Sidekiq::Queue.all
|
||||
|
|
@ -13,7 +15,15 @@ class Settings::BackgroundJobsController < ApplicationController
|
|||
|
||||
flash.now[:notice] = 'Job was successfully created.'
|
||||
|
||||
redirect_to settings_background_jobs_path, notice: 'Job was successfully created.'
|
||||
redirect_path =
|
||||
case params[:job_name]
|
||||
when 'start_immich_import', 'start_photoprism_import'
|
||||
imports_path
|
||||
else
|
||||
settings_background_jobs_path
|
||||
end
|
||||
|
||||
redirect_to redirect_path, notice: 'Job was successfully created.'
|
||||
end
|
||||
|
||||
def destroy
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Settings::UsersController < ApplicationController
|
||||
before_action :authenticate_user!
|
||||
before_action :authenticate_admin!
|
||||
before_action :authenticate_self_hosted!
|
||||
|
||||
def index
|
||||
@users = User.order(created_at: :desc)
|
||||
|
|
|
|||
23
app/javascript/controllers/base_controller.js
Normal file
23
app/javascript/controllers/base_controller.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { Controller } from "@hotwired/stimulus"
|
||||
|
||||
export default class extends Controller {
|
||||
static values = {
|
||||
selfHosted: Boolean
|
||||
}
|
||||
|
||||
// Every controller that extends BaseController and uses initialize()
|
||||
// should call super.initialize()
|
||||
// Example:
|
||||
// export default class extends BaseController {
|
||||
// initialize() {
|
||||
// super.initialize()
|
||||
// }
|
||||
// }
|
||||
initialize() {
|
||||
// Get the self-hosted value from the HTML root element
|
||||
if (!this.hasSelfHostedValue) {
|
||||
const selfHosted = document.documentElement.dataset.selfHosted === 'true'
|
||||
this.selfHostedValue = selfHosted
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { Controller } from "@hotwired/stimulus"
|
||||
import BaseController from "./base_controller"
|
||||
|
||||
// Connects to data-controller="checkbox-select-all"
|
||||
export default class extends Controller {
|
||||
export default class extends BaseController {
|
||||
static targets = ["parent", "child"]
|
||||
|
||||
connect() {
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
// - trips/new
|
||||
// - trips/edit
|
||||
|
||||
import { Controller } from "@hotwired/stimulus"
|
||||
import BaseController from "./base_controller"
|
||||
|
||||
export default class extends Controller {
|
||||
export default class extends BaseController {
|
||||
static targets = ["startedAt", "endedAt", "apiKey"]
|
||||
static values = { tripsId: String }
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Controller } from "@hotwired/stimulus";
|
||||
import BaseController from "./base_controller";
|
||||
import consumer from "../channels/consumer";
|
||||
|
||||
export default class extends Controller {
|
||||
export default class extends BaseController {
|
||||
static targets = ["index"];
|
||||
|
||||
connect() {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { Controller } from "@hotwired/stimulus"
|
||||
import BaseController from "./base_controller"
|
||||
import L from "leaflet"
|
||||
import { showFlashMessage } from "../maps/helpers"
|
||||
|
||||
export default class extends Controller {
|
||||
export default class extends BaseController {
|
||||
static targets = ["urlInput", "mapContainer", "saveButton"]
|
||||
|
||||
DEFAULT_TILE_URL = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
|
||||
|
|
|
|||
|
|
@ -13,26 +13,16 @@ import {
|
|||
|
||||
import { fetchAndDrawAreas, handleAreaCreated } from "../maps/areas";
|
||||
|
||||
import { showFlashMessage, fetchAndDisplayPhotos, debounce } from "../maps/helpers";
|
||||
|
||||
import {
|
||||
osmMapLayer,
|
||||
osmHotMapLayer,
|
||||
OPNVMapLayer,
|
||||
openTopoMapLayer,
|
||||
cyclOsmMapLayer,
|
||||
esriWorldStreetMapLayer,
|
||||
esriWorldTopoMapLayer,
|
||||
esriWorldImageryMapLayer,
|
||||
esriWorldGrayCanvasMapLayer
|
||||
} from "../maps/layers";
|
||||
import { showFlashMessage, fetchAndDisplayPhotos } from "../maps/helpers";
|
||||
import { countryCodesMap } from "../maps/country_codes";
|
||||
|
||||
import "leaflet-draw";
|
||||
import { initializeFogCanvas, drawFogCanvas, createFogOverlay } from "../maps/fog_of_war";
|
||||
import { TileMonitor } from "../maps/tile_monitor";
|
||||
import BaseController from "./base_controller";
|
||||
import { createAllMapLayers } from "../maps/layers";
|
||||
|
||||
export default class extends Controller {
|
||||
export default class extends BaseController {
|
||||
static targets = ["container"];
|
||||
|
||||
settingsButtonAdded = false;
|
||||
|
|
@ -41,6 +31,7 @@ export default class extends Controller {
|
|||
trackedMonthsCache = null;
|
||||
|
||||
connect() {
|
||||
super.connect();
|
||||
console.log("Map controller connected");
|
||||
|
||||
this.apiKey = this.element.dataset.api_key;
|
||||
|
|
@ -402,17 +393,7 @@ export default class extends Controller {
|
|||
|
||||
baseMaps() {
|
||||
let selectedLayerName = this.userSettings.preferred_map_layer || "OpenStreetMap";
|
||||
let maps = {
|
||||
OpenStreetMap: osmMapLayer(this.map, selectedLayerName),
|
||||
"OpenStreetMap.HOT": osmHotMapLayer(this.map, selectedLayerName),
|
||||
OPNV: OPNVMapLayer(this.map, selectedLayerName),
|
||||
openTopo: openTopoMapLayer(this.map, selectedLayerName),
|
||||
cyclOsm: cyclOsmMapLayer(this.map, selectedLayerName),
|
||||
esriWorldStreet: esriWorldStreetMapLayer(this.map, selectedLayerName),
|
||||
esriWorldTopo: esriWorldTopoMapLayer(this.map, selectedLayerName),
|
||||
esriWorldImagery: esriWorldImageryMapLayer(this.map, selectedLayerName),
|
||||
esriWorldGrayCanvas: esriWorldGrayCanvasMapLayer(this.map, selectedLayerName)
|
||||
};
|
||||
let maps = createAllMapLayers(this.map, selectedLayerName);
|
||||
|
||||
// Add custom map if it exists in settings
|
||||
if (this.userSettings.maps && this.userSettings.maps.url) {
|
||||
|
|
@ -536,13 +517,13 @@ export default class extends Controller {
|
|||
if (this.layerControl) {
|
||||
this.map.removeControl(this.layerControl);
|
||||
const controlsLayer = {
|
||||
Points: this.markersLayer,
|
||||
Routes: this.polylinesLayer,
|
||||
Heatmap: this.heatmapLayer,
|
||||
"Fog of War": this.fogOverlay,
|
||||
"Scratch map": this.scratchLayer,
|
||||
Areas: this.areasLayer,
|
||||
Photos: this.photoMarkers
|
||||
Points: this.markersLayer || L.layerGroup(),
|
||||
Routes: this.polylinesLayer || L.layerGroup(),
|
||||
Heatmap: this.heatmapLayer || L.layerGroup(),
|
||||
"Fog of War": new this.fogOverlay(),
|
||||
"Scratch map": this.scratchLayer || L.layerGroup(),
|
||||
Areas: this.areasLayer || L.layerGroup(),
|
||||
Photos: this.photoMarkers || L.layerGroup()
|
||||
};
|
||||
this.layerControl = L.control.layers(this.baseMaps(), controlsLayer).addTo(this.map);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import { Controller } from "@hotwired/stimulus"
|
||||
import BaseController from "./base_controller"
|
||||
import consumer from "../channels/consumer"
|
||||
|
||||
export default class extends Controller {
|
||||
export default class extends BaseController {
|
||||
static targets = ["badge", "list"]
|
||||
static values = { userId: Number }
|
||||
|
||||
initialize() {
|
||||
super.initialize()
|
||||
this.subscription = null
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Controller } from "@hotwired/stimulus"
|
||||
import BaseController from "./base_controller"
|
||||
|
||||
export default class extends Controller {
|
||||
export default class extends BaseController {
|
||||
static values = {
|
||||
timeout: Number
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
// This controller is being used on:
|
||||
// - trips/index
|
||||
|
||||
import { Controller } from "@hotwired/stimulus"
|
||||
import BaseController from "./base_controller"
|
||||
import L from "leaflet"
|
||||
|
||||
export default class extends Controller {
|
||||
export default class extends BaseController {
|
||||
static values = {
|
||||
tripId: Number,
|
||||
path: String,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
// - trips/edit
|
||||
// - trips/new
|
||||
|
||||
import { Controller } from "@hotwired/stimulus"
|
||||
import BaseController from "./base_controller"
|
||||
import L from "leaflet"
|
||||
import {
|
||||
osmMapLayer,
|
||||
|
|
@ -22,7 +22,7 @@ import {
|
|||
showFlashMessage
|
||||
} from '../maps/helpers';
|
||||
|
||||
export default class extends Controller {
|
||||
export default class extends BaseController {
|
||||
static targets = ["container", "startedAt", "endedAt"]
|
||||
static values = { }
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { Controller } from "@hotwired/stimulus"
|
||||
import L, { latLng } from "leaflet";
|
||||
import { osmMapLayer } from "../maps/layers";
|
||||
import BaseController from "./base_controller"
|
||||
import L from "leaflet"
|
||||
import { osmMapLayer } from "../maps/layers"
|
||||
|
||||
// This controller is used to display a map of all coordinates for a visit
|
||||
// on the "Map" modal of a visit on the Visits page
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ["container"];
|
||||
export default class extends BaseController {
|
||||
static targets = ["container"]
|
||||
|
||||
connect() {
|
||||
this.coordinates = JSON.parse(this.element.dataset.coordinates);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
import { Controller } from "@hotwired/stimulus";
|
||||
import BaseController from "./base_controller"
|
||||
|
||||
export default class extends Controller {
|
||||
export default class extends BaseController {
|
||||
static targets = ["name", "input"]
|
||||
|
||||
connect() {
|
||||
this.visitId = this.element.dataset.id;
|
||||
this.apiKey = this.element.dataset.api_key;
|
||||
this.visitId = this.element.dataset.id;
|
||||
|
||||
this.element.addEventListener("visit-name:updated", this.updateAll.bind(this));
|
||||
}
|
||||
|
||||
// Action to handle selection change
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Controller } from "@hotwired/stimulus";
|
||||
import BaseController from "./base_controller"
|
||||
|
||||
// This controller is used to handle the updating of visit names on the Visits page
|
||||
export default class extends Controller {
|
||||
export default class extends BaseController {
|
||||
static targets = ["name", "input"];
|
||||
|
||||
connect() {
|
||||
|
|
|
|||
|
|
@ -87,10 +87,19 @@ export function haversineDistance(lat1, lon1, lat2, lon2, unit = 'km') {
|
|||
}
|
||||
|
||||
export function showFlashMessage(type, message) {
|
||||
// Create the outer flash container div
|
||||
// Get or create the flash container
|
||||
let flashContainer = document.getElementById('flash-messages');
|
||||
if (!flashContainer) {
|
||||
flashContainer = document.createElement('div');
|
||||
flashContainer.id = 'flash-messages';
|
||||
flashContainer.className = 'fixed top-5 right-5 flex flex-col-reverse gap-2 z-40';
|
||||
document.body.appendChild(flashContainer);
|
||||
}
|
||||
|
||||
// Create the flash message div
|
||||
const flashDiv = document.createElement('div');
|
||||
flashDiv.setAttribute('data-controller', 'removals');
|
||||
flashDiv.className = `flex items-center fixed top-5 right-5 ${classesForFlash(type)} py-3 px-5 rounded-lg`;
|
||||
flashDiv.className = `flex items-center justify-between ${classesForFlash(type)} py-3 px-5 rounded-lg z-40`;
|
||||
|
||||
// Create the message div
|
||||
const messageDiv = document.createElement('div');
|
||||
|
|
@ -101,6 +110,7 @@ export function showFlashMessage(type, message) {
|
|||
const closeButton = document.createElement('button');
|
||||
closeButton.setAttribute('type', 'button');
|
||||
closeButton.setAttribute('data-action', 'click->removals#remove');
|
||||
closeButton.className = 'ml-auto'; // Ensures button stays on the right
|
||||
|
||||
// Create the SVG icon for the close button
|
||||
const closeIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
|
|
@ -116,21 +126,22 @@ export function showFlashMessage(type, message) {
|
|||
closeIconPath.setAttribute('stroke-width', '2');
|
||||
closeIconPath.setAttribute('d', 'M6 18L18 6M6 6l12 12');
|
||||
|
||||
// Append the path to the SVG
|
||||
// Append all elements
|
||||
closeIcon.appendChild(closeIconPath);
|
||||
// Append the SVG to the close button
|
||||
closeButton.appendChild(closeIcon);
|
||||
|
||||
// Append the message and close button to the flash div
|
||||
flashDiv.appendChild(messageDiv);
|
||||
flashDiv.appendChild(closeButton);
|
||||
flashContainer.appendChild(flashDiv);
|
||||
|
||||
// Append the flash message to the body or a specific flash container
|
||||
document.body.appendChild(flashDiv);
|
||||
|
||||
// Optional: Automatically remove the flash message after 5 seconds
|
||||
// Automatically remove after 5 seconds
|
||||
setTimeout(() => {
|
||||
if (flashDiv && flashDiv.parentNode) {
|
||||
flashDiv.remove();
|
||||
// Remove container if empty
|
||||
if (flashContainer && !flashContainer.hasChildNodes()) {
|
||||
flashContainer.remove();
|
||||
}
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,38 @@
|
|||
// Yeah I know it should be DRY but this is me doing a KISS at 21:00 on a Sunday night
|
||||
// Import the maps configuration
|
||||
// In non-self-hosted mode, we need to mount external maps_config.js to the container
|
||||
import { mapsConfig } from './maps_config';
|
||||
|
||||
export function createMapLayer(map, selectedLayerName, layerKey) {
|
||||
const config = mapsConfig[layerKey];
|
||||
|
||||
if (!config) {
|
||||
console.warn(`No configuration found for layer: ${layerKey}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
let layer = L.tileLayer(config.url, {
|
||||
maxZoom: config.maxZoom,
|
||||
attribution: config.attribution,
|
||||
// Add any other config properties that might be needed
|
||||
});
|
||||
|
||||
if (selectedLayerName === layerKey) {
|
||||
return layer.addTo(map);
|
||||
} else {
|
||||
return layer;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to create all map layers
|
||||
export function createAllMapLayers(map, selectedLayerName) {
|
||||
const layers = {};
|
||||
|
||||
Object.keys(mapsConfig).forEach(layerKey => {
|
||||
layers[layerKey] = createMapLayer(map, selectedLayerName, layerKey);
|
||||
});
|
||||
|
||||
return layers;
|
||||
}
|
||||
|
||||
export function osmMapLayer(map, selectedLayerName) {
|
||||
let layerName = 'OpenStreetMap';
|
||||
|
|
@ -57,166 +91,6 @@ export function openTopoMapLayer(map, selectedLayerName) {
|
|||
}
|
||||
}
|
||||
|
||||
// export function stadiaAlidadeSmoothMapLayer(map, selectedLayerName) {
|
||||
// let layerName = 'stadiaAlidadeSmooth';
|
||||
// let layer = L.tileLayer('https://tiles.stadiamaps.com/tiles/alidade_smooth/{z}/{x}/{y}{r}.{ext}', {
|
||||
// minZoom: 0,
|
||||
// maxZoom: 20,
|
||||
// attribution: '© <a href="https://www.stadiamaps.com/" target="_blank">Stadia Maps</a> © <a href="https://openmaptiles.org/" target="_blank">OpenMapTiles</a> © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
||||
// ext: 'png'
|
||||
// });
|
||||
|
||||
// if (selectedLayerName === layerName) {
|
||||
// return layer.addTo(map);
|
||||
// } else {
|
||||
// return layer;
|
||||
// }
|
||||
// }
|
||||
|
||||
// export function stadiaAlidadeSmoothDarkMapLayer(map, selectedLayerName) {
|
||||
// let layerName = 'stadiaAlidadeSmoothDark';
|
||||
// let layer = L.tileLayer('https://tiles.stadiamaps.com/tiles/alidade_smooth_dark/{z}/{x}/{y}{r}.{ext}', {
|
||||
// minZoom: 0,
|
||||
// maxZoom: 20,
|
||||
// attribution: '© <a href="https://www.stadiamaps.com/" target="_blank">Stadia Maps</a> © <a href="https://openmaptiles.org/" target="_blank">OpenMapTiles</a> © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
||||
// ext: 'png'
|
||||
// });
|
||||
|
||||
// if (selectedLayerName === layerName) {
|
||||
// return layer.addTo(map);
|
||||
// } else {
|
||||
// return layer;
|
||||
// }
|
||||
// }
|
||||
|
||||
// export function stadiaAlidadeSatelliteMapLayer(map, selectedLayerName) {
|
||||
// let layerName = 'stadiaAlidadeSatellite';
|
||||
// let layer = L.tileLayer('https://tiles.stadiamaps.com/tiles/alidade_satellite/{z}/{x}/{y}{r}.{ext}', {
|
||||
// minZoom: 0,
|
||||
// maxZoom: 20,
|
||||
// attribution: '© CNES, Distribution Airbus DS, © Airbus DS, © PlanetObserver (Contains Copernicus Data) | © <a href="https://www.stadiamaps.com/" target="_blank">Stadia Maps</a> © <a href="https://openmaptiles.org/" target="_blank">OpenMapTiles</a> © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
||||
// ext: 'jpg'
|
||||
// });
|
||||
|
||||
// if (selectedLayerName === layerName) {
|
||||
// return layer.addTo(map);
|
||||
// } else {
|
||||
// return layer;
|
||||
// }
|
||||
// }
|
||||
|
||||
// export function stadiaOsmBrightMapLayer(map, selectedLayerName) {
|
||||
// let layerName = 'stadiaOsmBright';
|
||||
// let layer = L.tileLayer('https://tiles.stadiamaps.com/tiles/osm_bright/{z}/{x}/{y}{r}.{ext}', {
|
||||
// minZoom: 0,
|
||||
// maxZoom: 20,
|
||||
// attribution: '© <a href="https://www.stadiamaps.com/" target="_blank">Stadia Maps</a> © <a href="https://openmaptiles.org/" target="_blank">OpenMapTiles</a> © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
||||
// ext: 'png'
|
||||
// });
|
||||
|
||||
// if (selectedLayerName === layerName) {
|
||||
// return layer.addTo(map);
|
||||
// } else {
|
||||
// return layer;
|
||||
// }
|
||||
// }
|
||||
|
||||
// export function stadiaOutdoorMapLayer(map, selectedLayerName) {
|
||||
// let layerName = 'stadiaOutdoor';
|
||||
// let layer = L.tileLayer('https://tiles.stadiamaps.com/tiles/outdoors/{z}/{x}/{y}{r}.{ext}', {
|
||||
// minZoom: 0,
|
||||
// maxZoom: 20,
|
||||
// attribution: '© <a href="https://www.stadiamaps.com/" target="_blank">Stadia Maps</a> © <a href="https://openmaptiles.org/" target="_blank">OpenMapTiles</a> © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
||||
// ext: 'png'
|
||||
// });
|
||||
|
||||
// if (selectedLayerName === layerName) {
|
||||
// return layer.addTo(map);
|
||||
// } else {
|
||||
// return layer;
|
||||
// }
|
||||
// }
|
||||
|
||||
// export function stadiaStamenTonerMapLayer(map, selectedLayerName) {
|
||||
// let layerName = 'stadiaStamenToner';
|
||||
// let layer = L.tileLayer('https://tiles.stadiamaps.com/tiles/stamen_toner/{z}/{x}/{y}{r}.{ext}', {
|
||||
// minZoom: 0,
|
||||
// maxZoom: 20,
|
||||
// attribution: '© <a href="https://www.stadiamaps.com/" target="_blank">Stadia Maps</a> © <a href="https://www.stamen.com/" target="_blank">Stamen Design</a> © <a href="https://openmaptiles.org/" target="_blank">OpenMapTiles</a> © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
||||
// ext: 'png'
|
||||
// });
|
||||
|
||||
// if (selectedLayerName === layerName) {
|
||||
// return layer.addTo(map);
|
||||
// } else {
|
||||
// return layer;
|
||||
// }
|
||||
// }
|
||||
|
||||
// export function stadiaStamenTonerBackgroundMapLayer(map, selectedLayerName) {
|
||||
// let layerName = 'stadiaStamenTonerBackground';
|
||||
// let layer = L.tileLayer('https://tiles.stadiamaps.com/tiles/stamen_toner_background/{z}/{x}/{y}{r}.{ext}', {
|
||||
// minZoom: 0,
|
||||
// maxZoom: 20,
|
||||
// attribution: '© <a href="https://www.stadiamaps.com/" target="_blank">Stadia Maps</a> © <a href="https://www.stamen.com/" target="_blank">Stamen Design</a> © <a href="https://openmaptiles.org/" target="_blank">OpenMapTiles</a> © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
||||
// ext: 'png'
|
||||
// });
|
||||
|
||||
// if (selectedLayerName === layerName) {
|
||||
// return layer.addTo(map);
|
||||
// } else {
|
||||
// return layer;
|
||||
// }
|
||||
// }
|
||||
|
||||
// export function stadiaStamenTonerLiteMapLayer(map, selectedLayerName) {
|
||||
// let layerName = 'stadiaStamenTonerLite';
|
||||
// let layer = L.tileLayer('https://tiles.stadiamaps.com/tiles/stamen_toner_lite/{z}/{x}/{y}{r}.{ext}', {
|
||||
// minZoom: 0,
|
||||
// maxZoom: 20,
|
||||
// attribution: '© <a href="https://www.stadiamaps.com/" target="_blank">Stadia Maps</a> © <a href="https://www.stamen.com/" target="_blank">Stamen Design</a> © <a href="https://openmaptiles.org/" target="_blank">OpenMapTiles</a> © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
||||
// ext: 'png'
|
||||
// });
|
||||
|
||||
// if (selectedLayerName === layerName) {
|
||||
// return layer.addTo(map);
|
||||
// } else {
|
||||
// return layer;
|
||||
// }
|
||||
// }
|
||||
|
||||
// export function stadiaStamenWatercolorMapLayer(map, selectedLayerName) {
|
||||
// let layerName = 'stadiaStamenWatercolor';
|
||||
// let layer = L.tileLayer('https://tiles.stadiamaps.com/tiles/stamen_watercolor/{z}/{x}/{y}.{ext}', {
|
||||
// minZoom: 1,
|
||||
// maxZoom: 16,
|
||||
// attribution: '© <a href="https://www.stadiamaps.com/" target="_blank">Stadia Maps</a> © <a href="https://www.stamen.com/" target="_blank">Stamen Design</a> © <a href="https://openmaptiles.org/" target="_blank">OpenMapTiles</a> © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
||||
// ext: 'jpg'
|
||||
// });
|
||||
|
||||
// if (selectedLayerName === layerName) {
|
||||
// return layer.addTo(map);
|
||||
// } else {
|
||||
// return layer;
|
||||
// }
|
||||
// }
|
||||
|
||||
// export function stadiaStamenTerrainMapLayer(map, selectedLayerName) {
|
||||
// let layerName = 'stadiaStamenTerrain';
|
||||
// let layer = L.tileLayer('https://tiles.stadiamaps.com/tiles/stamen_terrain/{z}/{x}/{y}{r}.{ext}', {
|
||||
// minZoom: 0,
|
||||
// maxZoom: 18,
|
||||
// attribution: '© <a href="https://www.stadiamaps.com/" target="_blank">Stadia Maps</a> © <a href="https://www.stamen.com/" target="_blank">Stamen Design</a> © <a href="https://openmaptiles.org/" target="_blank">OpenMapTiles</a> © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
||||
// ext: 'png'
|
||||
// });
|
||||
|
||||
// if (selectedLayerName === layerName) {
|
||||
// return layer.addTo(map);
|
||||
// } else {
|
||||
// return layer;
|
||||
// }
|
||||
// }
|
||||
|
||||
export function cyclOsmMapLayer(map, selectedLayerName) {
|
||||
let layerName = 'cyclOsm';
|
||||
let layer = L.tileLayer('https://{s}.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png', {
|
||||
|
|
|
|||
44
app/javascript/maps/maps_config.js
Normal file
44
app/javascript/maps/maps_config.js
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
export const mapsConfig = {
|
||||
"OpenStreetMap": {
|
||||
url: "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
|
||||
maxZoom: 19,
|
||||
attribution: "© <a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a>"
|
||||
},
|
||||
"OpenStreetMap.HOT": {
|
||||
url: "https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png",
|
||||
maxZoom: 19,
|
||||
attribution: "© OpenStreetMap contributors, Tiles style by Humanitarian OpenStreetMap Team hosted by OpenStreetMap France"
|
||||
},
|
||||
"OPNV": {
|
||||
url: "https://tileserver.memomaps.de/tilegen/{z}/{x}/{y}.png",
|
||||
maxZoom: 18,
|
||||
attribution: "Map <a href='https://memomaps.de/'>memomaps.de</a> <a href='http://creativecommons.org/licenses/by-sa/2.0/'>CC-BY-SA</a>, map data © <a href='https://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors"
|
||||
},
|
||||
"openTopo": {
|
||||
url: "https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png",
|
||||
maxZoom: 17,
|
||||
attribution: "Map data: © <a href='https://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors, <a href='http://viewfinderpanoramas.org'>SRTM</a> | Map style: © <a href='https://opentopomap.org'>OpenTopoMap</a> (<a href='https://creativecommons.org/licenses/by-sa/3.0/'>CC-BY-SA</a>)"
|
||||
},
|
||||
"cyclOsm": {
|
||||
url: "https://{s}.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png",
|
||||
maxZoom: 20,
|
||||
attribution: "<a href='https://github.com/cyclosm/cyclosm-cartocss-style/releases' title='CyclOSM - Open Bicycle render'>CyclOSM</a> | Map data: © <a href='https://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors"
|
||||
},
|
||||
"esriWorldStreet": {
|
||||
url: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}",
|
||||
attribution: "Tiles © Esri — Source: Esri, DeLorme, NAVTEQ, USGS, Intermap, iPC, NRCAN, Esri Japan, METI, Esri China (Hong Kong), Esri (Thailand), TomTom, 2012"
|
||||
},
|
||||
"esriWorldTopo": {
|
||||
url: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}",
|
||||
attribution: "Tiles © Esri — Esri, DeLorme, NAVTEQ, TomTom, Intermap, iPC, USGS, FAO, NPS, NRCAN, GeoBase, Kadaster NL, Ordnance Survey, Esri Japan, METI, Esri China (Hong Kong), and the GIS User Community"
|
||||
},
|
||||
"esriWorldImagery": {
|
||||
url: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
|
||||
attribution: "Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community"
|
||||
},
|
||||
"esriWorldGrayCanvas": {
|
||||
url: "https://server.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer/tile/{z}/{y}/{x}",
|
||||
attribution: "Tiles © Esri — Esri, DeLorme, NAVTEQ",
|
||||
maxZoom: 16
|
||||
}
|
||||
};
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
<!DOCTYPE html>
|
||||
<html data-theme="<%= app_theme %>">
|
||||
<html data-theme="<%= app_theme %>" data-self-hosted="<%= @self_hosted %>">
|
||||
<head>
|
||||
<title><%= full_title(yield(:title)) %></title>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<div role="tablist" class="tabs tabs-lifted tabs-lg">
|
||||
<%= link_to 'Integrations', settings_path, role: 'tab', class: "tab #{active_tab?(settings_path)}" %>
|
||||
<% if current_user.admin? %>
|
||||
<% if DawarichSettings.self_hosted? && current_user.admin? %>
|
||||
<%= 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 %>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<% content_for :title, "Background jobs" %>
|
||||
<% content_for :title, "Map settings" %>
|
||||
|
||||
<div class="min-h-content w-full my-5">
|
||||
<%= render 'settings/navigation' %>
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div role="alert" class="alert mt-5">
|
||||
<div role="alert" class="alert my-5">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
SELF_HOSTED = ENV.fetch('SELF_HOSTED', 'true') == 'true'
|
||||
|
||||
MIN_MINUTES_SPENT_IN_CITY = ENV.fetch('MIN_MINUTES_SPENT_IN_CITY', 60).to_i
|
||||
DISTANCE_UNIT = ENV.fetch('DISTANCE_UNIT', 'km').to_sym
|
||||
|
||||
|
|
@ -11,7 +13,7 @@ TELEMETRY_URL = 'https://influxdb2.frey.today/api/v2/write'
|
|||
# Reverse geocoding settings
|
||||
PHOTON_API_HOST = ENV.fetch('PHOTON_API_HOST', nil)
|
||||
PHOTON_API_KEY = ENV.fetch('PHOTON_API_KEY', nil)
|
||||
PHOTON_API_USE_HTTPS = ENV.fetch('PHOTON_API_USE_HTTPS', 'true') == 'true'
|
||||
PHOTON_API_USE_HTTPS = ENV.fetch('PHOTON_API_USE_HTTPS', 'false') == 'true'
|
||||
|
||||
NOMINATIM_API_HOST = ENV.fetch('NOMINATIM_API_HOST', nil)
|
||||
NOMINATIM_API_KEY = ENV.fetch('NOMINATIM_API_KEY', nil)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,10 @@ class DawarichSettings
|
|||
@geoapify_enabled ||= GEOAPIFY_API_KEY.present?
|
||||
end
|
||||
|
||||
def self_hosted?
|
||||
@self_hosted ||= SELF_HOSTED
|
||||
end
|
||||
|
||||
def prometheus_exporter_enabled?
|
||||
@prometheus_exporter_enabled ||=
|
||||
ENV['PROMETHEUS_EXPORTER_ENABLED'].to_s == 'true' &&
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ Rails.application.routes.draw do
|
|||
mount ActionCable.server => '/cable'
|
||||
mount Rswag::Api::Engine => '/api-docs'
|
||||
mount Rswag::Ui::Engine => '/api-docs'
|
||||
authenticate :user, ->(u) { u.admin? } do
|
||||
authenticate :user, ->(u) { u.admin? && DawarichSettings.self_hosted? } do
|
||||
mount Sidekiq::Web => '/sidekiq'
|
||||
end
|
||||
|
||||
|
|
@ -53,11 +53,16 @@ Rails.application.routes.draw do
|
|||
constraints: { year: /\d{4}/, month: /\d{1,2}|all/ }
|
||||
|
||||
root to: 'home#index'
|
||||
|
||||
if SELF_HOSTED
|
||||
devise_for :users, skip: [:registrations]
|
||||
as :user do
|
||||
get 'users/edit' => 'devise/registrations#edit', :as => 'edit_user_registration'
|
||||
put 'users' => 'devise/registrations#update', :as => 'user_registration'
|
||||
end
|
||||
else
|
||||
devise_for :users
|
||||
end
|
||||
|
||||
get 'map', to: 'map#index'
|
||||
|
||||
|
|
|
|||
|
|
@ -8,16 +8,24 @@ RSpec.describe '/settings/background_jobs', type: :request do
|
|||
.to_return(status: 200, body: '[{"name": "1.0.0"}]', headers: {})
|
||||
end
|
||||
|
||||
context 'when Dawarich is in self-hosted mode' do
|
||||
before do
|
||||
allow(DawarichSettings).to receive(:self_hosted?).and_return(true)
|
||||
end
|
||||
|
||||
context 'when user is not authenticated' do
|
||||
it 'redirects to sign in page' do
|
||||
get settings_background_jobs_url
|
||||
|
||||
expect(response).to redirect_to(new_user_session_url)
|
||||
expect(response).to redirect_to(root_url)
|
||||
expect(flash[:notice]).to eq('You are not authorized to perform this action.')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is authenticated' do
|
||||
before { sign_in create(:user) }
|
||||
let(:user) { create(:user, admin: false) }
|
||||
|
||||
before { sign_in user }
|
||||
|
||||
context 'when user is not an admin' do
|
||||
it 'redirects to root page' do
|
||||
|
|
@ -26,6 +34,32 @@ RSpec.describe '/settings/background_jobs', type: :request do
|
|||
expect(response).to redirect_to(root_url)
|
||||
expect(flash[:notice]).to eq('You are not authorized to perform this action.')
|
||||
end
|
||||
|
||||
context 'when job name is start_immich_import' do
|
||||
it 'redirects to imports page' do
|
||||
post settings_background_jobs_url, params: { job_name: 'start_immich_import' }
|
||||
|
||||
expect(response).to redirect_to(imports_url)
|
||||
end
|
||||
|
||||
it 'enqueues a new job' do
|
||||
expect do
|
||||
post settings_background_jobs_url, params: { job_name: 'start_immich_import' }
|
||||
end.to have_enqueued_job(EnqueueBackgroundJob)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when job name is start_photoprism_import' do
|
||||
it 'redirects to imports page' do
|
||||
get settings_background_jobs_url, params: { job_name: 'start_photoprism_import' }
|
||||
end
|
||||
|
||||
it 'enqueues a new job' do
|
||||
expect do
|
||||
post settings_background_jobs_url, params: { job_name: 'start_photoprism_import' }
|
||||
end.to have_enqueued_job(EnqueueBackgroundJob)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is an admin' do
|
||||
|
|
@ -75,4 +109,104 @@ RSpec.describe '/settings/background_jobs', type: :request do
|
|||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when Dawarich is not in self-hosted mode' do
|
||||
before do
|
||||
allow(DawarichSettings).to receive(:self_hosted?).and_return(false)
|
||||
end
|
||||
|
||||
context 'when user is not authenticated' do
|
||||
it 'redirects to sign in page' do
|
||||
get settings_background_jobs_url
|
||||
|
||||
expect(response).to redirect_to(root_url)
|
||||
expect(flash[:notice]).to eq('You are not authorized to perform this action.')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is authenticated' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
before { sign_in user }
|
||||
|
||||
describe 'GET /index' do
|
||||
it 'redirects to root page' do
|
||||
get settings_background_jobs_url
|
||||
|
||||
expect(response).to redirect_to(root_url)
|
||||
expect(flash[:notice]).to eq('You are not authorized to perform this action.')
|
||||
end
|
||||
|
||||
context 'when user is an admin' do
|
||||
before { sign_in create(:user, :admin) }
|
||||
|
||||
it 'redirects to root page' do
|
||||
get settings_background_jobs_url
|
||||
|
||||
expect(response).to redirect_to(root_url)
|
||||
expect(flash[:notice]).to eq('You are not authorized to perform this action.')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /create' do
|
||||
it 'redirects to root page' do
|
||||
post settings_background_jobs_url, params: { job_name: 'start_reverse_geocoding' }
|
||||
|
||||
expect(response).to redirect_to(root_url)
|
||||
expect(flash[:notice]).to eq('You are not authorized to perform this action.')
|
||||
end
|
||||
|
||||
context 'when job name is start_immich_import' do
|
||||
it 'redirects to imports page' do
|
||||
post settings_background_jobs_url, params: { job_name: 'start_immich_import' }
|
||||
|
||||
expect(response).to redirect_to(root_url)
|
||||
expect(flash[:notice]).to eq('You are not authorized to perform this action.')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when job name is start_photoprism_import' do
|
||||
it 'redirects to imports page' do
|
||||
post settings_background_jobs_url, params: { job_name: 'start_photoprism_import' }
|
||||
|
||||
expect(response).to redirect_to(root_url)
|
||||
expect(flash[:notice]).to eq('You are not authorized to perform this action.')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is an admin' do
|
||||
before { sign_in create(:user, :admin) }
|
||||
|
||||
it 'redirects to root page' do
|
||||
get settings_background_jobs_url
|
||||
|
||||
expect(response).to redirect_to(root_url)
|
||||
expect(flash[:notice]).to eq('You are not authorized to perform this action.')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'DELETE /destroy' do
|
||||
it 'redirects to root page' do
|
||||
delete settings_background_job_url('queue_name')
|
||||
|
||||
expect(response).to redirect_to(root_url)
|
||||
expect(flash[:notice]).to eq('You are not authorized to perform this action.')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is an admin' do
|
||||
before { sign_in create(:user, :admin) }
|
||||
|
||||
it 'redirects to root page' do
|
||||
get settings_background_jobs_url
|
||||
|
||||
expect(response).to redirect_to(root_url)
|
||||
expect(flash[:notice]).to eq('You are not authorized to perform this action.')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,11 +6,16 @@ RSpec.describe '/settings/users', type: :request do
|
|||
let(:valid_attributes) { { email: 'user@domain.com', password: '4815162342' } }
|
||||
let!(:admin) { create(:user, :admin) }
|
||||
|
||||
context 'when Dawarich is in self-hosted mode' do
|
||||
before do
|
||||
allow(DawarichSettings).to receive(:self_hosted?).and_return(true)
|
||||
end
|
||||
|
||||
context 'when user is not authenticated' do
|
||||
it 'redirects to sign in page' do
|
||||
post settings_users_url, params: { user: valid_attributes }
|
||||
|
||||
expect(response).to redirect_to(new_user_session_url)
|
||||
expect(response).to redirect_to(root_url)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -83,4 +88,41 @@ RSpec.describe '/settings/users', type: :request do
|
|||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when Dawarich is not in self-hosted mode' do
|
||||
before do
|
||||
allow(DawarichSettings).to receive(:self_hosted?).and_return(false)
|
||||
sign_in admin
|
||||
end
|
||||
|
||||
describe 'GET /index' do
|
||||
it 'redirects to root page' do
|
||||
get settings_users_url
|
||||
|
||||
expect(response).to redirect_to(root_url)
|
||||
expect(flash[:notice]).to eq('You are not authorized to perform this action.')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /create' do
|
||||
it 'redirects to root page' do
|
||||
post settings_users_url, params: { user: valid_attributes }
|
||||
|
||||
expect(response).to redirect_to(root_url)
|
||||
expect(flash[:notice]).to eq('You are not authorized to perform this action.')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PATCH /update' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
it 'redirects to root page' do
|
||||
patch settings_user_url(user), params: { user: valid_attributes }
|
||||
|
||||
expect(response).to redirect_to(root_url)
|
||||
expect(flash[:notice]).to eq('You are not authorized to perform this action.')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ RSpec.describe 'Settings', type: :request do
|
|||
end
|
||||
|
||||
it 'generates an API key for the user' do
|
||||
expect { post '/settings/generate_api_key' }.to change { user.reload.api_key }
|
||||
expect { post '/settings/generate_api_key' }.to(change { user.reload.api_key })
|
||||
end
|
||||
|
||||
it 'redirects back' do
|
||||
|
|
@ -83,4 +83,39 @@ RSpec.describe 'Settings', type: :request do
|
|||
expect(user.reload.settings).to eq(params[:settings])
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /settings/users' do
|
||||
let!(:user) { create(:user, admin: true) }
|
||||
|
||||
before do
|
||||
stub_request(:any, 'https://api.github.com/repos/Freika/dawarich/tags')
|
||||
.to_return(status: 200, body: '[{"name": "1.0.0"}]', headers: {})
|
||||
|
||||
sign_in user
|
||||
end
|
||||
|
||||
context 'when self-hosted' do
|
||||
before do
|
||||
allow(DawarichSettings).to receive(:self_hosted?).and_return(true)
|
||||
end
|
||||
|
||||
it 'returns http success' do
|
||||
get '/settings/users'
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when not self-hosted' do
|
||||
before do
|
||||
allow(DawarichSettings).to receive(:self_hosted?).and_return(false)
|
||||
end
|
||||
|
||||
it 'redirects to root path' do
|
||||
get '/settings/users'
|
||||
|
||||
expect(response).to redirect_to(root_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,6 +3,11 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe '/sidekiq', type: :request do
|
||||
context 'when Dawarich is in self-hosted mode' do
|
||||
before do
|
||||
allow(DawarichSettings).to receive(:self_hosted?).and_return(true)
|
||||
end
|
||||
|
||||
context 'when user is not authenticated' do
|
||||
it 'redirects to sign in page' do
|
||||
get sidekiq_url
|
||||
|
|
@ -38,4 +43,31 @@ RSpec.describe '/sidekiq', type: :request do
|
|||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when Dawarich is not in self-hosted mode' do
|
||||
before do
|
||||
allow(DawarichSettings).to receive(:self_hosted?).and_return(false)
|
||||
Rails.application.reload_routes!
|
||||
end
|
||||
|
||||
context 'when user is not authenticated' do
|
||||
it 'redirects to sign in page' do
|
||||
get sidekiq_url
|
||||
|
||||
expect(response).to redirect_to('/users/sign_in')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is authenticated' do
|
||||
before { sign_in create(:user, :admin) }
|
||||
|
||||
it 'redirects to root page' do
|
||||
get sidekiq_url
|
||||
|
||||
expect(response).to redirect_to(root_url)
|
||||
expect(flash[:error]).to eq('You are not authorized to perform this action.')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
35
spec/requests/users_spec.rb
Normal file
35
spec/requests/users_spec.rb
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Users', 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
|
||||
|
||||
describe 'GET /users/sign_up' do
|
||||
context 'when self-hosted' do
|
||||
before do
|
||||
stub_const('SELF_HOSTED', true)
|
||||
end
|
||||
|
||||
it 'returns http success' do
|
||||
get '/users/sign_up'
|
||||
expect(response).to have_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when not self-hosted' do
|
||||
before do
|
||||
stub_const('SELF_HOSTED', false)
|
||||
Rails.application.reload_routes!
|
||||
end
|
||||
|
||||
it 'returns http success' do
|
||||
get '/users/sign_up'
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Reference in a new issue