moduler 0.1.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.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/builds/moduler/tailwind.css +1359 -0
  3. data/app/assets/config/moduler/manifest.js +2 -0
  4. data/app/assets/javascripts/moduler/application.js +9 -0
  5. data/app/assets/javascripts/moduler/controllers/flash_controller.js +19 -0
  6. data/app/assets/stylesheets/moduler/tailwind.css +21 -0
  7. data/app/components/moduler/administration/page_component.html.erb +7 -0
  8. data/app/components/moduler/administration/page_component.rb +6 -0
  9. data/app/components/moduler/badge_component.html.erb +8 -0
  10. data/app/components/moduler/badge_component.rb +10 -0
  11. data/app/components/moduler/button_component.html.erb +7 -0
  12. data/app/components/moduler/button_component.rb +19 -0
  13. data/app/components/moduler/button_link_component.html.erb +5 -0
  14. data/app/components/moduler/button_link_component.rb +15 -0
  15. data/app/components/moduler/filter_component.html.erb +12 -0
  16. data/app/components/moduler/filter_component.rb +15 -0
  17. data/app/components/moduler/form/base_error_component.html.erb +1 -0
  18. data/app/components/moduler/form/base_error_component.rb +9 -0
  19. data/app/components/moduler/form/email_component.html.erb +14 -0
  20. data/app/components/moduler/form/email_component.rb +11 -0
  21. data/app/components/moduler/form/password_component.html.erb +14 -0
  22. data/app/components/moduler/form/password_component.rb +11 -0
  23. data/app/components/moduler/form/section_component.html.erb +5 -0
  24. data/app/components/moduler/form/section_component.rb +14 -0
  25. data/app/components/moduler/form/select_component.html.erb +14 -0
  26. data/app/components/moduler/form/select_component.rb +12 -0
  27. data/app/components/moduler/form/submit_component.html.erb +3 -0
  28. data/app/components/moduler/form/submit_component.rb +10 -0
  29. data/app/components/moduler/form/text_component.html.erb +14 -0
  30. data/app/components/moduler/form/text_component.rb +11 -0
  31. data/app/components/moduler/list_component.html.erb +15 -0
  32. data/app/components/moduler/list_component.rb +18 -0
  33. data/app/components/moduler/list_item_component.html.erb +23 -0
  34. data/app/components/moduler/list_item_component.rb +14 -0
  35. data/app/components/moduler/page_header_component.html.erb +16 -0
  36. data/app/components/moduler/page_header_component.rb +12 -0
  37. data/app/components/moduler/sidebar_action_component.html.erb +5 -0
  38. data/app/components/moduler/sidebar_action_component.rb +15 -0
  39. data/app/controllers/concerns/moduler/authentication.rb +62 -0
  40. data/app/controllers/moduler/administration/application_controller.rb +7 -0
  41. data/app/controllers/moduler/administration/home_controller.rb +9 -0
  42. data/app/controllers/moduler/administration/sessions_controller.rb +34 -0
  43. data/app/controllers/moduler/administration/users_controller.rb +56 -0
  44. data/app/controllers/moduler/application_controller.rb +18 -0
  45. data/app/controllers/moduler/home_controller.rb +5 -0
  46. data/app/controllers/moduler/profile/sessions/revocation_controller.rb +14 -0
  47. data/app/controllers/moduler/profile/sessions_controller.rb +21 -0
  48. data/app/controllers/moduler/profile_controller.rb +27 -0
  49. data/app/controllers/moduler/sessions_controller.rb +39 -0
  50. data/app/controllers/moduler/users_controller.rb +36 -0
  51. data/app/filters/application_filter.rb +41 -0
  52. data/app/filters/query_filter.rb +18 -0
  53. data/app/helpers/moduler/application_helper.rb +4 -0
  54. data/app/lib/moduler/form_builder.rb +23 -0
  55. data/app/models/moduler/current.rb +6 -0
  56. data/app/models/moduler/session.rb +17 -0
  57. data/app/models/moduler/user.rb +18 -0
  58. data/app/policies/moduler/administration/base_policy.rb +11 -0
  59. data/app/policies/moduler/administration/home_policy.rb +9 -0
  60. data/app/policies/moduler/administration/session_policy.rb +13 -0
  61. data/app/policies/moduler/administration/user_policy.rb +25 -0
  62. data/app/policies/moduler/application_policy.rb +38 -0
  63. data/app/views/kaminari/_gap.html.erb +1 -0
  64. data/app/views/kaminari/_next_page.html.erb +1 -0
  65. data/app/views/kaminari/_page.html.erb +3 -0
  66. data/app/views/kaminari/_paginator.html.erb +13 -0
  67. data/app/views/kaminari/_prev_page.html.erb +1 -0
  68. data/app/views/layouts/moduler/application.html.erb +16 -10
  69. data/app/views/moduler/administration/home/index.html.erb +6 -0
  70. data/app/views/moduler/administration/sessions/_session.html.erb +30 -0
  71. data/app/views/moduler/administration/sessions/index.html.erb +9 -0
  72. data/app/views/moduler/administration/users/_user.html.erb +13 -0
  73. data/app/views/moduler/administration/users/edit.html.erb +14 -0
  74. data/app/views/moduler/administration/users/index.html.erb +13 -0
  75. data/app/views/moduler/administration/users/new.html.erb +18 -0
  76. data/app/views/moduler/common/_flash.html.erb +16 -0
  77. data/app/views/moduler/common/_header.html.erb +18 -0
  78. data/app/views/moduler/common/_sidebar.html.erb +6 -0
  79. data/app/views/moduler/common/administration/_sidebar.html.erb +6 -0
  80. data/app/views/moduler/home/index.html.erb +0 -0
  81. data/app/views/moduler/profile/edit.html.erb +28 -0
  82. data/app/views/moduler/profile/sessions/_session.html.erb +52 -0
  83. data/app/views/moduler/profile/sessions/index.html.erb +26 -0
  84. data/app/views/moduler/sessions/new.html.erb +31 -0
  85. data/app/views/moduler/users/new.html.erb +26 -0
  86. data/config/importmap.rb +5 -0
  87. data/config/locales/moduler/administration/sessions/en.yml +24 -0
  88. data/config/locales/moduler/administration/users/en.yml +43 -0
  89. data/config/locales/moduler/common/en.yml +13 -0
  90. data/config/locales/moduler/components/en.yml +11 -0
  91. data/config/locales/moduler/en.yml +4 -0
  92. data/config/locales/moduler/profile/en.yml +26 -0
  93. data/config/locales/moduler/profile/sessions/en.yml +25 -0
  94. data/config/locales/moduler/sessions/en.yml +18 -0
  95. data/config/locales/moduler/users/en.yml +21 -0
  96. data/config/routes.rb +19 -0
  97. data/db/migrate/20260322152259_create_moduler_users.rb +12 -0
  98. data/db/migrate/20260322153744_create_moduler_sessions.rb +13 -0
  99. data/db/migrate/20261015171504_add_role_to_moduler_users.rb +5 -0
  100. data/db/migrate/20261020191709_enable_pg_trgm.rb +5 -0
  101. data/lib/moduler/configuration.rb +12 -0
  102. data/lib/moduler/engine.rb +20 -0
  103. data/lib/moduler/router.rb +10 -0
  104. data/lib/moduler/routing.rb +11 -0
  105. data/lib/moduler/version.rb +1 -1
  106. data/lib/moduler.rb +12 -1
  107. data/lib/tasks/moduler_tasks.rake +42 -4
  108. metadata +250 -2
