diff --git a/.rubocop.yml b/.rubocop.yml index 2271d0d..928fd6f 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -27,9 +27,9 @@ Metrics/BlockLength: - config/initializers/doorkeeper_openid_connect.rb - lib/tasks/* - app/admin/* - - spec/models/*.rb - - spec/controllers/*.rb - - spec/controllers/admin/*.rb + - 'Rakefile' + - '**/*.rake' + - 'spec/**/*.rb' Style/GlobalVars: AllowedVariables: diff --git a/app/controllers/admin/custom_group_data_types_controller.rb b/app/controllers/admin/custom_group_data_types_controller.rb new file mode 100644 index 0000000..1c6b6ec --- /dev/null +++ b/app/controllers/admin/custom_group_data_types_controller.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class Admin::CustomGroupDataTypesController < AdminController + private + + def model + CustomGroupDataType + end + + def model_params + params.require(:custom_group_data_type).permit( + :name, :custom_type + ) + end + + def whitelist_attributes + %w[name custom_type] + end +end diff --git a/app/controllers/admin/custom_userdata_types_controller.rb b/app/controllers/admin/custom_userdata_types_controller.rb new file mode 100644 index 0000000..5836481 --- /dev/null +++ b/app/controllers/admin/custom_userdata_types_controller.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class Admin::CustomUserdataTypesController < AdminController + private + + def model + CustomUserdataType + end + + def model_params + params.require(:custom_userdata_type).permit( + :name, :custom_type + ) + end + + def whitelist_attributes + %w[name custom_type] + end +end diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index a7ac94a..4eb7e2e 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -6,6 +6,24 @@ def email render :email, layout: nil end + def update_custom_attributes # rubocop:disable Metrics/MethodLength + @model = Group.find(params[:group_id]) + custom_groupdata_params.each do |name, value| + custom_type = CustomGroupDataType.where(name: name).first + custom_datum = CustomGroupdatum.where( + group_id: @model.id, + custom_group_data_type: custom_type + ).first_or_initialize + begin + custom_datum.value = value + custom_datum.save + rescue RuntimeError + flash[:error] = 'Failed to update group data, invalid value' + end + end + redirect_to [:edit, :admin, @model] + end + private def whitelist_attributes @@ -53,4 +71,8 @@ def model_params def sort_whitelist %w[created_at name] end + + def custom_groupdata_params + params.require(:custom_data).permit! + end end diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 06cbc6d..31b9cd7 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -25,6 +25,24 @@ def reset_password end end + def update_custom_attributes # rubocop:disable Metrics/MethodLength + @model = User.find(params[:user_id]) + custom_userdata_params.each do |name, value| + custom_type = CustomUserdataType.where(name: name).first + custom_datum = CustomUserdatum.where( + user_id: @model.id, + custom_userdata_type: custom_type + ).first_or_initialize + begin + custom_datum.value = value + custom_datum.save + rescue RuntimeError + flash[:error] = 'Failed to update userdata, invalid value' + end + end + redirect_to [:edit, :admin, @model] + end + private def can_destroy? @@ -78,4 +96,8 @@ def filter(rel) # rubocop:disable Metrics/AbcSize rel end end + + def custom_userdata_params + params.require(:custom_data).permit! + end end diff --git a/app/controllers/profile/additional_properties_controller.rb b/app/controllers/profile/additional_properties_controller.rb new file mode 100644 index 0000000..156317c --- /dev/null +++ b/app/controllers/profile/additional_properties_controller.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +class Profile::AdditionalPropertiesController < ApplicationController + def index; end + + def update # rubocop:disable Metrics/MethodLength + custom_userdata_params.each do |name, value| + custom_type = CustomUserdataType.where(name: name).first + custom_datum = CustomUserdatum.where( + user_id: current_user.id, + custom_userdata_type: custom_type + ).first_or_initialize + begin + custom_datum.value = value + custom_datum.save + rescue RuntimeError + flash[:error] = 'Failed to update userdata, invalid value' + end + end + redirect_to profile_additional_properties_path + end + + protected + + def custom_userdata_params + params.require(:custom_data).permit! + end +end diff --git a/app/models/concerns/deserializable.rb b/app/models/concerns/deserializable.rb new file mode 100644 index 0000000..7f7558f --- /dev/null +++ b/app/models/concerns/deserializable.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Deserializable + SEPARATOR_REGEXP = /[\n,;]+/.freeze + + # takes a string and returns a typed thing + def deserialize(value, custom_type) + case custom_type + when 'boolean' + ['t', 'true', '1', 1, true, :true].include?(value) # rubocop:disable Lint/BooleanSymbol + when 'array' + (value || '').split(SEPARATOR_REGEXP).map(&:strip).reject(&:empty?) + when 'integer' + value.to_i + else + value + end + end +end diff --git a/app/models/custom_group_data_type.rb b/app/models/custom_group_data_type.rb new file mode 100644 index 0000000..faca96f --- /dev/null +++ b/app/models/custom_group_data_type.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class CustomGroupDataType < ApplicationRecord + has_many :custom_groupdata, dependent: :destroy +end diff --git a/app/models/custom_groupdatum.rb b/app/models/custom_groupdatum.rb new file mode 100644 index 0000000..05a7439 --- /dev/null +++ b/app/models/custom_groupdatum.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +class CustomGroupdatum < ApplicationRecord + belongs_to :group + belongs_to :custom_group_data_type + + serialize :value_raw, JSON + + delegate :name, to: :custom_group_data_type + + include Deserializable + + def value + value_raw + end + + def type + custom_group_data_type.custom_type + end + + def value=(new_value) # rubocop:disable Metrics/MethodLength + new_value = deserialize(new_value, custom_group_data_type.custom_type) + if custom_group_data_type + valid = case custom_group_data_type.custom_type + when 'boolean' + new_value.is_a?(TrueClass) || new_value.is_a?(FalseClass) + when 'array' + new_value.is_a?(Array) + when 'integer' + new_value.is_a?(Integer) + else + true + end + raise "Invalid User Data: #{new_value} isn't an #{custom_group_data_type.custom_type}" unless valid + end + self.value_raw = new_value + end +end diff --git a/app/models/custom_userdata_type.rb b/app/models/custom_userdata_type.rb new file mode 100644 index 0000000..647dc59 --- /dev/null +++ b/app/models/custom_userdata_type.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class CustomUserdataType < ApplicationRecord + has_many :custom_userdata, dependent: :destroy +end diff --git a/app/models/custom_userdatum.rb b/app/models/custom_userdatum.rb new file mode 100644 index 0000000..7d43efc --- /dev/null +++ b/app/models/custom_userdatum.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +class CustomUserdatum < ApplicationRecord + belongs_to :user + belongs_to :custom_userdata_type + serialize :value_raw, JSON + + delegate :name, to: :custom_userdata_type + + include Deserializable + + def value + value_raw + end + + def type + custom_userdata_type.custom_type + end + + def value=(new_value) # rubocop:disable Metrics/MethodLength + new_value = deserialize(new_value, custom_userdata_type.custom_type) + if custom_userdata_type + valid = case custom_userdata_type.custom_type + when 'boolean' + new_value.is_a?(TrueClass) || new_value.is_a?(FalseClass) + when 'array' + new_value.is_a?(Array) + when 'integer' + new_value.is_a?(Integer) + else + true + end + raise "Invalid User Data: #{new_value} isn't an #{custom_userdata_type.custom_type}" unless valid + end + self.value_raw = new_value + end +end diff --git a/app/models/group.rb b/app/models/group.rb index 77d2984..bdf4dbb 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -42,6 +42,8 @@ class Group < ApplicationRecord has_many :user_groups, dependent: :destroy has_many :users, through: :user_groups + has_many :custom_groupdata, dependent: :destroy + def to_s name end diff --git a/app/models/setting.rb b/app/models/setting.rb index 0e36366..0e1a897 100644 --- a/app/models/setting.rb +++ b/app/models/setting.rb @@ -17,6 +17,8 @@ # RailsSettings Model class Setting < ApplicationRecord + + SEPARATOR_REGEXP = /[\n,;]+/.freeze # cache_prefix { "v1" } # Define your fields diff --git a/app/models/user.rb b/app/models/user.rb index 86ea1ef..1612057 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -56,6 +56,8 @@ class User < ApplicationRecord # rubocop:disable Metrics/ClassLength has_many :logins, dependent: :destroy + has_many :custom_userdata, dependent: :destroy + validates :username, presence: true, uniqueness: { case_sensitive: false } # rubocop:disable Rails/UniqueValidationWithoutIndex validate :validate_username diff --git a/app/views/admin/custom_group_data_types/_form.html.erb b/app/views/admin/custom_group_data_types/_form.html.erb new file mode 100644 index 0000000..aa77a37 --- /dev/null +++ b/app/views/admin/custom_group_data_types/_form.html.erb @@ -0,0 +1,39 @@ +<%= form_with(model: [:admin, model], local: true, class: 'form') do |form| %> + <% if model.errors.any? %> +
Preview refreshes after save.
+ + <%- end %> +Preview refreshes after save.
- -