diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index ed1d867..83a98c3 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -9,6 +9,7 @@ class ApplicationController < ActionController::Base helper_method :current_user_disguised? helper_method :current_tab + before_action :redirect_to_setup_if_needed before_action :authenticate_user! class AccessForbidden < StandardError; end @@ -43,6 +44,16 @@ class ApplicationController < ActionController::Base private + # Redirect to the web setup wizard when the application has not yet been + # initialised (i.e. no admin account exists in the database). + def redirect_to_setup_if_needed + return if User.exists?(status: :admin) + redirect_to new_setup_path + rescue ActiveRecord::StatementInvalid + # Tables may not exist yet (migrations not run). Fall through and let the + # normal request handling surface a meaningful error. + end + def render_no_content(record) helpers.render_errors(record) render html: nil, layout: true diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 7f1154d..4030650 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -10,10 +10,11 @@ class RegistrationsController < Devise::RegistrationsController def build_resource(hash = {}) super - # Skip the email confirmation step when the admin has opted out of it via - # config.skip_email_confirmation in application.rb. The account becomes - # active immediately so the user can sign in right after registering. - resource.skip_confirmation! if Rails.application.config.skip_email_confirmation + # Skip the email confirmation step when the admin has enabled this option + # via the web setup wizard (stored as the "skip_email_confirmation" Setting). + # The account becomes active immediately so the user can sign in right after + # registering. + resource.skip_confirmation! if Setting.get("skip_email_confirmation") == "true" end def update_resource(resource, params) diff --git a/app/controllers/setup_controller.rb b/app/controllers/setup_controller.rb new file mode 100644 index 0000000..c84bc4f --- /dev/null +++ b/app/controllers/setup_controller.rb @@ -0,0 +1,59 @@ +# Handles the one-time web-based installation wizard. +# +# The wizard is only accessible when no admin account exists yet. Once an +# admin has been created the controller redirects every request to the root +# path, so it can never be used to overwrite an existing installation. +class SetupController < ActionController::Base + # Use the full application layout (header, flash, etc.) so the page looks + # consistent with the rest of the site. + layout "application" + + before_action :redirect_if_installed + + def new + end + + def create + email = params[:admin_email].to_s.strip + password = params[:admin_password].to_s + confirm = params[:admin_password_confirmation].to_s + + errors = [] + errors << t(".email_blank") if email.blank? + errors << t(".password_blank") if password.blank? + errors << t(".password_mismatch") if password != confirm + + if errors.any? + flash.now[:alert] = errors.join(" ") + return render :new, status: :unprocessable_entity + end + + user = User.new(email: email, password: password, status: :admin) + user.skip_confirmation! + + unless user.save + flash.now[:alert] = user.errors.full_messages.join(" ") + return render :new, status: :unprocessable_entity + end + + # Persist runtime settings chosen during setup. + Setting.set("skip_email_confirmation", + params[:skip_email_confirmation] == "1") + + # Optionally seed the built-in default units. + if params[:seed_units] == "1" + load Rails.root.join("db/seeds/units.rb") + end + + redirect_to new_user_session_path, notice: t(".success") + end + + private + + def redirect_if_installed + redirect_to root_path if User.exists?(status: :admin) + rescue ActiveRecord::StatementInvalid + # Tables are not yet migrated — stay on the setup page so the user sees a + # meaningful error rather than a crash. + end +end diff --git a/app/models/quantity.rb b/app/models/quantity.rb index ed81124..27969ea 100644 --- a/app/models/quantity.rb +++ b/app/models/quantity.rb @@ -15,8 +15,8 @@ class Quantity < ApplicationRecord errors.add(:parent, :descendant_reference) if ancestor_of?(parent) end validates :name, presence: true, uniqueness: {scope: [:user_id, :parent_id]}, - length: {maximum: type_for_attribute(:name).limit} - validates :description, length: {maximum: type_for_attribute(:description).limit} + length: {maximum: type_for_attribute(:name).limit || Float::INFINITY} + validates :description, length: {maximum: type_for_attribute(:description).limit || Float::INFINITY} # Update :depths of progenies after parent change before_save if: :parent_changed? do diff --git a/app/models/setting.rb b/app/models/setting.rb new file mode 100644 index 0000000..7b9e374 --- /dev/null +++ b/app/models/setting.rb @@ -0,0 +1,20 @@ +# Key-value store for runtime application settings that are configured through +# the web setup wizard (or updated by an administrator) rather than hard-coded +# in application.rb. +# +# Known keys: +# skip_email_confirmation – "true"/"false", mirrors the homonymous option +# that was previously in application.rb. +class Setting < ApplicationRecord + validates :key, presence: true, uniqueness: true + + # Return the string value stored for +key+, or +default+ when absent. + def self.get(key, default: nil) + find_by(key: key)&.value || default + end + + # Persist +value+ for +key+, creating the record if it does not yet exist. + def self.set(key, value) + find_or_initialize_by(key: key).update!(value: value.to_s) + end +end diff --git a/app/views/setup/new.html.erb b/app/views/setup/new.html.erb new file mode 100644 index 0000000..bcfb20e --- /dev/null +++ b/app/views/setup/new.html.erb @@ -0,0 +1,38 @@ +<%= form_with url: setup_path, method: :post, class: "main-area" do |f| %> +
+ + + + <%= submit_tag t(".submit") %> +<% end %> diff --git a/config/application.rb.dist b/config/application.rb.dist index a097379..3b28023 100644 --- a/config/application.rb.dist +++ b/config/application.rb.dist @@ -55,11 +55,8 @@ module FixinMe # Sender address of account registration-related messages Devise.mailer_sender = 'noreply@localhost' - # When set to true, new user registrations are automatically confirmed - # without requiring email verification, so accounts become active - # immediately upon sign-up. Intended for installations where outgoing - # email is not configured, or for development / testing environments. - # Defaults to false (email confirmation is required). - # config.skip_email_confirmation = true + # Whether to skip e-mail confirmation for new registrations is configured + # through the web setup wizard and stored in the database (Setting model), + # so it does not need to be set here. end end diff --git a/config/locales/en.yml b/config/locales/en.yml index 6d09873..82ad2a3 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -163,6 +163,23 @@ en: