maquina 0.5.2 → 0.7.2

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 (94) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/Gemfile +2 -0
  4. data/Gemfile.lock +3 -35
  5. data/app/assets/javascripts/controllers/alert_controller.js +22 -0
  6. data/app/assets/stylesheets/maquina/application.tailwind.css +4 -101
  7. data/app/assets/stylesheets/maquina.css +114 -0
  8. data/app/controllers/concerns/maquina/authenticate.rb +29 -1
  9. data/app/controllers/concerns/maquina/index.rb +81 -2
  10. data/app/controllers/concerns/maquina/resourceful.rb +95 -9
  11. data/app/controllers/maquina/application_controller.rb +1 -1
  12. data/app/controllers/maquina/dashboard_controller.rb +10 -5
  13. data/app/controllers/maquina/invitations_controller.rb +0 -2
  14. data/app/controllers/maquina/plans_controller.rb +13 -23
  15. data/app/controllers/maquina/sessions_controller.rb +1 -1
  16. data/app/controllers/maquina/users_controller.rb +0 -2
  17. data/app/helpers/maquina/application_helper.rb +0 -8
  18. data/app/helpers/maquina/navbar_menu_helper.rb +1 -1
  19. data/app/models/concerns/maquina/blockeable.rb +64 -0
  20. data/app/models/concerns/maquina/organization_scoped.rb +26 -0
  21. data/app/models/concerns/maquina/retain_passwords.rb +44 -0
  22. data/app/models/concerns/maquina/sqlite_search.rb +92 -0
  23. data/app/models/concerns/maquina/user_scoped.rb +26 -0
  24. data/app/models/maquina/active_session.rb +40 -0
  25. data/app/models/maquina/current.rb +39 -2
  26. data/app/models/maquina/invitation.rb +28 -0
  27. data/app/models/maquina/membership.rb +23 -1
  28. data/app/models/maquina/organization.rb +19 -2
  29. data/app/models/maquina/plan.rb +26 -6
  30. data/app/models/maquina/used_password.rb +30 -0
  31. data/app/models/maquina/user.rb +50 -8
  32. data/app/policies/maquina/application_policy.rb +1 -1
  33. data/app/policies/maquina/dashboard_policy.rb +7 -0
  34. data/app/policies/maquina/navigation_policy.rb +2 -0
  35. data/app/views/layouts/maquina/application.html.erb +2 -2
  36. data/app/views/layouts/maquina/sessions.html.erb +1 -1
  37. data/app/views/maquina/application/_navbar.html.erb +8 -3
  38. data/app/views/maquina/application/components/checkbox_component.rb +3 -3
  39. data/app/views/maquina/application/components/component_base.rb +3 -2
  40. data/app/views/maquina/application/edit.html.erb +10 -7
  41. data/app/views/maquina/application/filters.rb +118 -0
  42. data/app/views/maquina/application/index.html.erb +6 -3
  43. data/app/views/maquina/application/index_header.rb +5 -2
  44. data/app/views/maquina/application/index_table.rb +71 -6
  45. data/app/views/maquina/application/index_tools.rb +17 -0
  46. data/app/views/maquina/application/new.html.erb +10 -7
  47. data/app/views/maquina/application/search.rb +42 -0
  48. data/app/views/maquina/application/sessions_header.rb +3 -9
  49. data/app/views/maquina/dashboard/index.html.erb +4 -0
  50. data/app/views/maquina/dashboard/stats.rb +35 -0
  51. data/app/views/maquina/dashboard/tasks.rb +124 -0
  52. data/app/views/maquina/first_runs/form.rb +0 -2
  53. data/app/views/maquina/first_runs/show.html.erb +4 -1
  54. data/app/views/maquina/navbar/title.rb +4 -2
  55. data/config/importmap.rb +1 -13
  56. data/config/locales/flash.en.yml +6 -0
  57. data/config/locales/flash.es.yml +6 -0
  58. data/config/locales/forms.en.yml +33 -4
  59. data/config/locales/forms.es.yml +22 -11
  60. data/config/locales/models.en.yml +10 -0
  61. data/config/locales/models.es.yml +10 -0
  62. data/config/locales/views.en.yml +33 -5
  63. data/config/locales/views.es.yml +28 -10
  64. data/config/routes.rb +1 -0
  65. data/db/migrate/20221109010726_create_maquina_plans.rb +1 -1
  66. data/db/migrate/20221113000409_create_maquina_users.rb +1 -1
  67. data/db/migrate/20221113020108_create_maquina_used_passwords.rb +1 -1
  68. data/db/migrate/20221115223414_create_maquina_active_sessions.rb +1 -3
  69. data/db/migrate/20230201203922_create_maquina_invitations.rb +1 -1
  70. data/db/migrate/20230829183530_create_maquina_organizations.rb +1 -1
  71. data/db/migrate/20230829192656_create_maquina_memberships.rb +2 -4
  72. data/db/migrate/20241109191405_add_counter_cache_to_plans.rb +5 -0
  73. data/lib/generators/maquina/install_generator.rb +67 -1
  74. data/lib/generators/maquina/tailwind_config/templates/lib/generators/tailwind_config/templates/config/tailwind.config.js.tt +9 -5
  75. data/lib/generators/maquina/tailwind_config/templates/lib/tasks/tailwind.rake.tt +2 -0
  76. data/lib/generators/maquina/templates/config/initializers/maquina.rb.tt +2 -1
  77. data/lib/maquina/engine.rb +6 -3
  78. data/lib/maquina/version.rb +1 -1
  79. data/lib/maquina.rb +2 -9
  80. metadata +20 -76
  81. data/app/assets/javascripts/maquina/application.js +0 -4
  82. data/app/assets/javascripts/maquina/controllers/alert_controller.js +0 -29
  83. data/app/assets/javascripts/maquina/controllers/application.js +0 -9
  84. data/app/assets/javascripts/maquina/controllers/index.js +0 -11
  85. data/app/models/concerns/maquina/authenticate_by.rb +0 -33
  86. data/app/views/maquina/navbar/search.rb +0 -40
  87. data/lib/generators/maquina/install_templates/install_templates_generator.rb +0 -31
  88. /data/app/assets/javascripts/{maquina/controllers → controllers}/backdrop_controller.js +0 -0
  89. /data/app/assets/javascripts/{maquina/controllers → controllers}/file_controller.js +0 -0
  90. /data/app/assets/javascripts/{maquina/controllers → controllers}/mobile_menu_controller.js +0 -0
  91. /data/app/assets/javascripts/{maquina/controllers → controllers}/modal_controller.js +0 -0
  92. /data/app/assets/javascripts/{maquina/controllers → controllers}/modal_open_controller.js +0 -0
  93. /data/app/assets/javascripts/{maquina/controllers → controllers}/popup_menu_controller.js +0 -0
  94. /data/app/assets/javascripts/{maquina/controllers → controllers}/submit_form_controller.js +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f39a0b2ab151429d764ab650eeb2000b0d7f273f2d8db62432eb331f46ae5609