@@ -0,0 +1,28 @@
1
+ <main class="mx-auto flex w-full flex-1 max-w-[120rem] flex-col gap-4 px-4 md:flex-row md:gap-8 md:pl-0 md:pr-6">
2
+ <%= render "moduler/common/sidebar" %>
3
+
4
+ <div class="flex min-w-0 flex-1 flex-col pb-4 md:mt-8">
5
+ <div class="mb-4 flex min-h-9 items-center justify-between gap-4">
6
+ <h1 class="text-base font-semibold text-gray-900"><%= t(".title") %></h1>
7
+ </div>
8
+
9
+ <%= form_with model: @user, url: moduler.profile_path, method: :patch, data: { turbo: false }, class: "w-full max-w-lg space-y-6" do |form| %>
10
+ <%= render(::Moduler::Form::BaseErrorComponent.new(form)) %>
11
+
12
+ <%= render(::Moduler::Form::SectionComponent.new(t(".section.data"))) do %>
13
+ <%= render(::Moduler::Form::EmailComponent.new(form, :email_address, label: t(".label.email_address"), disabled: :disabled)) %>
14
+ <% end %>
15
+
16
+ <%= render(::Moduler::Form::SectionComponent.new(t(".section.password"), divider: true)) do %>
17
+ <%= render(::Moduler::Form::PasswordComponent.new(form, :password, label: t(".label.password"))) %>
18
+ <%= render(::Moduler::Form::PasswordComponent.new(form, :password_confirmation, label: t(".label.password_confirmation"), description: t(".description.password_confirmation"))) %>
19
+ <% end %>
20
+
21
+ <%= render(::Moduler::Form::SectionComponent.new(t(".section.current_password"), divider: true)) do %>
22
+ <%= render(::Moduler::Form::PasswordComponent.new(form, :password_challenge, label: t(".label.password_challenge"), description: t(".description.password_challenge"))) %>
23
+ <% end %>
24
+
25
+ <%= render(::Moduler::Form::SubmitComponent.new(form, text: t(".action.save"))) %>
26
+ <% end %>
27
+ </div>
28
+ </main>
@@ -0,0 +1,52 @@
1
+ <% current = session == Moduler::Current.session %>
2
+ <% mobile = session.user_agent.to_s.match?(/Mobile|Android|iPhone|iPad/) %>
3
+
4
+ <li id="<%= dom_id(session) %>" class="flex items-center justify-between gap-4 px-4 py-4 sm:px-5 <%= "bg-slate-50/60" unless session.active? %>">
5
+ <div class="flex min-w-0 items-start gap-4">
6
+ <div class="flex size-10 shrink-0 items-center justify-center rounded-full <%= session.active? ? "bg-slate-100 text-slate-600" : "bg-slate-100/60 text-slate-400" %>">
7
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="size-5" aria-hidden="true">
8
+ <% if mobile %>
9
+ <rect x="7" y="2.5" width="10" height="19" rx="2" />
10
+ <path d="M11 18.5h2" />
11
+ <% else %>
12
+ <rect x="3" y="4" width="18" height="12" rx="1.5" />
13
+ <path d="M8 20h8M12 16v4" />
14
+ <% end %>
15
+ </svg>
16
+ </div>
17
+
18
+ <div class="min-w-0">
19
+ <div class="flex flex-wrap items-center gap-2">
20
+ <p class="text-sm font-semibold <%= session.active? ? "text-gray-900" : "text-slate-500" %>">
21
+ <%= session.ip_address.presence || t(".unknown_ip") %>
22
+ </p>
23
+
24
+ <% if current %>
25
+ <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"><%= t(".status.current") %></span>
26
+ <% elsif session.active? %>
27
+ <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"><%= t(".status.active") %></span>
28
+ <% else %>
29
+ <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"><%= t(".status.signed_out") %></span>
30
+ <% end %>
31
+ </div>
32
+
33
+ <p class="mt-1 truncate text-xs text-slate-500" title="<%= session.user_agent %>">
34
+ <%= session.user_agent.presence || t(".unknown_device") %>
35
+ </p>
36
+
37
+ <p class="mt-1 text-xs text-slate-400">
38
+ <%= time_tag session.signed_in_at, t(".signed_in", time: time_ago_in_words(session.signed_in_at)), title: l(session.signed_in_at, format: :long) %>
39
+ <% if session.signed_out_at %>
40
+ <span aria-hidden="true">&middot;</span>
41
+ <%= time_tag session.signed_out_at, t(".signed_out", time: time_ago_in_words(session.signed_out_at)), title: l(session.signed_out_at, format: :long) %>
42
+ <% end %>
43
+ </p>
44
+ </div>
45
+ </div>
46
+
47
+ <% if session.active? %>
48
+ <%= button_to t(".action.destroy"), moduler.profile_session_path(session), method: :delete,
49
+ 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",
50
+ form: { class: "shrink-0", data: { turbo_confirm: t(current ? ".confirmation.destroy_current" : ".confirmation.destroy") } } %>
51
+ <% end %>
52
+ </li>
@@ -0,0 +1,26 @@
1
+ <main class="mx-auto flex w-full flex-1 max-w-[120rem] flex-col gap-4 px-4 md:flex-row md:gap-8 md:pl-0 md:pr-6">
2
+ <%= render "moduler/common/sidebar" %>
3
+
4
+ <div class="flex min-w-0 flex-1 flex-col pb-4 md:mt-8">
5
+ <div class="mb-6 flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
6
+ <div>
7
+ <h1 class="text-base font-semibold text-gray-900"><%= t(".title") %></h1>
8
+ <p class="mt-1 text-sm text-slate-500"><%= t(".description") %></p>
9
+ </div>
10
+
11
+ <%= button_to t(".action.revoke_all"), moduler.profile_sessions_revocation_path, method: :delete,
12
+ 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",
13
+ form: { class: "shrink-0", data: { turbo_confirm: t(".confirmation.revoke") } } %>
14
+ </div>
15
+
16
+ <div class="flex-1">
17
+ <ul id="sessions" role="list" class="divide-y divide-slate-100 rounded-lg border border-slate-200 bg-white">
18
+ <%= render partial: "moduler/profile/sessions/session", collection: @sessions %>
19
+ </ul>
20
+ </div>
21
+
22
+ <div class="mt-4 flex justify-end">
23
+ <%= paginate @sessions %>
24
+ </div>
25
+ </div>
26
+ </main>
@@ -0,0 +1,31 @@
1
+ <div class="flex min-h-full">
2
+ <div class="flex flex-1 flex-col justify-center px-4 py-12 sm:px-6 lg:flex-none lg:px-20 xl:px-24">
3
+ <div class="mx-auto w-full max-w-sm lg:w-96">
4
+ <div>
5
+ <h2 class="mt-8 text-2xl/9 font-bold tracking-tight text-gray-900"><%= t(".title") %></h2>
6
+ <p class="mt-2 text-sm/6 text-gray-500">
7
+ <%= t(".subtitle") %>
8
+ <%= link_to t(".action.sign_up"), moduler.new_user_path, class: "font-semibold text-custom hover:text-custom" %>
9
+ </p>
10
+ </div>
11
+
12
+ <div class="mt-10">
13
+ <% flash.each do |type, message| %>
14
+ <div class="my-2 text-sm text-green-600">
15
+ <%= message %>
16
+ </div>
17
+ <% end %>
18
+
19
+ <div>
20
+ <%= form_for @user, url: moduler.session_path, class: "space-y-6", data: { turbo: false } do |form| %>
21
+ <%= render(::Moduler::Form::BaseErrorComponent.new(form)) %>
22
+
23
+ <%= render(::Moduler::Form::EmailComponent.new(form, :email_address, label: t(".label.email_address"), placeholder: t(".placeholder.email_address"))) %>
24
+ <%= render(::Moduler::Form::PasswordComponent.new(form, :password, label: t(".label.password"), placeholder: t(".placeholder.password"))) %>
25
+ <%= render(::Moduler::Form::SubmitComponent.new(form, text: t(".action.sign_in"))) %>
26
+ <% end %>
27
+ </div>
28
+ </div>
29
+ </div>
30
+ </div>
31
+ </div>
@@ -0,0 +1,26 @@
1
+ <div class="flex min-h-full">
2
+ <div class="flex flex-1 flex-col justify-center px-4 py-12 sm:px-6 lg:flex-none lg:px-20 xl:px-24">
3
+ <div class="mx-auto w-full max-w-sm lg:w-96">
4
+ <div>
5
+ <h2 class="mt-8 text-2xl/9 font-bold tracking-tight text-gray-900"><%= t(".title") %></h2>
6
+ <p class="mt-2 text-sm/6 text-gray-500">
7
+ <%= t(".subtitle") %>
8
+ <%= link_to t(".action.sign_in"), moduler.new_session_path, class: "font-semibold text-custom hover:text-custom" %>
9
+ </p>
10
+ </div>
11
+
12
+ <div class="mt-10">
13
+ <div>
14
+ <%= form_for @user, url: moduler.user_path, class: "space-y-6", data: { turbo: false } do |form| %>
15
+ <%= render(::Moduler::Form::BaseErrorComponent.new(form)) %>
16
+
17
+ <%= render(::Moduler::Form::EmailComponent.new(form, :email_address, label: t(".label.email_address"), placeholder: t(".placeholder.email_address"))) %>
18
+ <%= render(::Moduler::Form::PasswordComponent.new(form, :password, label: t(".label.password"), placeholder: t(".placeholder.password"), description: t(".description.password"))) %>
19
+ <%= render(::Moduler::Form::PasswordComponent.new(form, :password_confirmation, placeholder: t(".placeholder.password_confirmation"))) %>
20
+ <%= render(::Moduler::Form::SubmitComponent.new(form, text: t(".action.sign_up"))) %>
21
+ <% end %>
22
+ </div>
23
+ </div>
24
+ </div>
25
+ </div>
26
+ </div>
@@ -0,0 +1,5 @@
1
+ pin "moduler/application"
2
+ pin "@hotwired/turbo-rails", to: "turbo.min.js"
3
+ pin "@hotwired/stimulus", to: "stimulus.min.js"
4
+ pin "@hotwired/stimulus-loading", to: "stimulus-loading.js"
5
+ pin_all_from File.expand_path("../app/assets/javascripts/moduler/controllers", __dir__), under: "moduler/controllers"
@@ -0,0 +1,24 @@
1
+ en:
2
+ moduler:
3
+ administration:
4
+ sessions:
5
+ index:
6
+ title: Sessions
7
+ description: Sessions of all users. Search sessions by email address, IP address or device and revoke any of them.
8
+ empty: No sessions found.
9
+ destroy:
10
+ notice: Session has been revoked
11
+ session:
12
+ action:
13
+ destroy: Revoke
14
+ status:
15
+ active: Active
16
+ signed_out: Signed out
17
+ current: This device
18
+ signed_in: Signed in %{time} ago
19
+ signed_out: Signed out %{time} ago
20
+ unknown_ip: Unknown IP address
21
+ unknown_device: Unknown device
22
+ confirmation:
23
+ destroy: The user will be signed out of this session. Are you sure?
24
+ destroy_current: You will be signed out of this device. Are you sure?
@@ -0,0 +1,43 @@
1
+ en:
2
+ moduler:
3
+ administration:
4
+ users:
5
+ roles:
6
+ member: Member
7
+ administrator: Administrator
8
+ index:
9
+ title: Users
10
+ description: Manage user accounts and their roles. Search users by email address.
11
+ empty: No users found.
12
+ action:
13
+ new: Create
14
+ new:
15
+ title: New user
16
+ label:
17
+ email_address: Email address
18
+ role: Role
19
+ password: Password
20
+ description:
21
+ password: Use at least 8 characters.
22
+ section:
23
+ data: User data
24
+ password: Password
25
+ action:
26
+ create: Create
27
+ create:
28
+ notice: User has been created
29
+ edit:
30
+ title: Edit user
31
+ label:
32
+ email_address: Email address
33
+ role: Role
34
+ section:
35
+ data: User data
36
+ action:
37
+ save: Save
38
+ update:
39
+ notice: User has been updated
40
+ user:
41
+ action:
42
+ edit: Edit
43
+ created: Created %{time} ago
@@ -0,0 +1,13 @@
1
+ en:
2
+ moduler:
3
+ common:
4
+ sidebar:
5
+ action:
6
+ profile: Profile
7
+ sessions: Sessions
8
+ header:
9
+ nav:
10
+ administration: Administration
11
+ home: Home
12
+ action:
13
+ sign_out: Sign out
@@ -0,0 +1,11 @@
1
+ en:
2
+ moduler:
3
+ components:
4
+ filter:
5
+ placeholder:
6
+ query: Search...
7
+ action:
8
+ filter: Filter
9
+ clear: Clear
10
+ list:
11
+ empty: No results found.
@@ -0,0 +1,4 @@
1
+ en:
2
+ moduler:
3
+ flash:
4
+ not_authorized: You are not authorized to perform this action.
@@ -0,0 +1,26 @@
1
+ en:
2
+ moduler:
3
+ profile:
4
+ edit:
5
+ title: Profile
6
+ label:
7
+ email_address: Email address
8
+ password: New password
9
+ password_confirmation: Confirm new password
10
+ password_challenge: Current password
11
+ description:
12
+ password_confirmation: Leave blank to keep your current password. Changing it signs out your other active sessions.
13
+ password_challenge: Required to save changes to password.
14
+ action:
15
+ save: Save
16
+ section:
17
+ data: Your data
18
+ password: Change password
19
+ current_password: Confirm changes
20
+ sidebar:
21
+ profile: Profile
22
+ notifications: Notifications
23
+ security: Security
24
+ billing: Billing
25
+ update:
26
+ notice: Your profile has been updated
@@ -0,0 +1,25 @@
1
+ en:
2
+ moduler:
3
+ profile:
4
+ sessions:
5
+ index:
6
+ title: Sessions
7
+ description: Devices where you are signed in to your account. Revoke any session you don't recognize.
8
+ action:
9
+ revoke_all: Revoke all
10
+ confirmation:
11
+ revoke: You will be signed out of all sessions, including this one. Are you sure?
12
+ session:
13
+ action:
14
+ destroy: Revoke
15
+ status:
16
+ active: Active
17
+ signed_out: Signed out
18
+ current: This device
19
+ signed_in: Signed in %{time} ago
20
+ signed_out: Signed out %{time} ago
21
+ unknown_ip: Unknown IP address
22
+ unknown_device: Unknown device
23
+ confirmation:
24
+ destroy: Are you sure?
25
+ destroy_current: You will be signed out of this device. Are you sure?
@@ -0,0 +1,18 @@
1
+ en:
2
+ moduler:
3
+ sessions:
4
+ new:
5
+ title: Sign in to your account
6
+ subtitle: Enter your email and password to sign in.
7
+ action:
8
+ sign_in: Sign in
9
+ sign_up: Sign up
10
+ label:
11
+ email_address: Email address
12
+ password: Password
13
+ placeholder:
14
+ email_address: Enter your email address
15
+ password: Enter your password
16
+ create:
17
+ invalid_credentials: Invalid credentials
18
+ rate_limited: Too many sign-in attempts. Please try again in a few minutes.
@@ -0,0 +1,21 @@
1
+ en:
2
+ moduler:
3
+ users:
4
+ new:
5
+ title: "Create an account"
6
+ subtitle: "Already have an account?"
7
+ action:
8
+ sign_in: "Sign in"
9
+ sign_up: "Sign up"
10
+ label:
11
+ email_address: "Email address"
12
+ password: "Password"
13
+ placeholder:
14
+ email_address: "Email address"
15
+ password: "Password"
16
+ password_confirmation: "Confirm password"
17
+ description:
18
+ password: "Use at least 8 characters containing at least 1 number and 1 lowercase letter"
19
+ create:
20
+ success: "User created successfully."
21
+ rate_limited: "Too many sign-up attempts. Please try again in a few minutes."
data/config/routes.rb CHANGED
@@ -1,2 +1,21 @@
1
1
  Moduler::Engine.routes.draw do
