maquina 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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>
|