4
- data.tar.gz: ff0518eb8328e885ef95245e3faad7fecf7f7a69637c00f967a2efb7d0a34d2f
3
+ metadata.gz: 4b1650769946b608a9ed3993d2aeae3107961d2fdf57400f1db5a6a3b66cd10a
4
+ data.tar.gz: 8dada0a36f41e6c3d8c23c830c2456ff7eeac792221d8a0a6163bdd645865765
5
5
  SHA512:
6
- metadata.gz: 27a55a0eefda11374168612994a6fdf4b2f13c9264483aadc894c77b881231a8ac84ff5594698e30fedb5aca57b3a221a0b46c0b2f560463f28538555786ae74
7
- data.tar.gz: 591238b607bf082ff2247ff83391cbfac9738827dd3043e1c8958b1812394dff383556569d15cc11b0c381e2f3f862bd642191f57e1e3ffd8cfe605898e81372
6
+ metadata.gz: b573303faa3ffa46a65ed9ac71c7ec8f80a034e49f50ff2fdb6c234289c705472e33118ee5523889fa8216b76db0a8c5034f1dd0b76719631d3b67d94dd0dc12
7
+ data.tar.gz: 1748332d65ea73bf86e696b6dd3b52b4534dbf9eb9a05ae12c66bd75d2e64387b73ab9f8347d05309053803740924cace533c4fb49e16bd68d7b78ab201f7b00
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.3.4
1
+ 3.3.5
data/Gemfile CHANGED
@@ -10,7 +10,9 @@ gem "pg_search"
10
10
  gem "puma"
11
11
  gem "propshaft"
