maquina 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +28 -0
- data/Rakefile +8 -0
- data/app/assets/images/maquina/maquina.svg +18 -0
- data/app/assets/javascripts/maquina/application.js +4 -0
- data/app/assets/javascripts/maquina/controllers/alert_controller.js +29 -0
- data/app/assets/javascripts/maquina/controllers/application.js +9 -0
- data/app/assets/javascripts/maquina/controllers/file_controller.js +60 -0
- data/app/assets/javascripts/maquina/controllers/index.js +11 -0
- data/app/assets/javascripts/maquina/controllers/mobile_menu_controller.js +31 -0
- data/app/assets/javascripts/maquina/controllers/modal_controller.js +39 -0
- data/app/assets/javascripts/maquina/controllers/modal_open_controller.js +15 -0
- data/app/assets/javascripts/maquina/controllers/popup_menu_controller.js +17 -0
- data/app/assets/javascripts/maquina/controllers/submit_form_controller.js +11 -0
- data/app/assets/stylesheets/maquina/application.css +15 -0
- data/app/assets/stylesheets/maquina/application.tailwind.css +102 -0
- data/app/controllers/concerns/maquina/authenticate.rb +41 -0
- data/app/controllers/concerns/maquina/create.rb +27 -0
- data/app/controllers/concerns/maquina/destroy.rb +28 -0
- data/app/controllers/concerns/maquina/edit.rb +29 -0
- data/app/controllers/concerns/maquina/index.rb +33 -0
- data/app/controllers/concerns/maquina/new.rb +22 -0
- data/app/controllers/concerns/maquina/resourceful.rb +180 -0
- data/app/controllers/concerns/maquina/show.rb +27 -0
- data/app/controllers/concerns/maquina/update.rb +31 -0
- data/app/controllers/maquina/accept_invitations_controller.rb +28 -0
- data/app/controllers/maquina/application_controller.rb +19 -0
- data/app/controllers/maquina/dashboard_controller.rb +16 -0
- data/app/controllers/maquina/invitations_controller.rb +51 -0
- data/app/controllers/maquina/plans_controller.rb +56 -0
- data/app/controllers/maquina/sessions_controller.rb +56 -0
- data/app/controllers/maquina/unauthorized_controller.rb +9 -0
- data/app/controllers/maquina/users_controller.rb +24 -0
- data/app/helpers/maquina/application_helper.rb +19 -0
- data/app/helpers/maquina/navbar_menu_helper.rb +29 -0
- data/app/helpers/maquina/views_helper.rb +9 -0
- data/app/jobs/maquina/application_job.rb +4 -0
- data/app/mailers/maquina/application_mailer.rb +8 -0
- data/app/mailers/maquina/user_notifications_mailer.rb +16 -0
- data/app/models/concerns/maquina/authenticate_by.rb +33 -0
- data/app/models/concerns/maquina/blockeable.rb +28 -0
- data/app/models/concerns/maquina/multifactor.rb +80 -0
- data/app/models/concerns/maquina/retain_passwords.rb +32 -0
- data/app/models/concerns/maquina/searchable.rb +24 -0
- data/app/models/maquina/active_session.rb +33 -0
- data/app/models/maquina/application_record.rb +11 -0
- data/app/models/maquina/current.rb +21 -0
- data/app/models/maquina/invitation.rb +15 -0
- data/app/models/maquina/plan.rb +38 -0
- data/app/models/maquina/used_password.rb +17 -0
- data/app/models/maquina/user.rb +33 -0
- data/app/policies/maquina/application_policy.rb +50 -0
- data/app/policies/maquina/invitation_policy.rb +13 -0
- data/app/policies/maquina/navigation_policy.rb +13 -0
- data/app/policies/maquina/plan_policy.rb +49 -0
- data/app/policies/maquina/user_policy.rb +27 -0
- data/app/views/layouts/maquina/application.html.erb +26 -0
- data/app/views/layouts/maquina/mailer.html.erb +377 -0
- data/app/views/layouts/maquina/mailer.text.erb +12 -0
- data/app/views/layouts/maquina/sessions.html.erb +24 -0
- data/app/views/maquina/accept_invitations/new.html.erb +9 -0
- data/app/views/maquina/accept_invitations/new_view.rb +41 -0
- data/app/views/maquina/application/_navbar.html.erb +21 -0
- data/app/views/maquina/application/alert.rb +104 -0
- data/app/views/maquina/application/components/action_text_component.rb +20 -0
- data/app/views/maquina/application/components/checkbox_component.rb +21 -0
- data/app/views/maquina/application/components/component_base.rb +60 -0
- data/app/views/maquina/application/components/file_component.rb +59 -0
- data/app/views/maquina/application/components/input_component.rb +20 -0
- data/app/views/maquina/application/components/select_component.rb +44 -0
- data/app/views/maquina/application/create.turbo_stream.erb +11 -0
- data/app/views/maquina/application/edit.html.erb +9 -0
- data/app/views/maquina/application/edit.rb +17 -0
- data/app/views/maquina/application/form.rb +77 -0
- data/app/views/maquina/application/index.html.erb +10 -0
- data/app/views/maquina/application/index_header.rb +46 -0
- data/app/views/maquina/application/index_modal.rb +43 -0
- data/app/views/maquina/application/index_table.rb +121 -0
- data/app/views/maquina/application/new.html.erb +9 -0
- data/app/views/maquina/application/new.rb +18 -0
- data/app/views/maquina/application/sessions_header.rb +31 -0
- data/app/views/maquina/application/show.html.erb +1 -0
- data/app/views/maquina/application/update.turbo_stream.erb +11 -0
- data/app/views/maquina/application_view.rb +46 -0
- data/app/views/maquina/dashboard/index.html.erb +0 -0
- data/app/views/maquina/invitations/create.turbo_stream.erb +13 -0
- data/app/views/maquina/invitations/new.html.erb +3 -0
- data/app/views/maquina/navbar/menu.rb +62 -0
- data/app/views/maquina/navbar/menu_item_link.rb +34 -0
- data/app/views/maquina/navbar/mobile_button.rb +29 -0
- data/app/views/maquina/navbar/mobile_menu.rb +47 -0
- data/app/views/maquina/navbar/notification.rb +37 -0
- data/app/views/maquina/navbar/profile.rb +16 -0
- data/app/views/maquina/navbar/profile_button.rb +24 -0
- data/app/views/maquina/navbar/profile_menu.rb +108 -0
- data/app/views/maquina/navbar/profile_menu_item_link.rb +41 -0
- data/app/views/maquina/navbar/search.rb +40 -0
- data/app/views/maquina/navbar/title.rb +22 -0
- data/app/views/maquina/sessions/create.turbo_stream.erb +11 -0
- data/app/views/maquina/sessions/form.rb +56 -0
- data/app/views/maquina/sessions/new.html.erb +9 -0
- data/app/views/maquina/unauthorized/401.html.erb +1 -0
- data/app/views/maquina/user_notifications_mailer/invitation_email.html.erb +40 -0
- data/app/views/maquina/user_notifications_mailer/invitation_email.text.erb +12 -0
- data/config/definitions.rb +1 -0
- data/config/initializers/importmap.rb +17 -0
- data/config/initializers/money.rb +116 -0
- data/config/initializers/pagy.rb +235 -0
- data/config/locales/flash.en.yml +44 -0
- data/config/locales/forms.en.yml +58 -0
- data/config/locales/mailers.en.yml +35 -0
- data/config/locales/models.en.yml +34 -0
- data/config/locales/routes.en.yml +7 -0
- data/config/locales/views.en.yml +45 -0
- data/config/routes.rb +17 -0
- data/db/migrate/20221109010726_create_maquina_plans.rb +13 -0
- data/db/migrate/20221113000409_create_maquina_users.rb +19 -0
- data/db/migrate/20221113020108_create_maquina_used_passwords.rb +10 -0
- data/db/migrate/20221115223414_create_maquina_active_sessions.rb +15 -0
- data/db/migrate/20230201203922_create_maquina_invitations.rb +12 -0
- data/db/schema.rb +1 -0
- data/lib/generators/maquina/install_generator.rb +32 -0
- data/lib/generators/maquina/install_templates/install_templates_generator.rb +31 -0
- data/lib/generators/maquina/tailwind_config/tailwind_config_generator.rb +11 -0
- data/lib/generators/maquina/tailwind_config/templates/app/assets/config/maquina/tailwind.config.js.tt +68 -0
- data/lib/generators/maquina/templates/config/initializers/maquina.rb +3 -0
- data/lib/maquina/engine.rb +17 -0
- data/lib/maquina/version.rb +3 -0
- data/lib/maquina.rb +48 -0
- data/lib/tasks/install.rake +19 -0
- data/lib/tasks/maquina_tasks.rake +4 -0
- data/lib/tasks/tailwind.rake +25 -0
- metadata +456 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rotp"
|
|
4
|
+
|
|
5
|
+
module Maquina
|
|
6
|
+
module Multifactor
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
included do
|
|
10
|
+
if has_attribute?(:otp_secret_key) && has_attribute?(:otp_recovery_codes)
|
|
11
|
+
encrypts :otp_secret_key, :otp_recovery_codes
|
|
12
|
+
|
|
13
|
+
def multifactor?
|
|
14
|
+
otp_secret_key.present?
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def enable_multifactor!(multifactor_secret_key)
|
|
18
|
+
self.otp_secret_key = multifactor_secret_key
|
|
19
|
+
self.otp_recovery_codes = 10.times.map { SecureRandom.alphanumeric(12) }.join(" ")
|
|
20
|
+
|
|
21
|
+
save!
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def disable_multifactor!
|
|
25
|
+
self.last_otp_at = nil
|
|
26
|
+
self.otp_secret_key = nil
|
|
27
|
+
self.otp_recovery_codes = nil
|
|
28
|
+
|
|
29
|
+
save!
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def verify_multifactor_code!(multifactor_code, multifactor_secret_key = nil)
|
|
33
|
+
multifactor_secret_key ||= otp_secret_key
|
|
34
|
+
|
|
35
|
+
options = {drift_behind: 15}
|
|
36
|
+
options[:after] = last_otp_at.to_i if last_otp_at.present?
|
|
37
|
+
|
|
38
|
+
last_at = totp_instance(multifactor_secret_key).verify(multifactor_code, **options)
|
|
39
|
+
|
|
40
|
+
if last_at.present?
|
|
41
|
+
self.last_otp_at = Time.at(last_at).utc.to_datetime
|
|
42
|
+
save!
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
last_at
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def verify_multifactor_recovery_code!(recovery_code)
|
|
49
|
+
return nil if !multifactor?
|
|
50
|
+
|
|
51
|
+
codes = otp_recovery_codes.split(" ")
|
|
52
|
+
valid_code = codes.detect do |code|
|
|
53
|
+
ActiveSupport::SecurityUtils.secure_compare(recovery_code.strip, code)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
if valid_code.present?
|
|
57
|
+
codes.delete(recovery_code.strip)
|
|
58
|
+
self.otp_recovery_codes = codes.join(" ")
|
|
59
|
+
save!
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
valid_code
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def totp_instance(multifactor_secret_key = nil)
|
|
68
|
+
multifactor_secret_key ||= otp_secret_key
|
|
69
|
+
@totp_instance ||= ROTP::TOTP.new(multifactor_secret_key, issuer: I18n.t(:application_name), default: "Maquina")
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
class_methods do
|
|
75
|
+
def generate_multifactor_secret
|
|
76
|
+
ROTP::Base32.random
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Maquina
|
|
4
|
+
module RetainPasswords
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
validate :password_uniqueness, if: ->(user) { user.password_digest_changed? }
|
|
9
|
+
after_create :store_password_digest
|
|
10
|
+
after_update :store_password_digest, if: ->(user) { user.previous_changes.has_key?(:password_digest) }
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def password_uniqueness
|
|
15
|
+
return if Maquina.configuration.password_retain_count.blank? || Maquina.configuration.password_retain_count.zero?
|
|
16
|
+
|
|
17
|
+
used_before = Maquina::UsedPassword.where(user: self).detect do |used_password|
|
|
18
|
+
bcrypt = ::BCrypt::Password.new(used_password.password_digest)
|
|
19
|
+
hashed_value = ::BCrypt::Engine.hash_secret(password, bcrypt.salt)
|
|
20
|
+
|
|
21
|
+
ActiveSupport::SecurityUtils.secure_compare(hashed_value, used_password.password_digest)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
errors.add(:password, :password_already_used) if used_before.present?
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def store_password_digest
|
|
28
|
+
Maquina::UsedPassword.store_password_digest(id, password_digest)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pg_search"
|
|
4
|
+
|
|
5
|
+
module Maquina
|
|
6
|
+
module Searchable
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
included do
|
|
10
|
+
include PgSearch::Model
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class_methods do
|
|
14
|
+
def search_scope(fields: [], options: {})
|
|
15
|
+
return if fields.empty?
|
|
16
|
+
|
|
17
|
+
default_options = {tsearch: {prefix: true, any_word: true}}
|
|
18
|
+
search_options = options.deep_merge(default_options)
|
|
19
|
+
|
|
20
|
+
pg_search_scope :search_full, against: fields, using: search_options # , ignoring: :accents
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Maquina
|
|
4
|
+
class ActiveSession < ApplicationRecord
|
|
5
|
+
belongs_to :user, class_name: "Maquina::User", foreign_key: :maquina_user_id
|
|
6
|
+
delegate :blocked?, to: :user
|
|
7
|
+
|
|
8
|
+
after_initialize :configure_expiration
|
|
9
|
+
|
|
10
|
+
validates :expires_at, presence: true, comparison: {greater_than: Time.zone.now}, if: ->(session) { (session.new_record? || session.changed.includes?("expires_at")) && Maquina.configuration.session_expiration.present? }
|
|
11
|
+
validate :non_blocked_user
|
|
12
|
+
|
|
13
|
+
def expired?
|
|
14
|
+
return false if expires_at.blank?
|
|
15
|
+
|
|
16
|
+
!expires_at.future?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def non_blocked_user
|
|
22
|
+
return if user.blank? || !blocked?
|
|
23
|
+
|
|
24
|
+
errors.add(:user, :blocked)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def configure_expiration
|
|
28
|
+
if new_record? && expires_at.blank? && Maquina.configuration.session_expiration.present?
|
|
29
|
+
self.expires_at = Time.zone.now.since(Maquina.configuration.session_expiration)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Maquina
|
|
4
|
+
class Current < ActiveSupport::CurrentAttributes
|
|
5
|
+
attribute :active_session, :user
|
|
6
|
+
|
|
7
|
+
def signed_in?
|
|
8
|
+
return false if active_session.blank?
|
|
9
|
+
!active_session.expired? && !active_session.blocked?
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def active_session=(value)
|
|
13
|
+
super
|
|
14
|
+
self.user = value&.user
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def management?
|
|
18
|
+
user.management?
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Maquina
|
|
4
|
+
class Invitation < ApplicationRecord
|
|
5
|
+
validates :email, presence: true, format: {with: URI::MailTo::EMAIL_REGEXP}
|
|
6
|
+
|
|
7
|
+
def accepted?
|
|
8
|
+
accepted_at.present?
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def accept!
|
|
12
|
+
update!(accepted_at: Time.zone.now)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Maquina
|
|
4
|
+
##
|
|
5
|
+
# A class representing the Plan model in the Maquina module.
|
|
6
|
+
#
|
|
7
|
+
# == Attributes
|
|
8
|
+
#
|
|
9
|
+
# - +price+:: The price of the Plan, represented as a monetary value with currency.
|
|
10
|
+
# - +name+:: The name of the Plan.
|
|
11
|
+
#
|
|
12
|
+
# == Validations
|
|
13
|
+
#
|
|
14
|
+
# - +price+:: Must be greater than or equal to 0 if the Plan is marked as free.
|
|
15
|
+
# - +price+:: Must be greater than 0 if the Plan is not marked as free.
|
|
16
|
+
# - +name+:: Must be present and unique.
|
|
17
|
+
#
|
|
18
|
+
# == Scopes
|
|
19
|
+
#
|
|
20
|
+
# - +search_full+:: Provides a search scope for Plan model, allowing searching by name with options for prefix matching and matching any word.
|
|
21
|
+
#
|
|
22
|
+
# == Usage
|
|
23
|
+
#
|
|
24
|
+
# The Plan model represents a pricing plan in the Maquina module. To use the Plan model, create instances with valid
|
|
25
|
+
# attributes and use the provided methods and scopes for querying and manipulating plan data.
|
|
26
|
+
|
|
27
|
+
class Plan < ApplicationRecord
|
|
28
|
+
include Searchable
|
|
29
|
+
|
|
30
|
+
monetize :price_cents
|
|
31
|
+
|
|
32
|
+
validates :price, numericality: {greater_than_or_equal_to: 0}, if: ->(plan) { plan.free? }
|
|
33
|
+
validates :price, numericality: {greater_than: 0}, if: ->(plan) { !plan.free? }
|
|
34
|
+
validates :name, presence: true, uniqueness: true
|
|
35
|
+
|
|
36
|
+
search_scope(fields: [:name])
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Maquina
|
|
4
|
+
class UsedPassword < ApplicationRecord
|
|
5
|
+
belongs_to :user, class_name: "Maquina::User", foreign_key: :maquina_user_id
|
|
6
|
+
|
|
7
|
+
encrypts :password_digest
|
|
8
|
+
validates :password_digest, presence: true
|
|
9
|
+
|
|
10
|
+
def self.store_password_digest(user_id, password_digest)
|
|
11
|
+
return if Maquina.configuration.password_retain_count.blank? || Maquina.configuration.password_retain_count.zero?
|
|
12
|
+
|
|
13
|
+
Maquina::UsedPassword.where(user: user_id).order(id: :desc).offset(2).delete_all
|
|
14
|
+
Maquina::UsedPassword.create(maquina_user_id: user_id, password_digest: password_digest)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Maquina
|
|
4
|
+
class User < ApplicationRecord
|
|
5
|
+
include Maquina::Searchable
|
|
6
|
+
include Maquina::RetainPasswords
|
|
7
|
+
include Maquina::AuthenticateBy
|
|
8
|
+
include Maquina::Blockeable
|
|
9
|
+
include Maquina::Multifactor
|
|
10
|
+
|
|
11
|
+
PASSWORD_COMPLEXITY_REGEX = /\A(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&#-=+])[A-Za-z\d@$!%*?&#-=+]{8,}\z/
|
|
12
|
+
has_secure_password
|
|
13
|
+
|
|
14
|
+
validates :email, presence: true, uniqueness: true, format: {with: URI::MailTo::EMAIL_REGEXP}
|
|
15
|
+
validates :password, format: {with: PASSWORD_COMPLEXITY_REGEX}, unless: ->(user) { user.password.blank? }
|
|
16
|
+
|
|
17
|
+
before_save :downcase_email
|
|
18
|
+
|
|
19
|
+
search_scope(fields: [:email])
|
|
20
|
+
|
|
21
|
+
def expired_password?
|
|
22
|
+
return false if password_expires_at.blank?
|
|
23
|
+
|
|
24
|
+
password_expires_at < Time.zone.now
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def downcase_email
|
|
30
|
+
self.email = email.downcase
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Maquina
|
|
4
|
+
class ApplicationPolicy < ActionPolicy::Base
|
|
5
|
+
def index?
|
|
6
|
+
false
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def show?
|
|
10
|
+
false
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def new?
|
|
14
|
+
false
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def create?
|
|
18
|
+
new?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def edit?
|
|
22
|
+
false
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def update?
|
|
26
|
+
edit?
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def destroy?
|
|
30
|
+
false
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def new_modal?
|
|
34
|
+
false
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def management?
|
|
40
|
+
user.management?
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Define shared methods useful for most policies.
|
|
44
|
+
# For example:
|
|
45
|
+
#
|
|
46
|
+
# def owner?
|
|
47
|
+
# record.user_id == user.id
|
|
48
|
+
# end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Maquina
|
|
4
|
+
##
|
|
5
|
+
# A class representing the authorization policy for the Plan model.
|
|
6
|
+
#
|
|
7
|
+
# == Methods
|
|
8
|
+
#
|
|
9
|
+
# - +index?+:: Returns true if the user has authorization to view the index page for Plan model.
|
|
10
|
+
# - +new?+:: Returns true if the user has authorization to view the new page for creating a new Plan object.
|
|
11
|
+
# - +create?+:: Returns the same result as new?, as it requires the same authorization to create a new Plan object.
|
|
12
|
+
# - +edit?+:: Returns true if the user has authorization to view the edit page for an existing Plan object.
|
|
13
|
+
# - +relation_scope+:: Provides a relation scope for Plan model, allowing customization of query scopes based on authorization policies.
|
|
14
|
+
#
|
|
15
|
+
# == Private Methods
|
|
16
|
+
#
|
|
17
|
+
# - +management?+:: Helper method to determine if the user is in a management role. This method exists in Maquina::ApplicationPolicy.
|
|
18
|
+
#
|
|
19
|
+
# == Usage
|
|
20
|
+
#
|
|
21
|
+
# To use PlanPolicy, define the relevant authorization methods, such as management? or any other custom authorization
|
|
22
|
+
# methods, in the Maquina module or in a related authorization system. Then, inherit from PlanPolicy in your
|
|
23
|
+
# Plan model's policy class, and use the provided authorization methods, such as index?, new?, create?, and edit?, to
|
|
24
|
+
# define the authorization policies for Plan model actions.
|
|
25
|
+
|
|
26
|
+
class PlanPolicy < ApplicationPolicy
|
|
27
|
+
def index?
|
|
28
|
+
management?
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def new?
|
|
32
|
+
management?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def create?
|
|
36
|
+
new?
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def edit?
|
|
40
|
+
management?
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
relation_scope do |scope|
|
|
46
|
+
scope
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Maquina
|
|
4
|
+
class UserPolicy < ApplicationPolicy
|
|
5
|
+
def index?
|
|
6
|
+
management?
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def new?
|
|
10
|
+
management?
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def create?
|
|
14
|
+
new?
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def new_modal?
|
|
18
|
+
new?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
relation_scope do |scope|
|
|
24
|
+
scope.where(management: management?)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html class="h-full bg-gray-50">
|
|
3
|
+
<head>
|
|
4
|
+
<title><%= t("application_name") %></title>
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<%= csrf_meta_tags %>
|
|
7
|
+
<%= csp_meta_tag %>
|
|
8
|
+
|
|
9
|
+
<%= stylesheet_link_tag "maquina/tailwind", "inter-font", "data-turbo-track": "reload" %>
|
|
10
|
+
<%= stylesheet_link_tag "maquina/application", "data-turbo-track": "reload", media: "all" %>
|
|
11
|
+
|
|
12
|
+
<%= maquina_importmap_tags %>
|
|
13
|
+
</head>
|
|
14
|
+
|
|
15
|
+
<body class="h-full">
|
|
16
|
+
<%= render partial: "navbar" %>
|
|
17
|
+
|
|
18
|
+
<%= turbo_frame_tag :alert do %>
|
|
19
|
+
<%= render Maquina::Application::Alert.new(flash) %>
|
|
20
|
+
<% end %>
|
|
21
|
+
|
|
22
|
+
<main class="container mx-auto mt-24">
|
|
23
|
+
<%= yield %>
|
|
24
|
+
</main>
|
|
25
|
+
</body>
|
|
26
|
+
</html>
|