iron-cms 0.15.0 → 0.16.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/iron.css +97 -68
- data/app/assets/tailwind/iron/components/card.css +28 -26
- data/app/assets/tailwind/iron/components/selection-list.css +4 -0
- data/app/controllers/concerns/iron/api/cursor_pagination.rb +51 -0
- data/app/controllers/concerns/iron/api/locale_resolution.rb +23 -0
- data/app/controllers/concerns/iron/api/token_authentication.rb +29 -0
- data/app/controllers/concerns/iron/authorization.rb +4 -0
- data/app/controllers/iron/api/base_controller.rb +18 -0
- data/app/controllers/iron/api/content_controller.rb +75 -0
- data/app/controllers/iron/api/openapi_controller.rb +22 -0
- data/app/controllers/iron/api/search_controller.rb +15 -0
- data/app/controllers/iron/api/uploads_controller.rb +17 -0
- data/app/controllers/iron/passwords_controller.rb +8 -7
- data/app/controllers/iron/sessions/transfers_controller.rb +4 -2
- data/app/controllers/iron/sessions_controller.rb +4 -2
- data/app/controllers/iron/settings/integrations_controller.rb +34 -0
- data/app/controllers/iron/users/emails_controller.rb +5 -1
- data/app/controllers/iron/users/passwords_controller.rb +1 -1
- data/app/controllers/iron/users_controller.rb +9 -4
- data/app/mailers/iron/passwords_mailer.rb +2 -1
- data/app/models/iron/api/openapi_spec.rb +426 -0
- data/app/models/iron/current.rb +8 -0
- data/app/models/iron/entry/content_assignable.rb +53 -0
- data/app/models/iron/entry/deep_validation.rb +5 -1
- data/app/models/iron/entry/presentable.rb +1 -4
- data/app/models/iron/entry.rb +1 -1
- data/app/models/iron/field.rb +27 -1
- data/app/models/iron/field_definition.rb +3 -1
- data/app/models/iron/fields/block.rb +24 -12
- data/app/models/iron/fields/block_list.rb +48 -7
- data/app/models/iron/fields/boolean.rb +4 -10
- data/app/models/iron/fields/date.rb +11 -10
- data/app/models/iron/fields/file.rb +11 -8
- data/app/models/iron/fields/number.rb +4 -10
- data/app/models/iron/fields/reference.rb +22 -5
- data/app/models/iron/fields/reference_list.rb +22 -11
- data/app/models/iron/fields/rich_text_area.rb +4 -24
- data/app/models/iron/fields/text_area.rb +4 -10
- data/app/models/iron/fields/text_field.rb +4 -8
- data/app/models/iron/first_run.rb +13 -3
- data/app/models/iron/integration.rb +15 -0
- data/app/models/iron/{user → person}/transferable.rb +1 -1
- data/app/models/iron/person.rb +19 -0
- data/app/models/iron/seed.rb +11 -5
- data/app/models/iron/user/role.rb +7 -1
- data/app/models/iron/user.rb +10 -14
- data/app/views/iron/api/_entry.json.jbuilder +25 -0
- data/app/views/iron/api/_pagination.json.jbuilder +3 -0
- data/app/views/iron/api/content/index.json.jbuilder +4 -0
- data/app/views/iron/api/content/show.json.jbuilder +1 -0
- data/app/views/iron/api/fields/_block.json.jbuilder +19 -0
- data/app/views/iron/api/fields/_block_list.json.jbuilder +21 -0
- data/app/views/iron/api/fields/_boolean.json.jbuilder +1 -0
- data/app/views/iron/api/fields/_date.json.jbuilder +1 -0
- data/app/views/iron/api/fields/_file.json.jbuilder +12 -0
- data/app/views/iron/api/fields/_number.json.jbuilder +1 -0
- data/app/views/iron/api/fields/_reference.json.jbuilder +8 -0
- data/app/views/iron/api/fields/_reference_list.json.jbuilder +6 -0
- data/app/views/iron/api/fields/_rich_text_area.json.jbuilder +4 -0
- data/app/views/iron/api/fields/_text_area.json.jbuilder +1 -0
- data/app/views/iron/api/fields/_text_field.json.jbuilder +1 -0
- data/app/views/iron/api/search/show.json.jbuilder +4 -0
- data/app/views/iron/passwords_mailer/reset.html.erb +1 -1
- data/app/views/iron/passwords_mailer/reset.text.erb +1 -1
- data/app/views/iron/settings/integrations/_integration.html.erb +29 -0
- data/app/views/iron/settings/integrations/_new_integration_dialog.html.erb +43 -0
- data/app/views/iron/settings/integrations/index.html.erb +63 -0
- data/app/views/iron/settings/show.html.erb +18 -0
- data/app/views/iron/users/_change_role_dialog.html.erb +1 -1
- data/app/views/iron/users/_transfer.html.erb +1 -1
- data/app/views/iron/users/_user.html.erb +1 -1
- data/app/views/iron/users/index.html.erb +1 -1
- data/app/views/iron/users/show.html.erb +3 -3
- data/config/locales/en.yml +41 -0
- data/config/locales/it.yml +41 -0
- data/config/routes.rb +15 -0
- data/db/migrate/20260215222130_create_iron_people.rb +11 -0
- data/db/migrate/20260215222227_add_authenticatable_to_iron_users.rb +33 -0
- data/db/migrate/20260215222735_remove_legacy_auth_from_iron_users.rb +7 -0
- data/db/migrate/20260221231832_create_iron_integrations.rb +14 -0
- data/lib/iron/engine.rb +1 -0
- data/lib/iron/version.rb +1 -1
- data/lib/tasks/iron_tasks.rake +23 -0
- metadata +52 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 41a7997fe4c0ef674f5911df87d2f987b0b279b0686c7387b6fad582725e6829
|
|
4
|
+
data.tar.gz: dba2202a13aaeb1e16aff5294d03e91ed9adbda8d531ef69fa9bb5365a70aaee
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: db4d8aea62d74f936d53b466966e64990a7b1712c30fc86427e25535a8b631dfdd39d942a82573dc58e5a862add0a6e42ea09fb7b365a5b35919b1c9ad959618
|
|
7
|
+
data.tar.gz: 77dbdc5182e3935f751337f7a598ae2478901842585bc68385b58206eda8311d8bc3c6b63f11f5a84bd4c5cd54b0611125fd0faa279f44c2cda060e14adc8af6
|
data/app/assets/builds/iron.css
CHANGED
|
@@ -2958,6 +2958,9 @@
|
|
|
2958
2958
|
}
|
|
2959
2959
|
}
|
|
2960
2960
|
}
|
|
2961
|
+
.bg-amber-100 {
|
|
2962
|
+
background-color: var(--color-amber-100);
|
|
2963
|
+
}
|
|
2961
2964
|
.bg-gray-100 {
|
|
2962
2965
|
background-color: var(--color-gray-100);
|
|
2963
2966
|
}
|
|
@@ -3341,6 +3344,9 @@
|
|
|
3341
3344
|
--tw-tracking: var(--tracking-widest);
|
|
3342
3345
|
letter-spacing: var(--tracking-widest);
|
|
3343
3346
|
}
|
|
3347
|
+
.text-amber-600 {
|
|
3348
|
+
color: var(--color-amber-600);
|
|
3349
|
+
}
|
|
3344
3350
|
.text-current {
|
|
3345
3351
|
color: currentcolor;
|
|
3346
3352
|
}
|
|
@@ -4212,6 +4218,14 @@
|
|
|
4212
4218
|
border-top-color: var(--color-stone-300);
|
|
4213
4219
|
}
|
|
4214
4220
|
}
|
|
4221
|
+
.dark\:bg-amber-900\/50 {
|
|
4222
|
+
@media (prefers-color-scheme: dark) {
|
|
4223
|
+
background-color: color-mix(in srgb, oklch(41.4% 0.112 45.904) 50%, transparent);
|
|
4224
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
4225
|
+
background-color: color-mix(in oklab, var(--color-amber-900) 50%, transparent);
|
|
4226
|
+
}
|
|
4227
|
+
}
|
|
4228
|
+
}
|
|
4215
4229
|
.dark\:bg-green-500\/10 {
|
|
4216
4230
|
@media (prefers-color-scheme: dark) {
|
|
4217
4231
|
background-color: color-mix(in srgb, oklch(72.3% 0.219 149.579) 10%, transparent);
|
|
@@ -4339,6 +4353,11 @@
|
|
|
4339
4353
|
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
|
|
4340
4354
|
}
|
|
4341
4355
|
}
|
|
4356
|
+
.dark\:text-amber-400 {
|
|
4357
|
+
@media (prefers-color-scheme: dark) {
|
|
4358
|
+
color: var(--color-amber-400);
|
|
4359
|
+
}
|
|
4360
|
+
}
|
|
4342
4361
|
.dark\:text-green-400 {
|
|
4343
4362
|
@media (prefers-color-scheme: dark) {
|
|
4344
4363
|
color: var(--color-green-400);
|
|
@@ -4764,87 +4783,89 @@
|
|
|
4764
4783
|
}
|
|
4765
4784
|
}
|
|
4766
4785
|
}
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4786
|
+
@layer components {
|
|
4787
|
+
.card {
|
|
4788
|
+
background-color: var(--color-white);
|
|
4789
|
+
@media (prefers-color-scheme: dark) {
|
|
4790
|
+
background-color: color-mix(in srgb, oklch(26.8% 0.007 34.298) 50%, transparent);
|
|
4791
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
4792
|
+
background-color: color-mix(in oklab, var(--color-stone-800) 50%, transparent);
|
|
4793
|
+
}
|
|
4773
4794
|
}
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4795
|
+
overflow: hidden;
|
|
4796
|
+
border-radius: var(--radius-xl);
|
|
4797
|
+
--tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
|
|
4798
|
+
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
|
|
4799
|
+
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
|
4800
|
+
--tw-ring-color: color-mix(in oklab, var(--color-stone-900) 5%, transparent);
|
|
4801
|
+
@media (prefers-color-scheme: dark) {
|
|
4802
|
+
--tw-ring-color: color-mix(in srgb, #fff 10%, transparent);
|
|
4803
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
4804
|
+
--tw-ring-color: color-mix(in oklab, var(--color-white) 10%, transparent);
|
|
4805
|
+
}
|
|
4785
4806
|
}
|
|
4786
4807
|
}
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4808
|
+
.card-list {
|
|
4809
|
+
:where(& > :not(:last-child)) {
|
|
4810
|
+
--tw-divide-y-reverse: 0;
|
|
4811
|
+
border-bottom-style: var(--tw-border-style);
|
|
4812
|
+
border-top-style: var(--tw-border-style);
|
|
4813
|
+
border-top-width: calc(1px * var(--tw-divide-y-reverse));
|
|
4814
|
+
border-bottom-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
|
|
4815
|
+
}
|
|
4816
|
+
:where(& > :not(:last-child)) {
|
|
4817
|
+
border-color: var(--color-stone-100);
|
|
4818
|
+
}
|
|
4819
|
+
@media (prefers-color-scheme: dark) {
|
|
4820
|
+
:where(& > :not(:last-child)) {
|
|
4821
|
+
border-color: color-mix(in srgb, oklch(37.4% 0.01 67.558) 30%, transparent);
|
|
4822
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
4823
|
+
border-color: color-mix(in oklab, var(--color-stone-700) 30%, transparent);
|
|
4824
|
+
}
|
|
4825
|
+
}
|
|
4826
|
+
}
|
|
4795
4827
|
}
|
|
4796
|
-
:
|
|
4797
|
-
|
|
4828
|
+
.card:not(.card-list):has(.card-link) {
|
|
4829
|
+
position: relative;
|
|
4830
|
+
transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to;
|
|
4831
|
+
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
|
4832
|
+
transition-duration: var(--tw-duration, var(--default-transition-duration));
|
|
4798
4833
|
}
|
|
4799
|
-
|
|
4800
|
-
:
|
|
4801
|
-
|
|
4834
|
+
.card:not(.card-list):has(.card-link:hover) {
|
|
4835
|
+
background-color: var(--color-stone-50);
|
|
4836
|
+
@media (prefers-color-scheme: dark) {
|
|
4837
|
+
background-color: color-mix(in srgb, oklch(37.4% 0.01 67.558) 30%, transparent);
|
|
4802
4838
|
@supports (color: color-mix(in lab, red, red)) {
|
|
4803
|
-
|
|
4839
|
+
background-color: color-mix(in oklab, var(--color-stone-700) 30%, transparent);
|
|
4804
4840
|
}
|
|
4805
4841
|
}
|
|
4806
4842
|
}
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
|
4812
|
-
transition-duration: var(--tw-duration, var(--default-transition-duration));
|
|
4813
|
-
}
|
|
4814
|
-
.card:not(.card-list):has(.card-link:hover) {
|
|
4815
|
-
background-color: var(--color-stone-50);
|
|
4816
|
-
@media (prefers-color-scheme: dark) {
|
|
4817
|
-
background-color: color-mix(in srgb, oklch(37.4% 0.01 67.558) 30%, transparent);
|
|
4818
|
-
@supports (color: color-mix(in lab, red, red)) {
|
|
4819
|
-
background-color: color-mix(in oklab, var(--color-stone-700) 30%, transparent);
|
|
4820
|
-
}
|
|
4843
|
+
.card-link::after {
|
|
4844
|
+
content: "";
|
|
4845
|
+
position: absolute;
|
|
4846
|
+
inset: 0;
|
|
4821
4847
|
}
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
.card-item {
|
|
4829
|
-
padding-inline: calc(var(--spacing) * 5);
|
|
4830
|
-
padding-block: calc(var(--spacing) * 2.5);
|
|
4831
|
-
transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to;
|
|
4832
|
-
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
|
4833
|
-
transition-duration: var(--tw-duration, var(--default-transition-duration));
|
|
4834
|
-
}
|
|
4835
|
-
.card-item:has(.card-link) {
|
|
4836
|
-
position: relative;
|
|
4837
|
-
&:hover {
|
|
4838
|
-
@media (hover: hover) {
|
|
4839
|
-
background-color: var(--color-stone-50);
|
|
4840
|
-
}
|
|
4848
|
+
.card-item {
|
|
4849
|
+
padding-inline: calc(var(--spacing) * 5);
|
|
4850
|
+
padding-block: calc(var(--spacing) * 2.5);
|
|
4851
|
+
transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to;
|
|
4852
|
+
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
|
4853
|
+
transition-duration: var(--tw-duration, var(--default-transition-duration));
|
|
4841
4854
|
}
|
|
4842
|
-
|
|
4855
|
+
.card-item:has(.card-link) {
|
|
4856
|
+
position: relative;
|
|
4843
4857
|
&:hover {
|
|
4844
4858
|
@media (hover: hover) {
|
|
4845
|
-
background-color: color-
|
|
4846
|
-
|
|
4847
|
-
|
|
4859
|
+
background-color: var(--color-stone-50);
|
|
4860
|
+
}
|
|
4861
|
+
}
|
|
4862
|
+
@media (prefers-color-scheme: dark) {
|
|
4863
|
+
&:hover {
|
|
4864
|
+
@media (hover: hover) {
|
|
4865
|
+
background-color: color-mix(in srgb, oklch(37.4% 0.01 67.558) 30%, transparent);
|
|
4866
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
4867
|
+
background-color: color-mix(in oklab, var(--color-stone-700) 30%, transparent);
|
|
4868
|
+
}
|
|
4848
4869
|
}
|
|
4849
4870
|
}
|
|
4850
4871
|
}
|
|
@@ -6920,6 +6941,14 @@ dialog.modal {
|
|
|
6920
6941
|
text-overflow: ellipsis;
|
|
6921
6942
|
white-space: nowrap;
|
|
6922
6943
|
}
|
|
6944
|
+
.selection-list-description {
|
|
6945
|
+
font-size: var(--text-xs);
|
|
6946
|
+
line-height: var(--tw-leading, var(--text-xs--line-height));
|
|
6947
|
+
color: var(--color-stone-400);
|
|
6948
|
+
@media (prefers-color-scheme: dark) {
|
|
6949
|
+
color: var(--color-stone-500);
|
|
6950
|
+
}
|
|
6951
|
+
}
|
|
6923
6952
|
.selection-list-handle {
|
|
6924
6953
|
font-family: var(--font-mono);
|
|
6925
6954
|
font-size: var(--text-xs);
|
|
@@ -1,33 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
@layer components {
|
|
2
|
+
.card {
|
|
3
|
+
@apply bg-white dark:bg-stone-800/50;
|
|
4
|
+
@apply rounded-xl shadow-sm overflow-hidden;
|
|
5
|
+
@apply ring-1 ring-stone-900/5 dark:ring-white/10;
|
|
6
|
+
}
|
|
6
7
|
|
|
7
|
-
.card-list {
|
|
8
|
-
|
|
9
|
-
}
|
|
8
|
+
.card-list {
|
|
9
|
+
@apply divide-y divide-stone-100 dark:divide-stone-700/30;
|
|
10
|
+
}
|
|
10
11
|
|
|
11
|
-
.card:not(.card-list):has(.card-link) {
|
|
12
|
-
|
|
13
|
-
}
|
|
12
|
+
.card:not(.card-list):has(.card-link) {
|
|
13
|
+
@apply relative transition-colors;
|
|
14
|
+
}
|
|
14
15
|
|
|
15
|
-
.card:not(.card-list):has(.card-link:hover) {
|
|
16
|
-
|
|
17
|
-
}
|
|
16
|
+
.card:not(.card-list):has(.card-link:hover) {
|
|
17
|
+
@apply bg-stone-50 dark:bg-stone-700/30;
|
|
18
|
+
}
|
|
18
19
|
|
|
19
|
-
.card-link::after {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
20
|
+
.card-link::after {
|
|
21
|
+
content: "";
|
|
22
|
+
position: absolute;
|
|
23
|
+
inset: 0;
|
|
24
|
+
}
|
|
24
25
|
|
|
25
|
-
.card-item {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
26
|
+
.card-item {
|
|
27
|
+
@apply px-5 py-2.5;
|
|
28
|
+
@apply transition-colors;
|
|
29
|
+
}
|
|
29
30
|
|
|
30
|
-
.card-item:has(.card-link) {
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
.card-item:has(.card-link) {
|
|
32
|
+
@apply relative;
|
|
33
|
+
@apply hover:bg-stone-50 dark:hover:bg-stone-700/30;
|
|
34
|
+
}
|
|
33
35
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module Iron
|
|
2
|
+
module Api
|
|
3
|
+
module CursorPagination
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
DEFAULT_PER_PAGE = 25
|
|
7
|
+
MAX_PER_PAGE = 100
|
|
8
|
+
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
def paginate(scope)
|
|
12
|
+
per_page = (params[:per_page].presence || DEFAULT_PER_PAGE).to_i.clamp(1, MAX_PER_PAGE)
|
|
13
|
+
ordered = scope.order(updated_at: :desc, id: :desc)
|
|
14
|
+
|
|
15
|
+
records = if params[:after].present?
|
|
16
|
+
cursor = decode_cursor(params[:after])
|
|
17
|
+
ordered.where(
|
|
18
|
+
"(iron_entries.updated_at, iron_entries.id) < (?, ?)", cursor[:updated_at], cursor[:id]
|
|
19
|
+
).limit(per_page + 1)
|
|
20
|
+
else
|
|
21
|
+
ordered.limit(per_page + 1)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
has_more = records.size > per_page
|
|
25
|
+
records = records.first(per_page)
|
|
26
|
+
next_cursor = has_more ? encode_cursor(records.last) : nil
|
|
27
|
+
|
|
28
|
+
OpenStruct.new(
|
|
29
|
+
records: records,
|
|
30
|
+
pagination: OpenStruct.new(
|
|
31
|
+
per_page: per_page,
|
|
32
|
+
next_cursor: next_cursor,
|
|
33
|
+
has_more: has_more
|
|
34
|
+
)
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def encode_cursor(record)
|
|
39
|
+
Base64.urlsafe_encode64("#{record.updated_at.iso8601(6)},#{record.id}", padding: false)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def decode_cursor(cursor)
|
|
43
|
+
decoded = Base64.urlsafe_decode64(cursor)
|
|
44
|
+
updated_at_str, id_str = decoded.split(",", 2)
|
|
45
|
+
{ updated_at: Time.zone.parse(updated_at_str), id: id_str.to_i }
|
|
46
|
+
rescue ArgumentError
|
|
47
|
+
{ updated_at: Time.current, id: 0 }
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Iron
|
|
2
|
+
module Api
|
|
3
|
+
module LocaleResolution
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
included do
|
|
7
|
+
before_action :set_locale
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def set_locale
|
|
13
|
+
Current.locale = resolve_locale
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def resolve_locale
|
|
17
|
+
if params[:locale].present?
|
|
18
|
+
Locale.find_by(code: params[:locale])
|
|
19
|
+
end || Locale.default
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Iron
|
|
2
|
+
module Api
|
|
3
|
+
module TokenAuthentication
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
included do
|
|
7
|
+
include ActionController::HttpAuthentication::Token::ControllerMethods
|
|
8
|
+
|
|
9
|
+
before_action :authenticate_integration
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def authenticate_integration
|
|
15
|
+
authenticate_with_http_token do |token|
|
|
16
|
+
integration = Integration.find_by(token: token)
|
|
17
|
+
return head(:unauthorized) unless integration
|
|
18
|
+
return head(:unauthorized) if integration.expired?
|
|
19
|
+
|
|
20
|
+
user = integration.user
|
|
21
|
+
return head(:unauthorized) unless user&.active?
|
|
22
|
+
|
|
23
|
+
Current.user = user
|
|
24
|
+
integration.touch(:last_used_at)
|
|
25
|
+
end || head(:unauthorized)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Iron
|
|
2
|
+
module Api
|
|
3
|
+
class BaseController < ActionController::API
|
|
4
|
+
include ActiveStorage::SetCurrent
|
|
5
|
+
include TokenAuthentication, LocaleResolution, Authorization
|
|
6
|
+
|
|
7
|
+
rate_limit to: 60, within: 1.minute
|
|
8
|
+
|
|
9
|
+
rescue_from ActiveRecord::RecordNotFound, with: :render_not_found
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def render_not_found
|
|
14
|
+
render json: { error: "Not found" }, status: :not_found
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
module Iron
|
|
2
|
+
module Api
|
|
3
|
+
class ContentController < BaseController
|
|
4
|
+
include CursorPagination
|
|
5
|
+
|
|
6
|
+
before_action :set_content_type
|
|
7
|
+
before_action :set_entry, only: %i[show update destroy]
|
|
8
|
+
before_action :ensure_can_write, only: %i[create update destroy]
|
|
9
|
+
before_action :ensure_collection_content_type, only: :create
|
|
10
|
+
|
|
11
|
+
def index
|
|
12
|
+
if params.key?(:route)
|
|
13
|
+
@entry = @content_type.entries.find_by!(route: params[:route])
|
|
14
|
+
render :show
|
|
15
|
+
else
|
|
16
|
+
@collection = paginate(@content_type.entries)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def show
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def create
|
|
24
|
+
@entry = @content_type.entries.build
|
|
25
|
+
@entry.route = params[:route] if params.key?(:route)
|
|
26
|
+
@entry.assign_content(content_params)
|
|
27
|
+
|
|
28
|
+
if @entry.save
|
|
29
|
+
render :show, status: :created, location: api_content_url(handle: @content_type.handle, id: @entry.id)
|
|
30
|
+
else
|
|
31
|
+
render json: @entry.content_errors, status: :unprocessable_entity
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def update
|
|
36
|
+
@entry.route = params[:route] if params.key?(:route)
|
|
37
|
+
@entry.assign_content(content_params)
|
|
38
|
+
|
|
39
|
+
if @entry.save
|
|
40
|
+
render :show
|
|
41
|
+
else
|
|
42
|
+
render json: @entry.content_errors, status: :unprocessable_entity
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def destroy
|
|
47
|
+
@entry.destroy!
|
|
48
|
+
head :no_content
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def set_content_type
|
|
54
|
+
@content_type = ContentType.find_by!(handle: params[:handle])
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def set_entry
|
|
58
|
+
@entry = if params[:id].blank?
|
|
59
|
+
raise ActiveRecord::RecordNotFound unless @content_type.single?
|
|
60
|
+
@content_type.entry
|
|
61
|
+
else
|
|
62
|
+
@content_type.entries.find(params[:id])
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def content_params
|
|
67
|
+
params.fetch(:content, {}).permit!.to_h
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def ensure_collection_content_type
|
|
71
|
+
head :method_not_allowed unless @content_type.collection?
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Iron
|
|
2
|
+
module Api
|
|
3
|
+
class OpenapiController < BaseController
|
|
4
|
+
skip_before_action :authenticate_integration
|
|
5
|
+
|
|
6
|
+
def show
|
|
7
|
+
spec = Rails.cache.fetch(cache_key, expires_in: 1.hour) do
|
|
8
|
+
OpenapiSpec.new.to_h
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
render json: spec
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def cache_key
|
|
17
|
+
latest = [ ContentType, FieldDefinition, BlockDefinition ].filter_map { |m| m.maximum(:updated_at) }.max
|
|
18
|
+
"iron/openapi/#{latest&.to_i}"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Iron
|
|
2
|
+
module Api
|
|
3
|
+
class SearchController < BaseController
|
|
4
|
+
include CursorPagination
|
|
5
|
+
|
|
6
|
+
def show
|
|
7
|
+
return render json: { error: "Missing required parameter: q" }, status: :bad_request if params[:q].blank?
|
|
8
|
+
|
|
9
|
+
scope = Entry.search(params[:q], locale: Current.locale)
|
|
10
|
+
.includes(:fields, content_type: :field_definitions)
|
|
11
|
+
@collection = paginate(scope)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Iron
|
|
2
|
+
module Api
|
|
3
|
+
class UploadsController < BaseController
|
|
4
|
+
before_action :ensure_can_write
|
|
5
|
+
|
|
6
|
+
def create
|
|
7
|
+
blob = ActiveStorage::Blob.create_and_upload!(
|
|
8
|
+
io: params.require(:file),
|
|
9
|
+
filename: params[:file].original_filename,
|
|
10
|
+
content_type: params[:file].content_type
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
render json: { signed_id: blob.signed_id }, status: :created
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -3,14 +3,14 @@ module Iron
|
|
|
3
3
|
layout "iron/authentication"
|
|
4
4
|
allow_unauthenticated_access
|
|
5
5
|
before_action :require_email_configuration, only: %i[ new create ]
|
|
6
|
-
before_action :
|
|
6
|
+
before_action :set_person_by_token, only: %i[ edit update ]
|
|
7
7
|
|
|
8
8
|
def new
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def create
|
|
12
|
-
if
|
|
13
|
-
Iron::PasswordsMailer.reset(user).deliver_later
|
|
12
|
+
if (person = Person.find_by(email_address: params[:email_address])) && person.active?
|
|
13
|
+
Iron::PasswordsMailer.reset(person.user).deliver_later
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
redirect_to sign_in_url, notice: t("iron.passwords.notifications.reset_instructions_sent")
|
|
@@ -20,7 +20,7 @@ module Iron
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def update
|
|
23
|
-
if @
|
|
23
|
+
if @person.update(params.permit(:password, :password_confirmation))
|
|
24
24
|
redirect_to sign_in_url, notice: t("iron.passwords.notifications.reset_success")
|
|
25
25
|
else
|
|
26
26
|
redirect_to edit_password_url(params[:token]), alert: t("iron.passwords.alerts.passwords_mismatch")
|
|
@@ -34,9 +34,10 @@ module Iron
|
|
|
34
34
|
end
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
-
def
|
|
38
|
-
@
|
|
39
|
-
|
|
37
|
+
def set_person_by_token
|
|
38
|
+
@person = Person.find_by_password_reset_token!(params[:token])
|
|
39
|
+
@user = @person.user
|
|
40
|
+
redirect_to new_password_url, alert: t("iron.passwords.alerts.reset_link_invalid") unless @person.active?
|
|
40
41
|
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
|
41
42
|
redirect_to new_password_url, alert: t("iron.passwords.alerts.reset_link_invalid")
|
|
42
43
|
end
|
|
@@ -9,8 +9,10 @@ module Iron
|
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def update
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
person = Person.find_by_transfer_id(params[:id])
|
|
13
|
+
|
|
14
|
+
if person&.active?
|
|
15
|
+
start_new_session_for person.user
|
|
14
16
|
redirect_to after_authentication_url
|
|
15
17
|
else
|
|
16
18
|
redirect_to sign_in_url, alert: "Transfer link is invalid or has expired."
|
|
@@ -10,8 +10,10 @@ module Iron
|
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def create
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
person = Person.authenticate_by(params.permit(:email_address, :password))
|
|
14
|
+
|
|
15
|
+
if person&.active?
|
|
16
|
+
start_new_session_for person.user
|
|
15
17
|
redirect_to after_authentication_url
|
|
16
18
|
else
|
|
17
19
|
redirect_to sign_in_url, alert: "Try another email address or password."
|