12
12
  gem "tailwindcss-rails"
13
+ gem "importmap-rails"
13
14
  gem "turbo-rails"
15
+ gem "stimulus-rails"
14
16
 
15
17
  # Start debugger with binding.b [https://github.com/ruby/debug]
16
18
  #
data/Gemfile.lock CHANGED
@@ -1,18 +1,15 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- maquina (0.5.2)
4
+ maquina (0.7.2)
5
5
  action_policy (>= 0.6.0)
6
6
  bcrypt (>= 3.1.7)
7
- importmap-rails (>= 2.0.0)
8
7
  money-rails (>= 1.15.0)
9
8
  pagy (>= 6.2)
10
9
  phlex-rails (>= 1.1.0)
11
10
  rails (>= 7.0.4)
12
11
  rotp (>= 6.2.1)
13
- stimulus-rails (>= 1.3.0)
14
12
  tailwindcss-rails (>= 2.1.0)
15
- turbo-rails (>= 2.0.0)
16
13
 
17
14
  GEM
18
15
  remote: https://rubygems.org/
@@ -92,10 +89,8 @@ GEM
92
89
  addressable (2.8.7)
93
90
  public_suffix (>= 2.0.2, < 7.0)
94
91
  ast (2.4.2)
95
- backport (1.2.0)
96
92
  base64 (0.2.0)
97
93
  bcrypt (3.1.20)
98
- benchmark (0.3.0)
99
94
  bigdecimal (3.1.8)
100
95
  brakeman (6.2.1)
101
96
  racc
@@ -114,9 +109,7 @@ GEM
114
109
  debug (1.9.2)
115
110
  irb (~> 1.10)
116
111
  reline (>= 0.3.8)
117
- diff-lcs (1.5.1)
118
112
  drb (2.2.1)
119
- e2mmap (0.1.0)
120
113
  erubi (1.13.0)
121
114
  globalid (1.2.1)
122
115
  activesupport (>= 6.1)
@@ -130,12 +123,7 @@ GEM
130
123
  irb (1.14.0)
131
124
  rdoc (>= 4.0.0)
132
125
  reline (>= 0.4.2)
133
- jaro_winkler (1.6.0)
134
126
  json (2.7.2)
135
- kramdown (2.4.0)
136
- rexml
137
- kramdown-parser-gfm (1.1.0)
138
- kramdown (~> 2.0)
139
127
  language_server-protocol (3.17.0.3)
140
128
  launchy (3.0.1)
141
129
  addressable (~> 2.8)
@@ -239,14 +227,11 @@ GEM
239
227
  zeitwerk (~> 2.6)
240
228
  rainbow (3.1.1)
241
229
  rake (13.2.1)
242
- rbs (2.8.4)
243
230
  rdoc (6.7.0)
244
231
  psych (>= 4.0.0)
245
232
  regexp_parser (2.9.2)
246
233
  reline (0.5.10)
247
234
  io-console (~> 0.5)
248
- reverse_markdown (2.1.1)
249
- nokogiri
250
235
  rexml (3.3.7)
251
236
  rotp (6.3.0)
252
237
  rubocop (1.65.1)
@@ -273,22 +258,6 @@ GEM
273
258
  ruby-next-core (1.0.3)
274
259
  ruby-progressbar (1.13.0)
275
260
  securerandom (0.3.1)
276
- solargraph (0.50.0)
277
- backport (~> 1.2)
278
- benchmark
279
- bundler (~> 2.0)
280
- diff-lcs (~> 1.4)
281
- e2mmap
282
- jaro_winkler (~> 1.5)
283
- kramdown (~> 2.3)
284
- kramdown-parser-gfm (~> 1.1)
285
- parser (~> 3.0)
286
- rbs (~> 2.0)
287
- reverse_markdown (~> 2.0)
288
- rubocop (~> 1.38)
289
- thor (~> 1.0)
290
- tilt (~> 2.0)
291
- yard (~> 0.9, >= 0.9.24)
292
261
  standard (1.40.0)
293
262
  language_server-protocol (~> 3.17.0.2)
294
263
  lint_roller (~> 1.0)
@@ -310,7 +279,6 @@ GEM
310
279
  tailwindcss-rails (2.7.3-arm64-darwin)
311
280
  railties (>= 7.0.0)
312
281
  thor (1.3.2)
