moduler 0.2.0 → 0.3.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 +4 -4
- data/app/assets/builds/moduler/tailwind.css +1358 -1
- data/app/assets/javascripts/moduler/application.js +1 -0
- data/app/assets/javascripts/moduler/controllers/flash_controller.js +19 -0
- data/app/assets/stylesheets/moduler/tailwind.css +3 -0
- data/app/components/moduler/administration/page_component.html.erb +7 -0
- data/app/components/moduler/administration/page_component.rb +6 -0
- data/app/components/moduler/badge_component.html.erb +8 -0
- data/app/components/moduler/badge_component.rb +10 -0
- data/app/components/moduler/button_component.html.erb +7 -0
- data/app/components/moduler/button_component.rb +19 -0
- data/app/components/moduler/button_link_component.html.erb +5 -0
- data/app/components/moduler/button_link_component.rb +15 -0
- data/app/components/moduler/filter_component.html.erb +12 -0
- data/app/components/moduler/filter_component.rb +15 -0
- data/app/components/moduler/form/base_error_component.html.erb +1 -0
- data/app/components/moduler/form/base_error_component.rb +9 -0
- data/app/components/moduler/form/email_component.html.erb +14 -0
- data/app/components/moduler/form/email_component.rb +11 -0
- data/app/components/moduler/form/password_component.html.erb +14 -0
- data/app/components/moduler/form/password_component.rb +11 -0
- data/app/components/moduler/form/section_component.html.erb +5 -0
- data/app/components/moduler/form/section_component.rb +14 -0
- data/app/components/moduler/form/select_component.html.erb +14 -0
- data/app/components/moduler/form/select_component.rb +12 -0
- data/app/components/moduler/form/submit_component.html.erb +3 -0
- data/app/components/moduler/form/submit_component.rb +10 -0
- data/app/components/moduler/form/text_component.html.erb +14 -0
- data/app/components/moduler/form/text_component.rb +11 -0
- data/app/components/moduler/list_component.html.erb +15 -0
- data/app/components/moduler/list_component.rb +18 -0
- data/app/components/moduler/list_item_component.html.erb +23 -0
- data/app/components/moduler/list_item_component.rb +14 -0
- data/app/components/moduler/page_header_component.html.erb +16 -0
- data/app/components/moduler/page_header_component.rb +12 -0
- data/app/components/moduler/sidebar_action_component.html.erb +5 -0
- data/app/components/moduler/sidebar_action_component.rb +15 -0
- data/app/controllers/concerns/moduler/authentication.rb +10 -2
- data/app/controllers/moduler/administration/application_controller.rb +7 -0
- data/app/controllers/moduler/administration/home_controller.rb +9 -0
- data/app/controllers/moduler/administration/sessions_controller.rb +34 -0
- data/app/controllers/moduler/administration/users_controller.rb +56 -0
- data/app/controllers/moduler/application_controller.rb +15 -0
- data/app/controllers/moduler/profile/sessions/revocation_controller.rb +14 -0
- data/app/controllers/moduler/profile/sessions_controller.rb +21 -0
- data/app/controllers/moduler/profile_controller.rb +27 -0
- data/app/controllers/moduler/sessions_controller.rb +8 -1
- data/app/controllers/moduler/users_controller.rb +7 -0
- data/app/filters/application_filter.rb +41 -0
- data/app/filters/query_filter.rb +18 -0
- data/app/lib/moduler/form_builder.rb +2 -2
- data/app/models/moduler/session.rb +12 -0
- data/app/models/moduler/user.rb +9 -0
- data/app/policies/moduler/administration/base_policy.rb +11 -0
- data/app/policies/moduler/administration/home_policy.rb +9 -0
- data/app/policies/moduler/administration/session_policy.rb +13 -0
- data/app/policies/moduler/administration/user_policy.rb +25 -0
- data/app/policies/moduler/application_policy.rb +38 -0
- data/app/views/kaminari/_gap.html.erb +1 -0
- data/app/views/kaminari/_next_page.html.erb +1 -0
- data/app/views/kaminari/_page.html.erb +3 -0
- data/app/views/kaminari/_paginator.html.erb +13 -0
- data/app/views/kaminari/_prev_page.html.erb +1 -0
- data/app/views/layouts/moduler/application.html.erb +5 -6
- data/app/views/moduler/administration/home/index.html.erb +6 -0
- data/app/views/moduler/administration/sessions/_session.html.erb +30 -0
- data/app/views/moduler/administration/sessions/index.html.erb +9 -0
- data/app/views/moduler/administration/users/_user.html.erb +13 -0
- data/app/views/moduler/administration/users/edit.html.erb +14 -0
- data/app/views/moduler/administration/users/index.html.erb +13 -0
- data/app/views/moduler/administration/users/new.html.erb +18 -0
- data/app/views/moduler/common/_flash.html.erb +16 -0
- data/app/views/moduler/common/_header.html.erb +12 -3
- data/app/views/moduler/common/_sidebar.html.erb +6 -0
- data/app/views/moduler/common/administration/_sidebar.html.erb +6 -0
- data/app/views/moduler/profile/edit.html.erb +28 -0
- data/app/views/moduler/profile/sessions/_session.html.erb +52 -0
- data/app/views/moduler/profile/sessions/index.html.erb +26 -0
- data/app/views/moduler/sessions/new.html.erb +4 -18
- data/app/views/moduler/users/new.html.erb +5 -32
- data/config/importmap.rb +1 -0
- data/config/locales/moduler/administration/sessions/en.yml +24 -0
- data/config/locales/moduler/administration/users/en.yml +43 -0
- data/config/locales/moduler/common/en.yml +5 -0
- data/config/locales/moduler/components/en.yml +11 -0
- data/config/locales/moduler/en.yml +4 -0
- data/config/locales/moduler/profile/en.yml +26 -0
- data/config/locales/moduler/profile/sessions/en.yml +25 -0
- data/config/locales/moduler/sessions/en.yml +1 -0
- data/config/locales/moduler/users/en.yml +7 -1
- data/config/routes.rb +14 -1
- data/db/migrate/20261015171504_add_role_to_moduler_users.rb +5 -0
- data/db/migrate/20261020191709_enable_pg_trgm.rb +5 -0
- data/lib/moduler/engine.rb +2 -0
- data/lib/moduler/version.rb +1 -1
- data/lib/moduler.rb +4 -0
- data/lib/tasks/moduler_tasks.rake +2 -4
- metadata +151 -5
- data/app/assets/javascripts/moduler/controllers/hello_controller.js +0 -6
- data/app/controllers/moduler/hello_controller.rb +0 -5
- data/app/views/moduler/hello/index.html.erb +0 -5
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
export default class extends Controller {
|
|
4
|
+
static values = { dismissAfter: { type: Number, default: 5000 } }
|
|
5
|
+
|
|
6
|
+
connect() {
|
|
7
|
+
this.timeout = setTimeout(() => this.dismiss(), this.dismissAfterValue)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
disconnect() {
|
|
11
|
+
clearTimeout(this.timeout)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
dismiss() {
|
|
15
|
+
clearTimeout(this.timeout)
|
|
16
|
+
this.element.classList.add("opacity-0", "translate-x-2")
|
|
17
|
+
this.element.addEventListener("transitionend", () => this.element.remove(), { once: true })
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<% case @variant %>
|
|
2
|
+
<% when :success %>
|
|
3
|
+
<span class="inline-flex items-center rounded-full bg-emerald-50 px-2 py-0.5 text-xs font-medium text-emerald-700 ring-1 ring-inset ring-emerald-600/20"><%= @text %></span>
|
|
4
|
+
<% when :muted %>
|
|
5
|
+
<span class="inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium text-slate-400 ring-1 ring-inset ring-slate-200"><%= @text %></span>
|
|
6
|
+
<% else %>
|
|
7
|
+
<span class="inline-flex items-center rounded-full bg-slate-50 px-2 py-0.5 text-xs font-medium text-slate-600 ring-1 ring-inset ring-slate-500/20"><%= @text %></span>
|
|
8
|
+
<% end %>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<% if small? %>
|
|
2
|
+
<%= button_to @text, @url, method: @options.fetch(:method, :post), form: form_options,
|
|
3
|
+
class: "cursor-pointer rounded-md bg-white px-2.5 py-1 text-sm font-medium text-slate-600 shadow-sm ring-1 ring-inset ring-slate-300 hover:bg-slate-50 hover:text-red-600" %>
|
|
4
|
+
<% else %>
|
|
5
|
+
<%= button_to @text, @url, method: @options.fetch(:method, :post), form: form_options,
|
|
6
|
+
class: "cursor-pointer whitespace-nowrap rounded-md bg-white px-3 py-1.5 text-sm/6 font-semibold text-red-600 shadow-sm ring-1 ring-inset ring-slate-300 hover:bg-red-50 hover:ring-red-200 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600" %>
|
|
7
|
+
<% end %>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moduler
|
|
4
|
+
class ButtonComponent < ViewComponent::Base
|
|
5
|
+
def initialize(text, url, options = {})
|
|
6
|
+
@text = text
|
|
7
|
+
@url = url
|
|
8
|
+
@options = options
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def small?
|
|
12
|
+
@options[:size] == :sm
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def form_options
|
|
16
|
+
{ class: "shrink-0", data: { turbo_confirm: @options[:confirm] }.compact }
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<% if small? %>
|
|
2
|
+
<%= link_to @text, @url, class: "shrink-0 rounded-md bg-white px-2.5 py-1 text-sm font-medium text-slate-600 shadow-sm ring-1 ring-inset ring-slate-300 hover:bg-slate-50 hover:text-slate-900" %>
|
|
3
|
+
<% else %>
|
|
4
|
+
<%= link_to @text, @url, class: "cursor-pointer whitespace-nowrap rounded-md bg-white px-3 py-2 text-sm/6 font-semibold text-custom ring-1 ring-inset ring-slate-300 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-custom" %>
|
|
5
|
+
<% end %>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moduler
|
|
4
|
+
class ButtonLinkComponent < ViewComponent::Base
|
|
5
|
+
def initialize(text, url, options = {})
|
|
6
|
+
@text = text
|
|
7
|
+
@url = url
|
|
8
|
+
@options = options
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def small?
|
|
12
|
+
@options[:size] == :sm
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<%= form_with model: @filter, url: @url, method: :get, scope: :filter, data: { turbo: false }, class: "flex flex-wrap items-end gap-3" do |form| %>
|
|
2
|
+
<div class="min-w-48 flex-1">
|
|
3
|
+
<%= form.text_field :query, placeholder: placeholder, autocomplete: "off",
|
|
4
|
+
class: "w-full rounded-md border border-slate-300 px-3 text-sm text-slate-900 placeholder:text-slate-400 focus:border-slate-900 focus:outline-none focus:ring-1 focus:ring-slate-900" %>
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
<%= form.submit t("moduler.components.filter.action.filter"),
|
|
8
|
+
class: "cursor-pointer whitespace-nowrap rounded-md bg-white px-3 py-2 text-sm/6 font-semibold text-custom ring-1 ring-inset ring-slate-300 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-custom" %>
|
|
9
|
+
<% if @filter.query.present? %>
|
|
10
|
+
<%= link_to t("moduler.components.filter.action.clear"), @url, class: "flex h-10 items-center text-sm text-slate-500 hover:text-slate-900" %>
|
|
11
|
+
<% end %>
|
|
12
|
+
<% end %>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moduler
|
|
4
|
+
class FilterComponent < ViewComponent::Base
|
|
5
|
+
def initialize(filter, url, options = {})
|
|
6
|
+
@filter = filter
|
|
7
|
+
@url = url
|
|
8
|
+
@options = options
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def placeholder
|
|
12
|
+
@options.fetch(:placeholder) { t("moduler.components.filter.placeholder.query") }
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%= @form.full_error :base, class: "my-2 text-sm text-red-600" %>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<div class="mt-2">
|
|
2
|
+
<% if @options.key?(:label) %>
|
|
3
|
+
<%= @form.label @name, @options[:label], class: "block text-sm text-slate-500" %>
|
|
4
|
+
<% end %>
|
|
5
|
+
<div class="mt-2">
|
|
6
|
+
<%= @form.email_field @name, placeholder: @options[:placeholder], disabled: @options[:disabled], class: "block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-custom sm:text-sm/6" %>
|
|
7
|
+
</div>
|
|
8
|
+
<% if @options.key?(:description) %>
|
|
9
|
+
<p class="mt-1 text-xs text-gray-400"><%= @options[:description] %></p>
|
|
10
|
+
<% end %>
|
|
11
|
+
<% if @form.error?(@name) %>
|
|
12
|
+
<%= @form.error(@name, class: "text-red-500 text-sm mt-1") %>
|
|
13
|
+
<% end %>
|
|
14
|
+
</div>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<div class="mt-2">
|
|
2
|
+
<% if @options.key?(:label) %>
|
|
3
|
+
<%= @form.label @name, @options[:label], class: "block text-sm text-slate-500" %>
|
|
4
|
+
<% end %>
|
|
5
|
+
<div class="mt-2">
|
|
6
|
+
<%= @form.password_field @name, placeholder: @options[:placeholder], class: "block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-custom sm:text-sm/6" %>
|
|
7
|
+
</div>
|
|
8
|
+
<% if @options.key?(:description) %>
|
|
9
|
+
<p class="mt-1 text-xs text-gray-400"><%= @options[:description] %></p>
|
|
10
|
+
<% end %>
|
|
11
|
+
<% if @form.error?(@name) %>
|
|
12
|
+
<%= @form.error(@name, class: "text-red-500 text-sm mt-1") %>
|
|
13
|
+
<% end %>
|
|
14
|
+
</div>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<div class="mt-2">
|
|
2
|
+
<% if @options.key?(:label) %>
|
|
3
|
+
<%= @form.label @name, @options[:label], class: "block text-sm text-slate-500" %>
|
|
4
|
+
<% end %>
|
|
5
|
+
<div class="mt-2">
|
|
6
|
+
<%= @form.select @name, @choices, {}, class: "block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-custom sm:text-sm/6" %>
|
|
7
|
+
</div>
|
|
8
|
+
<% if @options.key?(:description) %>
|
|
9
|
+
<p class="mt-1 text-xs text-gray-400"><%= @options[:description] %></p>
|
|
10
|
+
<% end %>
|
|
11
|
+
<% if @form.error?(@name) %>
|
|
12
|
+
<%= @form.error(@name, class: "text-red-500 text-sm mt-1") %>
|
|
13
|
+
<% end %>
|
|
14
|
+
</div>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<div class="mt-4">
|
|
2
|
+
<%= @form.submit @options[:text], class: "cursor-pointer flex float-right rounded-md bg-custom px-3 py-1.5 text-sm/6 font-semibold text-white shadow-sm hover:bg-custom focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-custom" %>
|
|
3
|
+
</div>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<div class="mt-2">
|
|
2
|
+
<% if @options.key?(:label) %>
|
|
3
|
+
<%= @form.label @name, @options[:label], class: "block text-sm text-slate-500" %>
|
|
4
|
+
<% end %>
|
|
5
|
+
<div class="mt-2">
|
|
6
|
+
<%= @form.text_field @name, placeholder: @options[:placeholder], class: "block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-custom sm:text-sm/6" %>
|
|
7
|
+
</div>
|
|
8
|
+
<% if @options.key?(:description) %>
|
|
9
|
+
<p class="mt-1 text-xs text-gray-400"><%= @options[:description] %></p>
|
|
10
|
+
<% end %>
|
|
11
|
+
<% if @form.error?(@name) %>
|
|
12
|
+
<%= @form.error(@name, class: "text-red-500 text-sm mt-1") %>
|
|
13
|
+
<% end %>
|
|
14
|
+
</div>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<div class="mt-6 flex-1">
|
|
2
|
+
<% if @collection.any? %>
|
|
3
|
+
<ul id="<%= @options[:id] %>" role="list" class="divide-y divide-slate-100 rounded-lg border border-slate-200 bg-white">
|
|
4
|
+
<%= content %>
|
|
5
|
+
</ul>
|
|
6
|
+
<% else %>
|
|
7
|
+
<p class="rounded-lg border border-dashed border-slate-300 px-4 py-8 text-center text-sm text-slate-500"><%= empty_message %></p>
|
|
8
|
+
<% end %>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<% if paginated? %>
|
|
12
|
+
<div class="mt-4 flex justify-end">
|
|
13
|
+
<%= helpers.paginate @collection %>
|
|
14
|
+
</div>
|
|
15
|
+
<% end %>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moduler
|
|
4
|
+
class ListComponent < ViewComponent::Base
|
|
5
|
+
def initialize(collection, options = {})
|
|
6
|
+
@collection = collection
|
|
7
|
+
@options = options
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def empty_message
|
|
11
|
+
@options.fetch(:empty) { t("moduler.components.list.empty") }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def paginated?
|
|
15
|
+
@collection.respond_to?(:total_pages)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<li id="<%= @options[:id] %>" class="flex items-center justify-between gap-4 px-4 py-4 sm:px-5">
|
|
2
|
+
<div class="min-w-0">
|
|
3
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
4
|
+
<p class="truncate text-sm font-semibold text-gray-900"><%= @title %></p>
|
|
5
|
+
|
|
6
|
+
<% badges.each do |badge| %>
|
|
7
|
+
<%= badge %>
|
|
8
|
+
<% end %>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<% if meta? %>
|
|
12
|
+
<p class="mt-1 text-xs text-slate-400"><%= meta %></p>
|
|
13
|
+
<% end %>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<% if actions? %>
|
|
17
|
+
<div class="flex shrink-0 items-center gap-2">
|
|
18
|
+
<% actions.each do |action| %>
|
|
19
|
+
<%= action %>
|
|
20
|
+
<% end %>
|
|
21
|
+
</div>
|
|
22
|
+
<% end %>
|
|
23
|
+
</li>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moduler
|
|
4
|
+
class ListItemComponent < ViewComponent::Base
|
|
5
|
+
renders_many :badges, Moduler::BadgeComponent
|
|
6
|
+
renders_one :meta
|
|
7
|
+
renders_many :actions
|
|
8
|
+
|
|
9
|
+
def initialize(title, options = {})
|
|
10
|
+
@title = title
|
|
11
|
+
@options = options
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<div class="mb-6 flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
|
2
|
+
<div>
|
|
3
|
+
<h1 class="text-base font-semibold text-gray-900"><%= @title %></h1>
|
|
4
|
+
<% if @options.key?(:description) %>
|
|
5
|
+
<p class="mt-1 text-sm text-slate-500"><%= @options[:description] %></p>
|
|
6
|
+
<% end %>
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<% if actions? %>
|
|
10
|
+
<div class="flex shrink-0 items-center gap-2">
|
|
11
|
+
<% actions.each do |action| %>
|
|
12
|
+
<%= action %>
|
|
13
|
+
<% end %>
|
|
14
|
+
</div>
|
|
15
|
+
<% end %>
|
|
16
|
+
</div>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Moduler
|
|
4
|
+
class SidebarActionComponent < ViewComponent::Base
|
|
5
|
+
def initialize(action, route, expected_controller)
|
|
6
|
+
@action = action
|
|
7
|
+
@route = route
|
|
8
|
+
@expected_controller = expected_controller
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def active?
|
|
12
|
+
controller_path.eql?(@expected_controller)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -28,7 +28,9 @@ module Moduler
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def find_session_by_cookie
|
|
31
|
-
|
|
31
|
+
if cookies.signed[:session_id]
|
|
32
|
+
Session.active.find_by(id: cookies.signed[:session_id], signed_in_at: Session::LIFETIME.ago..)
|
|
33
|
+
end
|
|
32
34
|
end
|
|
33
35
|
|
|
34
36
|
def request_authentication
|
|
@@ -42,7 +44,13 @@ module Moduler
|
|
|
42
44
|
def start_new_session_for(user)
|
|
43
45
|
user.sessions.create!(user_agent: request.user_agent, ip_address: request.remote_ip, signed_in_at: Time.current).tap do |session|
|
|
44
46
|
Current.session = session
|
|
45
|
-
cookies.signed
|
|
47
|
+
cookies.signed[:session_id] = {
|
|
48
|
+
value: session.id,
|
|
49
|
+
expires: Session::LIFETIME.from_now,
|
|
50
|
+
httponly: true,
|
|
51
|
+
same_site: :lax,
|
|
52
|
+
secure: Rails.env.production?
|
|
53
|
+
}
|
|
46
54
|
end
|
|
47
55
|
end
|
|
48
56
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module Moduler
|
|
2
|
+
module Administration
|
|
3
|
+
class SessionsController < ApplicationController
|
|
4
|
+
before_action -> { authorize([ :moduler, :administration, :session ]) }
|
|
5
|
+
before_action :set_session, only: %i[ destroy ]
|
|
6
|
+
|
|
7
|
+
def index
|
|
8
|
+
@filter, @sessions = QueryFilter.new(Session.includes(:user), permitted_filter_parameters).call
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def destroy
|
|
12
|
+
@session.touch(:signed_out_at)
|
|
13
|
+
|
|
14
|
+
if @session == Current.session
|
|
15
|
+
cookies.delete(:session_id)
|
|
16
|
+
redirect_to moduler.new_session_path, status: :see_other
|
|
17
|
+
else
|
|
18
|
+
redirect_to moduler.administration_sessions_path, status: :see_other, notice: t(".notice")
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def set_session
|
|
25
|
+
@session = Session.active.find(params[:id])
|
|
26
|
+
authorize(@session, policy_class: SessionPolicy)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def permitted_filter_parameters
|
|
30
|
+
(params.permit(filter: %i[ query ])[:filter] || {}).merge(params.permit(:page))
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module Moduler
|
|
2
|
+
module Administration
|
|
3
|
+
class UsersController < ApplicationController
|
|
4
|
+
before_action -> { authorize([ :moduler, :administration, :user ]) }
|
|
5
|
+
before_action :set_user, only: %i[ edit update ]
|
|
6
|
+
|
|
7
|
+
def index
|
|
8
|
+
@filter, @users = QueryFilter.new(User, permitted_filter_parameters).call
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def new
|
|
12
|
+
@user = User.new
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def create
|
|
16
|
+
@user = User.new(permitted_parameters)
|
|
17
|
+
|
|
18
|
+
if @user.save
|
|
19
|
+
redirect_to moduler.administration_users_path, notice: t(".notice")
|
|
20
|
+
else
|
|
21
|
+
render :new, status: :unprocessable_entity
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def edit
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def update
|
|
29
|
+
if @user.update(permitted_update_parameters)
|
|
30
|
+
redirect_to moduler.administration_users_path, notice: t(".notice")
|
|
31
|
+
else
|
|
32
|
+
render :edit, status: :unprocessable_entity
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def set_user
|
|
39
|
+
@user = User.find(params[:id])
|
|
40
|
+
authorize(@user, policy_class: UserPolicy)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def permitted_parameters
|
|
44
|
+
params.expect(user: %i[ email_address role password ])
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def permitted_update_parameters
|
|
48
|
+
params.expect(user: %i[ email_address role ])
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def permitted_filter_parameters
|
|
52
|
+
(params.permit(filter: %i[ query ])[:filter] || {}).merge(params.permit(:page))
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -1,7 +1,22 @@
|
|
|
1
1
|
module Moduler
|
|
2
2
|
class ApplicationController < ActionController::Base
|
|
3
3
|
include Authentication
|
|
4
|
+
include Pundit::Authorization
|
|
4
5
|
|
|
5
6
|
default_form_builder FormBuilder
|
|
7
|
+
|
|
8
|
+
rescue_from Pundit::NotAuthorizedError, with: :not_authorized
|
|
9
|
+
|
|
10
|
+
helper_method :current_user
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def current_user
|
|
15
|
+
Current.user
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def not_authorized
|
|
19
|
+
redirect_to moduler.root_path, alert: t("moduler.flash.not_authorized")
|
|
20
|
+
end
|
|
6
21
|
end
|
|
7
22
|
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module Moduler
|
|
2
|
+
module Profile
|
|
3
|
+
module Sessions
|
|
4
|
+
class RevocationController < ApplicationController
|
|
5
|
+
def destroy
|
|
6
|
+
Current.user.sessions.active.update_all(signed_out_at: Time.current)
|
|
7
|
+
cookies.delete(:session_id)
|
|
8
|
+
|
|
9
|
+
redirect_to moduler.new_session_path, status: :see_other
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|