2
+ resource :session, only: %i[new create destroy]
3
+ resource :user, only: %i[new create]
4
+
5
+ resource :profile, only: %i[ edit update ], controller: :profile
6
+ namespace :profile do
7
+ namespace :sessions do
8
+ resource :revocation, only: :destroy, controller: :revocation
9
+ end
10
+ resources :sessions, only: %i[ index destroy ]
11
+ end
12
+
13
+ namespace :administration do
14
+ resources :users, only: %i[ index new create edit update ]
15
+ resources :sessions, only: %i[ index destroy ]
16
+
17
+ root "home#index"
18
+ end
19
+
20
+ root "home#index"
2
21
  end
@@ -0,0 +1,12 @@
1
+ class CreateModulerUsers < ActiveRecord::Migration[8.1]
2
+ def change
3
+ create_table :moduler_users, id: :uuid do |t|
4
+ t.string :email_address, null: false
5
+ t.string :password_digest
6
+
7
+ t.timestamps null: false
8
+ end
9
+
10
+ add_index :moduler_users, :email_address, unique: true
11
+ end
12
+ end
@@ -0,0 +1,13 @@
1
+ class CreateModulerSessions < ActiveRecord::Migration[8.1]
2
+ def change
3
+ create_table :moduler_sessions, id: :uuid do |t|
4
+ t.references :moduler_user, null: false, foreign_key: true, type: :uuid
5
+ t.string :ip_address
6
+ t.string :user_agent
7
+ t.datetime :signed_in_at
8
+ t.datetime :signed_out_at
9
+
10
+ t.timestamps null: false
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,5 @@
1
+ class AddRoleToModulerUsers < ActiveRecord::Migration[8.1]
2
+ def change
3
+ add_column :moduler_users, :role, :integer, default: 0
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class EnablePgTrgm < ActiveRecord::Migration[8.1]
2
+ def change
3
+ enable_extension "pg_trgm"
4
+ end
5
+ end
@@ -0,0 +1,12 @@
1
+ module Moduler
2
+ class Configuration
3
+ extend Forwardable
4
+
5
+ def_delegator :@configuration, :[]
6
+ def_delegator :@configuration, :dig
7
+
8
+ def initialize
9
+ @configuration = Rails.application.config_for(:moduler)
10
+ end
11
+ end
12
+ end
@@ -1,5 +1,25 @@
1
+ require "importmap-rails"
2
+ require "stimulus-rails"
3
+ require "turbo-rails"
4
+ require "tailwindcss-rails"
5
+ require "kaminari"
6
+
1
7
  module Moduler