313
- tilt (2.4.0)
314
282
  timeout (0.4.1)
315
283
  turbo-rails (2.0.6)
316
284
  actionpack (>= 6.0.0)
@@ -324,7 +292,6 @@ GEM
324
292
  websocket-driver (0.7.6)
325
293
  websocket-extensions (>= 0.1.0)
326
294
  websocket-extensions (0.1.5)
327
- yard (0.9.37)
328
295
  zeitwerk (2.6.18)
329
296
 
330
297
  PLATFORMS
@@ -335,6 +302,7 @@ DEPENDENCIES
335
302
  brakeman
336
303
  bundle-audit
337
304
  debug (>= 1.0.0)
305
+ importmap-rails
338
306
  irb
339
307
  letter_opener
340
308
  maquina!
@@ -342,9 +310,9 @@ DEPENDENCIES
342
310
  pg_search
343
311
  propshaft
344
312
  puma
345
- solargraph
346
313
  standard
347
314
  standard-rails
315
+ stimulus-rails
348
316
  tailwindcss-rails
349
317
  turbo-rails
350
318
 
@@ -0,0 +1,22 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+ import { enter, leave } from "el-transition"
3
+
4
+ /**
5
+ * Alert Controller
6
+ *
7
+ * Displays Rails flash messages with animation using el-transition.
8
+ * Auto-dismisses after 10 seconds or manual close.
9
+ */
10
+ export default class extends Controller {
11
+ static targets = ["alert"];
12
+
13
+ connect() {
14
+ enter(this.alertTarget).then(() => {
15
+ setTimeout(this.close.bind(this), 10000)
16
+ })
17
+ }
18
+
19
+ close() {
20
+ leave(this.alertTarget)
21
+ }
22
+ }
@@ -1,101 +1,4 @@
1
- @tailwind base;
2
- @tailwind components;
3
- @tailwind utilities;
4
-
5
- /* @layer base { */
6
- :root {
7
- --color-base: 31 27 54;
8
- --color-accented: 37 99 235;
9
- --color-inverted: 255 255 255;
10
- --color-accented-hover: 59 130 246;
11
- --color-muted: 55 65 81;
12
- --color-dimmed: 75 85 99;
13
- --color-error: 220 38 38;
14
-
15
- --color-border-base: 209 213 219;
16
- --color-border-accented: 37 99 235;
17
- }
18
-
19
- .label {
20
- @apply text-sm font-medium text-skin-muted;
21
- }
22
-
23
- .input {
24
- @apply appearance-none rounded-md border border-skin-base px-3 py-2 placeholder-gray-400 shadow-sm focus:border-skin-accented focus:outline-none focus:ring-skin-accented sm:text-sm;
25
- }
26
-
27
- .link {
28
- @apply font-medium text-skin-accented hover:text-skin-accented-hover;
29
- }
30
-
31
- .check {
32
- @apply border-skin-base h-4 w-4 text-skin-accented focus:ring-skin-accented rounded;
33
- }
34
-
35
- .button {
36
- @apply inline-flex justify-center py-2 px-4 border border-skin-base rounded-md shadow-sm text-sm font-medium text-skin-muted bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-skin-accented;
37
- }
38
-
39
- .control-error .label {
40
- @apply text-skin-error;
41
- }
42
-
43
- .control-error .input {
44
- @apply border-skin-error focus:border-skin-error focus:ring-skin-error;
45
- }
46
-
47
- .help {
48
- @apply mt-2 text-sm text-skin-dimmed;
49
- }
50
-
51
- .help-error {
52
- @apply text-skin-error;
53
- }
54
-
55
- .button[disabled] {
56
- @apply bg-gray-50 text-skin-dimmed cursor-default;
57
- }
58
-
59
- .button-accented {
60
- @apply rounded-md border border-transparent bg-skin-accented py-2 px-4 text-sm font-medium text-skin-inverted shadow-sm hover:bg-skin-accented-hover focus:outline-none focus:ring-2 focus:ring-skin-accented focus:ring-offset-2;
61
- }
62
-
63
- input[type="search"] {
64
- @apply block w-full rounded-md border border-skin-base pl-10 leading-5 placeholder-gray-400 focus:placeholder-gray-300 focus:outline-none focus:ring-1 focus:ring-skin-accented focus:border-skin-accented sm:text-sm;
65
- }
66
-
67
- .desktop-menu {
68
- @apply hidden lg:ml-6 lg:flex lg:space-x-8;
69
- }
70
-
71
- .desktop-menu-item {
72
- @apply font-sans uppercase border-transparent text-skin-dimmed underline-offset-4 decoration-2 hover:underline hover:decoration-skin-accented hover:text-skin-muted inline-flex items-center px-1 pt-1 text-sm font-medium;
73
- }
74
-
75
- .desktop-menu-item__active {
76
- @apply decoration-skin-accented underline text-skin-base;
77
- }
78
-
79
- .mobile-menu {
80
- @apply pt-2 pb-3 space-y-1;
81
- }
82
-
83
- .mobile-menu-item {
84
- @apply font-sans border-transparent text-skin-dimmed hover:bg-gray-200 hover:border-gray-300 hover:text-skin-muted block pl-3 pr-4 py-2 border-l-4 text-base font-medium;
85
- }
86
-
87
- .mobile-menu-item__active {
88
- @apply bg-skin-accented-hover border-skin-accented text-skin-accented hover:text-skin-accented hover:bg-skin-accented-hover hover:border-skin-accented;
89
- }
90
-
91
- .desktop-profile-menu-item {
92
- @apply block w-full text-left font-sans px-4 py-2 text-sm text-skin-dimmed hover:bg-gray-200 hover:text-skin-muted;
93
- }
94
-
95
- .mobile-profile-menu-item {
96
- @apply block font-sans px-4 py-2 text-base font-medium text-skin-dimmed hover:text-skin-muted hover:bg-gray-200;
97
- }
98
-
99
- .mobile-button {
100
- @apply inline-flex items-center justify-center p-2 rounded-md text-skin-dimmed hover:text-skin-muted hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-skin-accented;
101
- }
1
+ @import "tailwindcss/base";
2
+ @import "../maquina.css";
3
+ @import "tailwindcss/components";
4
+ @import "tailwindcss/utilities";
@@ -0,0 +1,114 @@
1
+ /* Base maquina styles */
2
+
3
+ :root {
4
+ --color-base: 31 27 54;
5
+ --color-accented: 37 99 235;
6
+ --color-inverted: 255 255 255;
7
+ --color-accented-hover: 59 130 246;
8
+ --color-muted: 55 65 81;
9
+ --color-dimmed: 75 85 99;
10
+ --color-error: 220 38 38;
11
+
12
+ --color-alternate: 219 90 186;
13
+ --color-alternate-1: 198 218 191;
14
+ --color-alternate-2: 23 24 59;
15
+
16
+ --color-border-base: 209 213 219;
17
+ --color-border-accented: 37 99 235;
18
+ }
19
+
20
+ .label {
21
+ @apply text-sm font-medium text-skin-muted;
22
+ }
23
+
24
+ .input {
25
+ @apply appearance-none rounded-md border border-skin-base px-3 py-2 placeholder-gray-400 shadow-sm focus:border-skin-accented focus:outline-none focus:ring-skin-accented sm:text-sm;
26
+ }
27
+
28
+ .link {
29
+ @apply font-medium text-skin-accented hover:text-skin-accented-hover;
30
+ }
31
+
32
+ .check {
33
+ @apply border-skin-base h-4 w-4 text-skin-accented focus:ring-skin-accented rounded;
34
+ }
35
+
36
+ .button {
37
+ @apply inline-flex justify-center py-2 px-4 border border-skin-base rounded-md shadow-sm text-sm font-medium text-skin-muted bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-skin-accented;
38
+ }
39
+
40
+ .select {
41
+ @apply block w-full rounded-md border-0 py-2 pl-3 pr-10 text-skin-base ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-skin-accented sm:text-sm/6;
42
+ }
43
+
44
+ .control-error .label {
45
+ @apply text-skin-error;
46
+ }
47
+
48
+ .control-error .input {
49
+ @apply border-skin-error focus:border-skin-error focus:ring-skin-error;
50
+ }
51
+
52
+ .filter-option {
53
+ @apply mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-gray-400 opacity-50 text-white;
54
+ }
55
+
56
+ .filter-option[selected] {
57
+ @apply bg-skin-accented text-white border-0 opacity-100;
58
+ }
59
+
60
+ .help {
61
+ @apply mt-2 text-sm text-skin-dimmed;
62
+ }
63
+
64
+ .help-error {
65
+ @apply text-skin-error;
66
+ }
67
+
68
+ .button[disabled] {
69
+ @apply bg-gray-50 text-skin-dimmed cursor-default;
70
+ }
71
+
72
+ .button-accented {
73
+ @apply rounded-md border border-transparent bg-skin-accented py-2 px-4 text-sm font-medium text-skin-inverted shadow-sm hover:bg-skin-accented-hover focus:outline-none focus:ring-2 focus:ring-skin-accented focus:ring-offset-2;
74
+ }
75
+
76
+ input[type="search"] {
77
+ @apply block w-full rounded-md border border-skin-base pl-10 leading-5 placeholder-gray-400 focus:placeholder-gray-300 focus:outline-none focus:ring-1 focus:ring-skin-accented focus:border-skin-accented sm:text-sm;
78
+ }
79
+
80
+ .desktop-menu {
81
+ @apply hidden lg:ml-6 lg:flex lg:space-x-8;
82
+ }
83
+
84
+ .desktop-menu-item {
85
+ @apply font-sans uppercase border-transparent text-skin-dimmed underline-offset-4 decoration-2 hover:underline hover:decoration-skin-accented hover:text-skin-muted inline-flex items-center px-1 pt-1 text-sm font-medium;
86
+ }
87
+
88
+ .desktop-menu-item__active {
89
+ @apply decoration-skin-accented underline text-skin-base;
90
+ }
91
+
92
+ .mobile-menu {
93
+ @apply pt-2 pb-3 space-y-1;
94
+ }
95
+
96
+ .mobile-menu-item {
97
+ @apply font-sans border-transparent text-skin-dimmed hover:bg-gray-200 hover:border-gray-300 hover:text-skin-muted block pl-3 pr-4 py-2 border-l-4 text-base font-medium;
98
+ }
99
+
100
+ .mobile-menu-item__active {
101
+ @apply bg-skin-accented-hover border-skin-accented text-skin-accented hover:text-skin-accented hover:bg-skin-accented-hover hover:border-skin-accented;
102
+ }
103
+
104
+ .desktop-profile-menu-item {
105
+ @apply block w-full text-left font-sans px-4 py-2 text-sm text-skin-dimmed hover:bg-gray-200 hover:text-skin-muted;
106
+ }
107
+
108
+ .mobile-profile-menu-item {
109
+ @apply block font-sans px-4 py-2 text-base font-medium text-skin-dimmed hover:text-skin-muted hover:bg-gray-200;
110
+ }
111
+
112
+ .mobile-button {
113
+ @apply inline-flex items-center justify-center p-2 rounded-md text-skin-dimmed hover:text-skin-muted hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-skin-accented;
114
+ }
@@ -1,6 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Maquina
4
+ ##
5
+ # A Rails controller concern that handles user authentication.
6
+ # Provides methods for checking authentication status, managing sessions,
7
+ # and controlling access to actions.
8
+ #
9
+ # == Usage
10
+ #
11
+ # class ApplicationController < ActionController::Base
12
+ # include Maquina::Authenticate
13
+ # end
14
+ #
4
15
  module Authenticate
5
16
  extend ActiveSupport::Concern
6
17
 
@@ -10,24 +21,41 @@ module Maquina
10
21
  end
11
22
 
12
23
  class_methods do
24
+ ##
25
+ # Skips authentication requirement for specified actions
13
26
  def allow_unauthenticated_access(...)
14
27
  skip_before_action(:require_authentication, ...)
15
28
  end
16
29
  end
17
30
 
31
+ ##
32
+ # Returns whether there is a signed in user
33
+ #
34
+ # authenticated? # => true
18
35
  def authenticated?
19
36
  Maquina::Current.signed_in?
20
37
  end
21
38
  alias_method :signed_in?, :authenticated?
22
39
 
40
+ ##
41
+ # Returns the currently signed in user or nil
42
+ #
43
+ # current_user # => #<User id: 1, email: "user@example.com">
23
44
  def current_user
24
45
  Maquina::Current.user
25
46
  end
26
47
 
48
+ ##
49
+ # Returns the URL to redirect to after successful authentication
50
+ #
51
+ # after_authentication_url # => "http://example.com/dashboard"
27
52
  def after_authentication_url
28
53
  session.delete(:return_to_after_authenticating) || root_url
29
54
  end
30
55
 
56
+ ##
57
+ # Ensures user is authenticated before proceeding.
58
+ # Redirects to login if no valid session exists.
31
59
  def require_authentication