2
8
  class Engine < ::Rails::Engine
3
9
  isolate_namespace Moduler
10
+
11
+ initializer "moduler.importmap", before: "importmap" do |app|
12
+ app.config.importmap.paths += [ Engine.root.join("config/importmap.rb") ]
13
+ end
14
+
15
+ initializer "moduler.assets" do |app|
16
+ app.config.assets.precompile += %w[moduler/manifest.js]
17
+ end
18
+
19
+ initializer "moduler.routes" do
20
+ Engine.routes.append do
21
+ instance_exec(&Moduler.router.routes) if Moduler.router&.routes
22
+ end
23
+ end
4
24
  end
5
25
  end
@@ -0,0 +1,10 @@
1
+ module Moduler
2
+ class Router
3
+ def initialize(path, routes)
4
+ @path = path
5
+ @routes = routes
6
+ end
7
+
8
+ attr_reader :path, :routes
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ module Moduler
2
+ mattr_accessor :router
3
+
4
+ module Routing
5
+ def moduler(path, &block)
6
+ Moduler.router = Router.new(path, block)
7
+
8
+ mount Moduler::Engine => path, as: :moduler
9
+ end
10
+ end
11
+ end
@@ -1,3 +1,3 @@
1
1
  module Moduler
2
- VERSION = "0.1.0"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/moduler.rb CHANGED
@@ -1,6 +1,17 @@
1
+ require "view_component"
2
+ require "pg_search"
3
+ require "pundit"
4
+
1
5
  require "moduler/version"