32
60
  load_session || request_authentication
33
61
  end
@@ -57,7 +85,7 @@ module Maquina
57
85
  )
58
86
  .find_by(id: active_session_id)
59
87
 
60
- return if active_session.blank?
88
+ return if active_session.blank? || active_session.expired?
61
89
 
62
90
  Maquina::Current.active_session = active_session
63
91
  end
@@ -1,6 +1,34 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Maquina
4
+ ##
5
+ # The Index concern provides standardized index action functionality for Rails controllers.
6
+ # It includes pagination, authorization, searching, and sorting capabilities.
7
+ #
8
+ # === Features
9
+ # * Automatic pagination using Pagy
10
+ # * Policy-based authorization support
11
+ # * Full-text search integration (when model supports it)
12
+ # * Default sorting by updated_at
13
+ # * HTML and JSON response formats
14
+ # * Customizable scope through block parameter
15
+ #
16
+ # === Dependencies
17
+ # * {Pagy::Backend}[https://ddnexus.github.io/pagy/api/backend]
18
+ # * Policy class (optional)
19
+ # * Model must respond to +full_search+ if searching is used
20
+ #
21
+ # === Usage
22
+ # class UsersController < ApplicationController
23
+ # include Maquina::Index
24
+ #
25
+ # def index
26
+ # index! do |scope|
27
+ # scope.includes(:organization)
28
+ # end
29
+ # end
30
+ # end
31
+ #
4
32
  module Index
5
33
  extend ActiveSupport::Concern
6
34
  include Pagy::Backend
@@ -10,11 +38,13 @@ module Maquina
10
38
  authorize! with: policy_class if policy_class.present?
11
39
 
12
40
  @collection ||= begin
13
- scope = resource_class.order(updated_at: :desc)
41
+ scope = order_by(resource_class)
14
42
  scope = authorized_scope(scope) if policy_class.present?
15
43
 
16
44
  search_value = params[:q]&.strip
17
- scope = scope.search_full(search_value) if search_value.present? && resource_class.searchable?
45
+ scope = scope.full_search(search_value) if search_value.present? && resource_class.searchable?
46
+
47
+ scope = filter_by(scope) if filters.any?
18
48
 
19
49
  scope = yield(scope) if block.present?
20
50
 
@@ -29,5 +59,54 @@ module Maquina
29
59
  end
30
60
  alias_method :index!, :index
31
61
  end
62
+
63
+ protected
64
+
65
+ ##
66
+ # Applies query parameters as filters to the given scope
67
+ #
68
+ # scope:: The base ActiveRecord::Relation to filter
69
+ # Returns:: The filtered ActiveRecord::Relation
70
+ #
71
+ def filter_by(scope)
72
+ filters.each_pair do |field, options|
73
+ if params[field].present?
74
+ scope = scope.where(field => params[field])
75
+ end
76
+ end
77
+
78
+ scope
79
+ end
80
+
81
+ ##
82
+ # Orders records based on order and direction parameters
83
+ #
84
+ # Returns scope ordered by updated_at desc if:
85
+ # * order parameter is blank
86
+ # * order attribute is not in sortable list
87
+ # * direction is not asc/desc
88
+ #
89
+ # scope:: The base ActiveRecord::Relation to order
90
+ # Returns:: The ordered ActiveRecord::Relation
91
+ #
92
+ def order_by(scope)
93
+ direction = ["asc", "desc"].include?(params[:dir]) ? params[:dir] : nil
94
+ attribute = params[:order]&.to_sym
95
+
96
+ return scope.order(updated_at: :desc) if attribute.blank? || !sortable.include?(attribute) || direction.blank?
97
+
98
+ attribute, direction = before_order_by(attribute, direction)
99
+ scope.order(attribute => direction)
100
+ end
101
+
102
+ # Hook method called before applying sort order. Override in controller to customize ordering behavior.
103
+ #
104
+ # attribute:: The Symbol attribute to sort by
105
+ # direction:: The String sort direction ("asc" or "desc")
106
+ # Returns:: Array containing the modified attribute and direction
107
+ #
108
+ def before_order_by(attribute, direction)
109
+ [attribute, direction]
110
+ end
32
111
  end
33
112
  end