2
6
  require "moduler/engine"
7
+ require "moduler/configuration"
8
+ require "moduler/router"
9
+ require "moduler/routing"
3
10
 
4
11
  module Moduler
5
- # Your code goes here...
12
+ def self.configuration
13
+ @configuration ||= Configuration.new
14
+ end
6
15
  end
16
+
17
+ ActionDispatch::Routing::Mapper.include Moduler::Routing
@@ -1,4 +1,42 @@
1
- # desc "Explaining what the task does"
2
- # task :moduler do
3
- # # Task goes here
4
- # end
1
+ require "tailwindcss-rails"
2
+
3
+ namespace :moduler do
4
+ namespace :tailwindcss do
5
+ desc "Build Tailwind CSS for Moduler Engine"
6
+ task :build do
7
+ input_path = "app/assets/stylesheets/moduler/tailwind.css"
8
+ output_path = "app/assets/builds/moduler/tailwind.css"
9
+
10
+ FileUtils.mkdir_p(Moduler::Engine.root.join("app/assets/builds/moduler"))
11
+
12
+ command = [
13
+ Tailwindcss::Ruby.executable,
14
+ "--input", input_path,
15
+ "--output", output_path
16
+ ]
17
+
18
+ Dir.chdir(Moduler::Engine.root) do
19
+ system(*command, exception: true)
20
+ end
21
+ end
22
+
23
+ desc "Watch and build Tailwind CSS for Moduler Engine"
24
+ task :watch do
25
+ input_path = "app/assets/stylesheets/moduler/tailwind.css"
26
+ output_path = "app/assets/builds/moduler/tailwind.css"
27
+
28
+ FileUtils.mkdir_p(Moduler::Engine.root.join("app/assets/builds/moduler"))
29
+
30
+ command = [
31
+ Tailwindcss::Ruby.executable,
32
+ "--input", input_path,
33
+ "--output", output_path,
34
+ "--watch"
35
+ ]
36
+
37
+ Dir.chdir(Moduler::Engine.root) do
38
+ system(*command, exception: true)
39
+ end
40
+ end
41
+ end
42
+ end