iron-cms 0.12.0 → 0.13.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/README.md +17 -18
- data/app/assets/builds/iron.css +3 -0
- data/app/controllers/concerns/iron/authentication.rb +2 -1
- data/app/controllers/iron/passwords_controller.rb +2 -1
- data/app/controllers/iron/sessions/transfers_controller.rb +1 -1
- data/app/controllers/iron/sessions_controller.rb +1 -1
- data/app/controllers/iron/users/reactivations_controller.rb +18 -0
- data/app/controllers/iron/users/roles_controller.rb +1 -1
- data/app/controllers/iron/users_controller.rb +5 -4
- data/app/models/iron/account/export.rb +3 -61
- data/app/models/iron/account/import.rb +1 -179
- data/app/models/iron/block_definition/importable.rb +6 -12
- data/app/models/iron/exporter.rb +75 -0
- data/app/models/iron/importer.rb +211 -0
- data/app/models/iron/seed.rb +77 -0
- data/app/models/iron/user/deactivatable.rb +23 -0
- data/app/models/iron/user.rb +1 -9
- data/app/views/iron/users/index.html.erb +25 -0
- data/config/locales/en.yml +4 -1
- data/config/locales/it.yml +4 -1
- data/config/routes.rb +1 -0
- data/db/migrate/20260209215027_add_active_to_iron_users.rb +5 -0
- data/lib/iron/version.rb +1 -1
- data/lib/tasks/iron_tasks.rake +10 -19
- metadata +7 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6f8165107a118a6f3a8dcefe95984760653d8baf16f96653642382fc73ce8aca
|
|
4
|
+
data.tar.gz: 7143fd0c4f78a53e4b945f52005cc1f68e225674667f0f6a8bce8dcb8b77a02d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e09696ccbfb17bf2a82d515e7297f097a4a6fbdeb8a30626bf1cb1e44818ae9f55f260fa6b7e04acce6707d1d212305318cddf6db545f62d04b37d919191c73f
|
|
7
|
+
data.tar.gz: be54e07e53ca096cb56e66887eab33085f76545716a61dfa7976866303f224867d9766eceaa338a65ecd3182d5b2b7af2003226d98c52d64dd17a68b912cc7e3
|
data/README.md
CHANGED
|
@@ -96,37 +96,36 @@ For cases where you need a basic responsive image without format optimization:
|
|
|
96
96
|
|
|
97
97
|
This generates a standard `<img>` with srcset but without modern format variants.
|
|
98
98
|
|
|
99
|
-
###
|
|
99
|
+
### Development Seeding
|
|
100
100
|
|
|
101
|
-
Iron provides
|
|
101
|
+
Iron provides a seeding mechanism to snapshot and restore CMS schema and content, making it easy for teammates to get a consistent development database.
|
|
102
102
|
|
|
103
|
-
####
|
|
103
|
+
#### Creating a seed
|
|
104
104
|
|
|
105
|
-
|
|
105
|
+
After setting up your content types and sample content via the admin UI:
|
|
106
106
|
|
|
107
107
|
```bash
|
|
108
|
-
bin/rails iron:
|
|
108
|
+
bin/rails iron:seed:dump
|
|
109
109
|
```
|
|
110
110
|
|
|
111
|
-
This
|
|
111
|
+
This exports the full CMS state to `db/seeds/iron.zip`. Commit this file to your repository.
|
|
112
112
|
|
|
113
|
-
|
|
114
|
-
- `content.tar.gz` - All entries and their field data
|
|
113
|
+
#### Loading a seed
|
|
115
114
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
#### Using Fixtures in Tests
|
|
119
|
-
|
|
120
|
-
Load the fixtures in your `test/test_helper.rb`:
|
|
115
|
+
Add this to your `db/seeds.rb`:
|
|
121
116
|
|
|
122
117
|
```ruby
|
|
123
|
-
|
|
124
|
-
fixtures :all
|
|
125
|
-
Iron::TestFixtures.load
|
|
126
|
-
end
|
|
118
|
+
Iron::Seed.load
|
|
127
119
|
```
|
|
128
120
|
|
|
129
|
-
|
|
121
|
+
Then `rails db:prepare` (or `rails db:seed`) will automatically bootstrap the CMS when the database is empty. Loading is skipped if content types already exist.
|
|
122
|
+
|
|
123
|
+
#### Admin credentials
|
|
124
|
+
|
|
125
|
+
The seed creates a default admin user during loading. Configure via environment variables:
|
|
126
|
+
|
|
127
|
+
- `IRON_SEED_EMAIL` (default: `admin@example.com`)
|
|
128
|
+
- `IRON_SEED_PASSWORD` (default: `password`)
|
|
130
129
|
|
|
131
130
|
## System Requirements
|
|
132
131
|
|
data/app/assets/builds/iron.css
CHANGED
|
@@ -3429,6 +3429,9 @@
|
|
|
3429
3429
|
.opacity-0 {
|
|
3430
3430
|
opacity: 0%;
|
|
3431
3431
|
}
|
|
3432
|
+
.opacity-50 {
|
|
3433
|
+
opacity: 50%;
|
|
3434
|
+
}
|
|
3432
3435
|
.shadow {
|
|
3433
3436
|
--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));
|
|
3434
3437
|
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
|
@@ -9,7 +9,7 @@ module Iron
|
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def create
|
|
12
|
-
if user = User.find_by(email_address: params[:email_address])
|
|
12
|
+
if user = User.active.find_by(email_address: params[:email_address])
|
|
13
13
|
Iron::PasswordsMailer.reset(user).deliver_later
|
|
14
14
|
end
|
|
15
15
|
|
|
@@ -36,6 +36,7 @@ module Iron
|
|
|
36
36
|
|
|
37
37
|
def set_user_by_token
|
|
38
38
|
@user = User.find_by_password_reset_token!(params[:token])
|
|
39
|
+
redirect_to new_password_url, alert: t("iron.passwords.alerts.reset_link_invalid") unless @user.active?
|
|
39
40
|
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
|
40
41
|
redirect_to new_password_url, alert: t("iron.passwords.alerts.reset_link_invalid")
|
|
41
42
|
end
|
|
@@ -10,7 +10,7 @@ module Iron
|
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def create
|
|
13
|
-
if user = User.authenticate_by(params.permit(:email_address, :password))
|
|
13
|
+
if user = User.active.authenticate_by(params.permit(:email_address, :password))
|
|
14
14
|
start_new_session_for user
|
|
15
15
|
redirect_to after_authentication_url
|
|
16
16
|
else
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Iron
|
|
2
|
+
module Users
|
|
3
|
+
class ReactivationsController < ApplicationController
|
|
4
|
+
before_action :set_user
|
|
5
|
+
before_action :ensure_can_administer
|
|
6
|
+
|
|
7
|
+
def update
|
|
8
|
+
@user.reactivate
|
|
9
|
+
redirect_to @user, notice: t("iron.users.notifications.reactivated")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
def set_user
|
|
14
|
+
@user = User.find(params.expect(:user_id))
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -8,7 +8,8 @@ module Iron
|
|
|
8
8
|
before_action :ensure_can_administer, only: %i[ index destroy ]
|
|
9
9
|
|
|
10
10
|
def index
|
|
11
|
-
@users = User.
|
|
11
|
+
@users = User.active
|
|
12
|
+
@deactivated_users = User.where(active: false)
|
|
12
13
|
end
|
|
13
14
|
|
|
14
15
|
def new
|
|
@@ -31,8 +32,8 @@ module Iron
|
|
|
31
32
|
end
|
|
32
33
|
|
|
33
34
|
def destroy
|
|
34
|
-
if @user.
|
|
35
|
-
|
|
35
|
+
if @user.deactivate
|
|
36
|
+
redirect_to users_path, notice: t("iron.users.notifications.removed"), status: :see_other
|
|
36
37
|
else
|
|
37
38
|
redirect_back fallback_location: users_path, alert: @user.errors.full_messages.to_sentence, status: :see_other
|
|
38
39
|
end
|
|
@@ -40,7 +41,7 @@ module Iron
|
|
|
40
41
|
|
|
41
42
|
private
|
|
42
43
|
def set_user
|
|
43
|
-
@user = User.find(params.expect(:id))
|
|
44
|
+
@user = User.active.find(params.expect(:id))
|
|
44
45
|
end
|
|
45
46
|
|
|
46
47
|
def join_params
|
|
@@ -17,70 +17,12 @@ module Iron
|
|
|
17
17
|
def perform
|
|
18
18
|
raise ArgumentError, "Nothing selected to export" unless include_schema? || include_content?
|
|
19
19
|
|
|
20
|
-
zipfile =
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
filename: "iron-export-#{id}.zip",
|
|
24
|
-
content_type: "application/zip"
|
|
25
|
-
)
|
|
20
|
+
zipfile = Tempfile.new([ "export", ".zip" ])
|
|
21
|
+
Exporter.new(zipfile.path, include_schema: include_schema?, include_content: include_content?).export
|
|
22
|
+
file.attach(io: File.open(zipfile.path), filename: "iron-export-#{id}.zip", content_type: "application/zip")
|
|
26
23
|
ensure
|
|
27
24
|
zipfile&.close
|
|
28
25
|
zipfile&.unlink
|
|
29
26
|
end
|
|
30
|
-
|
|
31
|
-
def generate_zip
|
|
32
|
-
Tempfile.new([ "export", ".zip" ]).tap do |tempfile|
|
|
33
|
-
Zip::File.open(tempfile.path, create: true) do |zip|
|
|
34
|
-
add_schema_to_zip(zip) if include_schema?
|
|
35
|
-
add_content_to_zip(zip) if include_content?
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def add_schema_to_zip(zip)
|
|
41
|
-
zip.get_output_stream("schema.json") { |f| f.write(export_schema_json) }
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def add_content_to_zip(zip)
|
|
45
|
-
exportable_entries.find_each do |entry|
|
|
46
|
-
path = "entries/#{entry.content_type.handle}/#{entry.id}.json"
|
|
47
|
-
zip.get_output_stream(path) { |f| f.write(entry.export_json) }
|
|
48
|
-
|
|
49
|
-
entry.export_attachments.each do |attachment|
|
|
50
|
-
zip.get_output_stream("files/#{attachment[:path]}", compression_method: Zip::Entry::STORED) do |f|
|
|
51
|
-
attachment[:blob].download { |chunk| f.write(chunk) }
|
|
52
|
-
end
|
|
53
|
-
rescue ActiveStorage::FileNotFoundError
|
|
54
|
-
# Skip missing files
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def export_schema_json
|
|
60
|
-
JSON.pretty_generate(
|
|
61
|
-
version: "1.0",
|
|
62
|
-
exported_at: Time.current.iso8601,
|
|
63
|
-
block_definitions: export_block_definitions,
|
|
64
|
-
content_types: export_content_types
|
|
65
|
-
)
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def export_block_definitions
|
|
69
|
-
BlockDefinition
|
|
70
|
-
.includes(:field_definitions)
|
|
71
|
-
.order(:handle)
|
|
72
|
-
.map(&:export_attributes)
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def export_content_types
|
|
76
|
-
ContentType
|
|
77
|
-
.includes(:title_field_definition, :web_page_title_field_definition, :field_definitions)
|
|
78
|
-
.order(:handle)
|
|
79
|
-
.map(&:export_attributes)
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def exportable_entries
|
|
83
|
-
Entry.includes(:content_type, :creator, fields: [ :locale, :definition ])
|
|
84
|
-
end
|
|
85
27
|
end
|
|
86
28
|
end
|
|
@@ -18,190 +18,12 @@ module Iron
|
|
|
18
18
|
def perform
|
|
19
19
|
raise ArgumentError, "Nothing selected to import" unless include_schema? || include_content?
|
|
20
20
|
|
|
21
|
-
import_from_zip
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def import_from_zip
|
|
25
21
|
Tempfile.create([ "import", ".zip" ]) do |tempfile|
|
|
26
22
|
tempfile.binmode
|
|
27
23
|
tempfile.write(file.download)
|
|
28
24
|
tempfile.rewind
|
|
29
25
|
|
|
30
|
-
|
|
31
|
-
@zip = zip
|
|
32
|
-
@files_dir = extract_files_to_temp_dir(zip)
|
|
33
|
-
|
|
34
|
-
import_schema_from_zip if include_schema?
|
|
35
|
-
import_content_from_zip if include_content?
|
|
36
|
-
ensure
|
|
37
|
-
FileUtils.rm_rf(@files_dir) if @files_dir
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def extract_files_to_temp_dir(zip)
|
|
43
|
-
dir = Dir.mktmpdir("iron-import")
|
|
44
|
-
zip.glob("files/**/*").each do |entry|
|
|
45
|
-
next if entry.directory?
|
|
46
|
-
|
|
47
|
-
path = File.join(dir, entry.name.sub("files/", ""))
|
|
48
|
-
FileUtils.mkdir_p(File.dirname(path))
|
|
49
|
-
entry.extract(path)
|
|
50
|
-
end
|
|
51
|
-
dir
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def import_schema_from_zip
|
|
55
|
-
return unless @zip.find_entry("schema.json")
|
|
56
|
-
|
|
57
|
-
schema_json = @zip.read("schema.json")
|
|
58
|
-
schema = parse_json(schema_json)
|
|
59
|
-
raise ArgumentError, "Invalid schema JSON format" unless schema
|
|
60
|
-
|
|
61
|
-
ActiveRecord::Base.transaction do
|
|
62
|
-
import_block_definitions(schema[:block_definitions] || [])
|
|
63
|
-
import_content_types(schema[:content_types] || [])
|
|
64
|
-
resolve_content_type_references(schema[:content_types] || [])
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def import_content_from_zip
|
|
69
|
-
ActiveRecord::Base.transaction do
|
|
70
|
-
id_mapping = {}
|
|
71
|
-
entries_data = []
|
|
72
|
-
|
|
73
|
-
# First pass: read all entry files and create entries
|
|
74
|
-
@zip.glob("entries/**/*.json").each do |zip_entry|
|
|
75
|
-
attrs = parse_json(zip_entry.get_input_stream.read)
|
|
76
|
-
next unless attrs
|
|
77
|
-
|
|
78
|
-
entries_data << attrs
|
|
79
|
-
entry = Entry.import_from_attributes(attrs, @files_dir)
|
|
80
|
-
id_mapping[attrs[:id]] = entry if entry
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
# Second pass: resolve references
|
|
84
|
-
resolve_references(entries_data, id_mapping)
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
def resolve_references(entries_data, id_mapping)
|
|
89
|
-
entries_data.each do |attrs|
|
|
90
|
-
entry = id_mapping[attrs[:id]]
|
|
91
|
-
next unless entry
|
|
92
|
-
|
|
93
|
-
(attrs[:fields] || {}).each do |locale_code, field_data|
|
|
94
|
-
locale = Locale.find_by(code: locale_code.to_s)
|
|
95
|
-
next unless locale
|
|
96
|
-
|
|
97
|
-
resolve_references_in_fields(entry, locale, field_data, id_mapping)
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def resolve_references_in_fields(entry, locale, fields_data, id_mapping, parent: nil)
|
|
103
|
-
fields_data.each do |handle, value_data|
|
|
104
|
-
next unless value_data.is_a?(Hash)
|
|
105
|
-
|
|
106
|
-
case value_data[:type]
|
|
107
|
-
when "reference"
|
|
108
|
-
resolve_reference_field(entry, locale, handle.to_s, value_data[:value], id_mapping, parent: parent)
|
|
109
|
-
when "reference_list"
|
|
110
|
-
resolve_reference_list_field(entry, locale, handle.to_s, value_data[:value], id_mapping, parent: parent)
|
|
111
|
-
when "block"
|
|
112
|
-
resolve_references_in_block(entry, locale, handle.to_s, value_data, id_mapping, parent: parent)
|
|
113
|
-
when "block_list"
|
|
114
|
-
resolve_references_in_block_list(entry, locale, handle.to_s, value_data, id_mapping, parent: parent)
|
|
115
|
-
end
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
def resolve_references_in_block(entry, locale, handle, value_data, id_mapping, parent: nil)
|
|
120
|
-
block_field = find_field(entry, locale, handle, parent)
|
|
121
|
-
return unless block_field
|
|
122
|
-
|
|
123
|
-
resolve_references_in_fields(entry, locale, value_data[:fields] || {}, id_mapping, parent: block_field)
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
def resolve_references_in_block_list(entry, locale, handle, value_data, id_mapping, parent: nil)
|
|
127
|
-
block_list_field = find_field(entry, locale, handle, parent)
|
|
128
|
-
return unless block_list_field
|
|
129
|
-
|
|
130
|
-
# Reload blocks and get them in rank order (matching import order)
|
|
131
|
-
ordered_blocks = block_list_field.blocks.reload.sort_by(&:rank)
|
|
132
|
-
|
|
133
|
-
(value_data[:value] || []).each_with_index do |block_data, index|
|
|
134
|
-
block = ordered_blocks[index]
|
|
135
|
-
next unless block
|
|
136
|
-
|
|
137
|
-
resolve_references_in_fields(entry, locale, block_data[:fields] || {}, id_mapping, parent: block)
|
|
138
|
-
end
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
def find_field(entry, locale, handle, parent)
|
|
142
|
-
# Reload associations to get freshly persisted fields from the first pass
|
|
143
|
-
fields = parent&.fields&.reload || entry.fields.reload
|
|
144
|
-
definition_scope = parent.is_a?(Fields::Block) ? parent.block_definition.field_definitions : entry.content_type.field_definitions
|
|
145
|
-
definition = definition_scope.find_by(handle: handle)
|
|
146
|
-
return unless definition
|
|
147
|
-
|
|
148
|
-
fields.find { |f| f.definition_id == definition.id && f.locale_id == locale.id }
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
def resolve_reference_field(entry, locale, handle, old_entry_id, id_mapping, parent: nil)
|
|
152
|
-
return unless old_entry_id
|
|
153
|
-
|
|
154
|
-
referenced_entry = id_mapping[old_entry_id]
|
|
155
|
-
return unless referenced_entry
|
|
156
|
-
|
|
157
|
-
field = find_field(entry, locale, handle, parent)
|
|
158
|
-
return unless field
|
|
159
|
-
|
|
160
|
-
field.update!(referenced_entry: referenced_entry)
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
def resolve_reference_list_field(entry, locale, handle, old_entry_ids, id_mapping, parent: nil)
|
|
164
|
-
return unless old_entry_ids.is_a?(Array)
|
|
165
|
-
|
|
166
|
-
field = find_field(entry, locale, handle, parent)
|
|
167
|
-
return unless field
|
|
168
|
-
|
|
169
|
-
old_entry_ids.each_with_index do |old_id, index|
|
|
170
|
-
referenced_entry = id_mapping[old_id]
|
|
171
|
-
next unless referenced_entry
|
|
172
|
-
|
|
173
|
-
field.references.create!(entry: referenced_entry, rank: index)
|
|
174
|
-
end
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
def parse_json(content)
|
|
178
|
-
JSON.parse(content, symbolize_names: true)
|
|
179
|
-
rescue JSON::ParserError
|
|
180
|
-
nil
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
def import_block_definitions(definitions)
|
|
184
|
-
definitions.each { |attrs| BlockDefinition.import_from_attributes(attrs) }
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
def import_content_types(definitions)
|
|
188
|
-
definitions.each { |attrs| ContentType.import_from_attributes(attrs) }
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
def resolve_content_type_references(definitions)
|
|
192
|
-
definitions.each do |attrs|
|
|
193
|
-
content_type = ContentType.find_by(handle: attrs[:handle])
|
|
194
|
-
next unless content_type
|
|
195
|
-
|
|
196
|
-
if attrs[:title_field_handle].present?
|
|
197
|
-
field = content_type.field_definitions.find_by(handle: attrs[:title_field_handle])
|
|
198
|
-
content_type.update!(title_field_definition: field) if field
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
if attrs[:web_page_title_field_handle].present?
|
|
202
|
-
field = content_type.field_definitions.find_by(handle: attrs[:web_page_title_field_handle])
|
|
203
|
-
content_type.update!(web_page_title_field_definition: field) if field
|
|
204
|
-
end
|
|
26
|
+
Importer.new(tempfile.path, include_schema: include_schema?, include_content: include_content?).import
|
|
205
27
|
end
|
|
206
28
|
end
|
|
207
29
|
end
|
|
@@ -4,21 +4,15 @@ module Iron
|
|
|
4
4
|
|
|
5
5
|
class_methods do
|
|
6
6
|
def import_from_attributes(attrs)
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
description: attrs[:description]
|
|
11
|
-
)
|
|
12
|
-
|
|
13
|
-
import_field_definitions(block_def, attrs[:field_definitions] || [])
|
|
14
|
-
|
|
15
|
-
block_def
|
|
7
|
+
find_or_initialize_by(handle: attrs[:handle]).tap do |block_def|
|
|
8
|
+
block_def.update!(name: attrs[:name], description: attrs[:description])
|
|
9
|
+
end
|
|
16
10
|
end
|
|
17
11
|
|
|
18
|
-
|
|
12
|
+
def populate_field_definitions(attrs)
|
|
13
|
+
block_def = find_by!(handle: attrs[:handle])
|
|
19
14
|
|
|
20
|
-
|
|
21
|
-
field_attrs_list.each do |field_attrs|
|
|
15
|
+
(attrs[:field_definitions] || []).each do |field_attrs|
|
|
22
16
|
FieldDefinition.import_from_attributes(field_attrs, schemable: block_def)
|
|
23
17
|
end
|
|
24
18
|
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
module Iron
|
|
2
|
+
class Exporter
|
|
3
|
+
def initialize(path, include_schema: true, include_content: true)
|
|
4
|
+
@path = path.to_s
|
|
5
|
+
@include_schema = include_schema
|
|
6
|
+
@include_content = include_content
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def export
|
|
10
|
+
Zip::File.open(path, create: true) do |zip|
|
|
11
|
+
write_schema(zip) if schema?
|
|
12
|
+
write_entries(zip) if content?
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
attr_reader :path
|
|
19
|
+
|
|
20
|
+
def schema? = @include_schema
|
|
21
|
+
def content? = @include_content
|
|
22
|
+
|
|
23
|
+
def write_schema(zip)
|
|
24
|
+
zip.get_output_stream("schema.json") { |f| f.write(schema_json) }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def write_entries(zip)
|
|
28
|
+
exportable_entries.find_each do |entry|
|
|
29
|
+
path = "entries/#{entry.content_type.handle}/#{entry.id}.json"
|
|
30
|
+
zip.get_output_stream(path) { |f| f.write(entry.export_json) }
|
|
31
|
+
|
|
32
|
+
entry.export_attachments.each do |attachment|
|
|
33
|
+
zip.get_output_stream("files/#{attachment[:path]}", compression_method: Zip::Entry::STORED) do |f|
|
|
34
|
+
attachment[:blob].download { |chunk| f.write(chunk) }
|
|
35
|
+
end
|
|
36
|
+
rescue ActiveStorage::FileNotFoundError
|
|
37
|
+
# Skip missing files
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def schema_json
|
|
43
|
+
JSON.pretty_generate(
|
|
44
|
+
version: "1.0",
|
|
45
|
+
exported_at: Time.current.iso8601,
|
|
46
|
+
default_locale: Current.account&.default_locale&.code,
|
|
47
|
+
locales: export_locales,
|
|
48
|
+
block_definitions: export_block_definitions,
|
|
49
|
+
content_types: export_content_types
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def export_locales
|
|
54
|
+
Locale.order(:code).map { |l| { code: l.code, name: l.name } }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def export_block_definitions
|
|
58
|
+
BlockDefinition
|
|
59
|
+
.includes(:field_definitions)
|
|
60
|
+
.order(:handle)
|
|
61
|
+
.map(&:export_attributes)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def export_content_types
|
|
65
|
+
ContentType
|
|
66
|
+
.includes(:title_field_definition, :web_page_title_field_definition, :field_definitions)
|
|
67
|
+
.order(:handle)
|
|
68
|
+
.map(&:export_attributes)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def exportable_entries
|
|
72
|
+
Entry.includes(:content_type, :creator, fields: [ :locale, :definition ])
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
module Iron
|
|
2
|
+
class Importer
|
|
3
|
+
def initialize(path, include_schema: true, include_content: true)
|
|
4
|
+
@path = path.to_s
|
|
5
|
+
@include_schema = include_schema
|
|
6
|
+
@include_content = include_content
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def import
|
|
10
|
+
Zip::File.open(path) do |zip|
|
|
11
|
+
files_dir = extract_files(zip)
|
|
12
|
+
|
|
13
|
+
import_schema(zip) if schema?
|
|
14
|
+
import_content(zip, files_dir) if content?
|
|
15
|
+
ensure
|
|
16
|
+
FileUtils.rm_rf(files_dir) if files_dir
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
attr_reader :path
|
|
23
|
+
|
|
24
|
+
def schema? = @include_schema
|
|
25
|
+
def content? = @include_content
|
|
26
|
+
|
|
27
|
+
def extract_files(zip)
|
|
28
|
+
dir = Dir.mktmpdir("iron-import")
|
|
29
|
+
zip.glob("files/**/*").each do |entry|
|
|
30
|
+
next if entry.directory?
|
|
31
|
+
|
|
32
|
+
path = File.join(dir, entry.name.sub("files/", ""))
|
|
33
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
34
|
+
entry.extract(path)
|
|
35
|
+
end
|
|
36
|
+
dir
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def import_schema(zip)
|
|
40
|
+
return unless zip.find_entry("schema.json")
|
|
41
|
+
|
|
42
|
+
schema = read_schema(zip)
|
|
43
|
+
|
|
44
|
+
ActiveRecord::Base.transaction do
|
|
45
|
+
import_locales(schema[:locales] || [])
|
|
46
|
+
import_block_definitions(schema[:block_definitions] || [])
|
|
47
|
+
populate_block_field_definitions(schema[:block_definitions] || [])
|
|
48
|
+
import_content_types(schema[:content_types] || [])
|
|
49
|
+
resolve_content_type_references(schema[:content_types] || [])
|
|
50
|
+
restore_default_locale(schema[:default_locale])
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def import_content(zip, files_dir)
|
|
55
|
+
ActiveRecord::Base.transaction do
|
|
56
|
+
id_mapping = {}
|
|
57
|
+
entries_data = []
|
|
58
|
+
|
|
59
|
+
# First pass: create all entries
|
|
60
|
+
zip.glob("entries/**/*.json").each do |zip_entry|
|
|
61
|
+
attrs = parse_json(zip_entry.get_input_stream.read)
|
|
62
|
+
entries_data << attrs
|
|
63
|
+
entry = Entry.import_from_attributes(attrs, files_dir)
|
|
64
|
+
id_mapping[attrs[:id]] = entry if entry
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Second pass: resolve references
|
|
68
|
+
resolve_references(entries_data, id_mapping)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def resolve_references(entries_data, id_mapping)
|
|
73
|
+
entries_data.each do |attrs|
|
|
74
|
+
entry = id_mapping[attrs[:id]]
|
|
75
|
+
next unless entry
|
|
76
|
+
|
|
77
|
+
(attrs[:fields] || {}).each do |locale_code, field_data|
|
|
78
|
+
locale = Locale.find_by(code: locale_code.to_s)
|
|
79
|
+
next unless locale
|
|
80
|
+
|
|
81
|
+
resolve_references_in_fields(entry, locale, field_data, id_mapping)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def resolve_references_in_fields(entry, locale, fields_data, id_mapping, parent: nil)
|
|
87
|
+
fields_data.each do |handle, value_data|
|
|
88
|
+
next unless value_data.is_a?(Hash)
|
|
89
|
+
|
|
90
|
+
case value_data[:type]
|
|
91
|
+
when "reference"
|
|
92
|
+
resolve_reference_field(entry, locale, handle.to_s, value_data[:value], id_mapping, parent: parent)
|
|
93
|
+
when "reference_list"
|
|
94
|
+
resolve_reference_list_field(entry, locale, handle.to_s, value_data[:value], id_mapping, parent: parent)
|
|
95
|
+
when "block"
|
|
96
|
+
resolve_references_in_block(entry, locale, handle.to_s, value_data, id_mapping, parent: parent)
|
|
97
|
+
when "block_list"
|
|
98
|
+
resolve_references_in_block_list(entry, locale, handle.to_s, value_data, id_mapping, parent: parent)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def resolve_references_in_block(entry, locale, handle, value_data, id_mapping, parent: nil)
|
|
104
|
+
block_field = find_field(entry, locale, handle, parent)
|
|
105
|
+
return unless block_field
|
|
106
|
+
|
|
107
|
+
resolve_references_in_fields(entry, locale, value_data[:fields] || {}, id_mapping, parent: block_field)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def resolve_references_in_block_list(entry, locale, handle, value_data, id_mapping, parent: nil)
|
|
111
|
+
block_list_field = find_field(entry, locale, handle, parent)
|
|
112
|
+
return unless block_list_field
|
|
113
|
+
|
|
114
|
+
ordered_blocks = block_list_field.blocks.reload.sort_by(&:rank)
|
|
115
|
+
|
|
116
|
+
(value_data[:value] || []).each_with_index do |block_data, index|
|
|
117
|
+
block = ordered_blocks[index]
|
|
118
|
+
next unless block
|
|
119
|
+
|
|
120
|
+
resolve_references_in_fields(entry, locale, block_data[:fields] || {}, id_mapping, parent: block)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def find_field(entry, locale, handle, parent)
|
|
125
|
+
fields = parent&.fields&.reload || entry.fields.reload
|
|
126
|
+
definition_scope = parent.is_a?(Fields::Block) ? parent.block_definition.field_definitions : entry.content_type.field_definitions
|
|
127
|
+
definition = definition_scope.find_by(handle: handle)
|
|
128
|
+
return unless definition
|
|
129
|
+
|
|
130
|
+
fields.find { |f| f.definition_id == definition.id && f.locale_id == locale.id }
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def resolve_reference_field(entry, locale, handle, old_entry_id, id_mapping, parent: nil)
|
|
134
|
+
return unless old_entry_id
|
|
135
|
+
|
|
136
|
+
referenced_entry = id_mapping[old_entry_id]
|
|
137
|
+
return unless referenced_entry
|
|
138
|
+
|
|
139
|
+
field = find_field(entry, locale, handle, parent)
|
|
140
|
+
return unless field
|
|
141
|
+
|
|
142
|
+
field.update!(referenced_entry: referenced_entry)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def resolve_reference_list_field(entry, locale, handle, old_entry_ids, id_mapping, parent: nil)
|
|
146
|
+
return unless old_entry_ids.is_a?(Array)
|
|
147
|
+
|
|
148
|
+
field = find_field(entry, locale, handle, parent)
|
|
149
|
+
return unless field
|
|
150
|
+
|
|
151
|
+
old_entry_ids.each_with_index do |old_id, index|
|
|
152
|
+
referenced_entry = id_mapping[old_id]
|
|
153
|
+
next unless referenced_entry
|
|
154
|
+
|
|
155
|
+
field.references.create!(entry: referenced_entry, rank: index)
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def read_schema(zip)
|
|
160
|
+
parse_json(zip.read("schema.json"))
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def parse_json(content)
|
|
164
|
+
JSON.parse(content, symbolize_names: true)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def import_locales(locales)
|
|
168
|
+
locales.each do |attrs|
|
|
169
|
+
Locale.find_or_create_by!(code: attrs[:code]) do |locale|
|
|
170
|
+
locale.name = attrs[:name]
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def restore_default_locale(locale_code)
|
|
176
|
+
return unless locale_code
|
|
177
|
+
|
|
178
|
+
locale = Locale.find_by(code: locale_code)
|
|
179
|
+
Current.account.update!(default_locale: locale) if locale
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def import_block_definitions(definitions)
|
|
183
|
+
definitions.each { |attrs| BlockDefinition.import_from_attributes(attrs) }
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def populate_block_field_definitions(definitions)
|
|
187
|
+
definitions.each { |attrs| BlockDefinition.populate_field_definitions(attrs) }
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def import_content_types(definitions)
|
|
191
|
+
definitions.each { |attrs| ContentType.import_from_attributes(attrs) }
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def resolve_content_type_references(definitions)
|
|
195
|
+
definitions.each do |attrs|
|
|
196
|
+
content_type = ContentType.find_by(handle: attrs[:handle])
|
|
197
|
+
next unless content_type
|
|
198
|
+
|
|
199
|
+
if attrs[:title_field_handle].present?
|
|
200
|
+
field = content_type.field_definitions.find_by(handle: attrs[:title_field_handle])
|
|
201
|
+
content_type.update!(title_field_definition: field) if field
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
if attrs[:web_page_title_field_handle].present?
|
|
205
|
+
field = content_type.field_definitions.find_by(handle: attrs[:web_page_title_field_handle])
|
|
206
|
+
content_type.update!(web_page_title_field_definition: field) if field
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
module Iron
|
|
2
|
+
class Seed
|
|
3
|
+
DEFAULT_PATH = "db/seeds/iron.zip"
|
|
4
|
+
|
|
5
|
+
def self.dump(path = nil) = new(path).dump
|
|
6
|
+
def self.load(path = nil) = new(path).load
|
|
7
|
+
|
|
8
|
+
def dump
|
|
9
|
+
FileUtils.mkdir_p(path.dirname)
|
|
10
|
+
Exporter.new(path).export
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def load
|
|
14
|
+
return unless path.exist?
|
|
15
|
+
return if already_seeded?
|
|
16
|
+
|
|
17
|
+
user, account = bootstrap
|
|
18
|
+
Current.set(user: user, account: account) do
|
|
19
|
+
Importer.new(path).import
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private_class_method :new
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
attr_reader :path
|
|
28
|
+
|
|
29
|
+
def initialize(path)
|
|
30
|
+
@path = Pathname.new(path || Rails.root.join(DEFAULT_PATH))
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def already_seeded?
|
|
34
|
+
ContentType.any?
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def bootstrap
|
|
38
|
+
locale = Locale.first || Locale.create!(**default_locale_attributes)
|
|
39
|
+
account = Account.first || Account.create!(name: "Iron", default_locale: locale)
|
|
40
|
+
user = User.first || User.create!(
|
|
41
|
+
email_address: seed_email,
|
|
42
|
+
password: seed_password,
|
|
43
|
+
role: :administrator
|
|
44
|
+
)
|
|
45
|
+
[ user, account ]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def seed_email
|
|
49
|
+
ENV["IRON_SEED_EMAIL"] || default_credential("admin@example.com")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def seed_password
|
|
53
|
+
ENV["IRON_SEED_PASSWORD"] || default_credential("password")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def default_credential(value)
|
|
57
|
+
raise "IRON_SEED_EMAIL and IRON_SEED_PASSWORD must be set in #{Rails.env}" unless Rails.env.local?
|
|
58
|
+
value
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def default_locale_attributes
|
|
62
|
+
schema = read_seed_schema
|
|
63
|
+
locale_code = schema&.dig(:default_locale)
|
|
64
|
+
locale_attrs = schema&.dig(:locales)&.find { |l| l[:code] == locale_code }
|
|
65
|
+
locale_attrs&.slice(:code, :name) || { code: "en", name: "English" }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def read_seed_schema
|
|
69
|
+
return unless path.exist?
|
|
70
|
+
|
|
71
|
+
Zip::File.open(path.to_s) do |zip|
|
|
72
|
+
entry = zip.find_entry("schema.json")
|
|
73
|
+
JSON.parse(entry.get_input_stream.read, symbolize_names: true) if entry
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Iron::User::Deactivatable
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
included do
|
|
5
|
+
scope :active, -> { where(active: true) }
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def deactivate
|
|
9
|
+
if current?
|
|
10
|
+
errors.add(:base, :cannot_deactivate_self)
|
|
11
|
+
return false
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
transaction do
|
|
15
|
+
sessions.destroy_all
|
|
16
|
+
update!(active: false)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def reactivate
|
|
21
|
+
update!(active: true)
|
|
22
|
+
end
|
|
23
|
+
end
|
data/app/models/iron/user.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
module Iron
|
|
2
2
|
class User < ApplicationRecord
|
|
3
|
+
include Deactivatable
|
|
3
4
|
include Role
|
|
4
5
|
include Transferable
|
|
5
6
|
|
|
@@ -7,8 +8,6 @@ module Iron
|
|
|
7
8
|
|
|
8
9
|
validates :language, inclusion: { in: ADMIN_LANGUAGES }
|
|
9
10
|
|
|
10
|
-
before_destroy :ensure_not_current_user
|
|
11
|
-
|
|
12
11
|
has_secure_password
|
|
13
12
|
has_many :sessions, dependent: :destroy
|
|
14
13
|
has_many :exports, class_name: "Iron::Account::Export", dependent: :destroy
|
|
@@ -33,12 +32,5 @@ module Iron
|
|
|
33
32
|
def email_local_part
|
|
34
33
|
email_address.split("@").first
|
|
35
34
|
end
|
|
36
|
-
|
|
37
|
-
def ensure_not_current_user
|
|
38
|
-
if Current.user == self
|
|
39
|
-
errors.add(:base, :cannot_delete_self)
|
|
40
|
-
throw :abort
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
35
|
end
|
|
44
36
|
end
|
|
@@ -25,4 +25,29 @@
|
|
|
25
25
|
<%= render @users %>
|
|
26
26
|
</div>
|
|
27
27
|
</section>
|
|
28
|
+
|
|
29
|
+
<% if @deactivated_users.any? %>
|
|
30
|
+
<section>
|
|
31
|
+
<h2 class="text-sm font-medium text-stone-500 dark:text-stone-400 mb-3"><%= t(".deactivated") %></h2>
|
|
32
|
+
<div class="card card-list">
|
|
33
|
+
<% @deactivated_users.each do |user| %>
|
|
34
|
+
<div class="card-item flex items-center justify-between gap-4">
|
|
35
|
+
<div class="flex items-center gap-3 min-w-0 flex-1">
|
|
36
|
+
<%= avatar user, class: "size-9 bg-stone-100 text-stone-500 text-[14px] dark:bg-stone-700 dark:text-stone-300 opacity-50" %>
|
|
37
|
+
<div class="min-w-0">
|
|
38
|
+
<span class="block text-sm font-medium text-stone-400 dark:text-stone-500 truncate"><%= user.name %></span>
|
|
39
|
+
<p class="text-xs text-stone-400 dark:text-stone-500 truncate"><%= user.email_address %></p>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<div class="flex items-center gap-2 shrink-0">
|
|
44
|
+
<%= button_to user_reactivation_path(user), method: :patch, class: "button-ghost button-sm" do %>
|
|
45
|
+
<%= t(".reactivate") %>
|
|
46
|
+
<% end %>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
<% end %>
|
|
50
|
+
</div>
|
|
51
|
+
</section>
|
|
52
|
+
<% end %>
|
|
28
53
|
</div>
|
data/config/locales/en.yml
CHANGED
|
@@ -30,7 +30,7 @@ en:
|
|
|
30
30
|
iron/user:
|
|
31
31
|
attributes:
|
|
32
32
|
base:
|
|
33
|
-
|
|
33
|
+
cannot_deactivate_self: "You cannot delete your own account while logged in"
|
|
34
34
|
role:
|
|
35
35
|
cannot_change_own_role: "cannot be changed for your own account"
|
|
36
36
|
iron:
|
|
@@ -455,8 +455,10 @@ en:
|
|
|
455
455
|
count:
|
|
456
456
|
one: "1 member"
|
|
457
457
|
other: "%{count} members"
|
|
458
|
+
deactivated: "Deactivated"
|
|
458
459
|
heading: "Users"
|
|
459
460
|
invite: "Invite"
|
|
461
|
+
reactivate: "Reactivate"
|
|
460
462
|
team: "Team"
|
|
461
463
|
title: "Users"
|
|
462
464
|
invite:
|
|
@@ -475,6 +477,7 @@ en:
|
|
|
475
477
|
title: "Join %{account}"
|
|
476
478
|
notifications:
|
|
477
479
|
password_changed: "Password changed."
|
|
480
|
+
reactivated: "User was successfully reactivated."
|
|
478
481
|
removed: "User was successfully removed."
|
|
479
482
|
roles:
|
|
480
483
|
administrator: "Administrator"
|
data/config/locales/it.yml
CHANGED
|
@@ -69,7 +69,7 @@ it:
|
|
|
69
69
|
iron/user:
|
|
70
70
|
attributes:
|
|
71
71
|
base:
|
|
72
|
-
|
|
72
|
+
cannot_deactivate_self: "Non puoi eliminare il tuo account mentre sei autenticato"
|
|
73
73
|
role:
|
|
74
74
|
cannot_change_own_role: "non può essere cambiato per il tuo account"
|
|
75
75
|
iron:
|
|
@@ -494,8 +494,10 @@ it:
|
|
|
494
494
|
count:
|
|
495
495
|
one: "1 membro"
|
|
496
496
|
other: "%{count} membri"
|
|
497
|
+
deactivated: "Disattivati"
|
|
497
498
|
heading: "Utenti"
|
|
498
499
|
invite: "Invita"
|
|
500
|
+
reactivate: "Riattiva"
|
|
499
501
|
team: "Team"
|
|
500
502
|
title: "Utenti"
|
|
501
503
|
invite:
|
|
@@ -514,6 +516,7 @@ it:
|
|
|
514
516
|
title: "Unisciti a %{account}"
|
|
515
517
|
notifications:
|
|
516
518
|
password_changed: "Password cambiata."
|
|
519
|
+
reactivated: "Utente riattivato con successo."
|
|
517
520
|
removed: "Utente rimosso con successo."
|
|
518
521
|
roles:
|
|
519
522
|
administrator: "Amministratore"
|
data/config/routes.rb
CHANGED
data/lib/iron/version.rb
CHANGED
data/lib/tasks/iron_tasks.rake
CHANGED
|
@@ -1,24 +1,15 @@
|
|
|
1
1
|
namespace :iron do
|
|
2
|
-
namespace :
|
|
3
|
-
desc "
|
|
4
|
-
task
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
content_path = fixture_dir.join("content.tar.gz")
|
|
10
|
-
|
|
11
|
-
puts "Exporting Iron schema to #{schema_path}..."
|
|
12
|
-
schema_tar = Iron::SchemaExporter.export
|
|
13
|
-
File.binwrite(schema_path, schema_tar)
|
|
14
|
-
|
|
15
|
-
puts "Exporting Iron content to #{content_path}..."
|
|
16
|
-
content_archive = Iron::ContentExport.export
|
|
17
|
-
content_archive.to_file(content_path)
|
|
2
|
+
namespace :seed do
|
|
3
|
+
desc "Dump CMS schema and content to db/seeds/iron.zip"
|
|
4
|
+
task dump: :environment do
|
|
5
|
+
path = ENV["SEED_PATH"]
|
|
6
|
+
Iron::Seed.dump(path)
|
|
7
|
+
puts "Iron CMS seed exported to #{path || Rails.root.join(Iron::Seed::DEFAULT_PATH)}"
|
|
8
|
+
end
|
|
18
9
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
10
|
+
desc "Load CMS schema and content from db/seeds/iron.zip"
|
|
11
|
+
task load: :environment do
|
|
12
|
+
Iron::Seed.load(ENV["SEED_PATH"])
|
|
22
13
|
end
|
|
23
14
|
end
|
|
24
15
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: iron-cms
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.13.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Massimo De Marchi
|
|
@@ -286,6 +286,7 @@ files:
|
|
|
286
286
|
- app/controllers/iron/users/emails_controller.rb
|
|
287
287
|
- app/controllers/iron/users/languages_controller.rb
|
|
288
288
|
- app/controllers/iron/users/passwords_controller.rb
|
|
289
|
+
- app/controllers/iron/users/reactivations_controller.rb
|
|
289
290
|
- app/controllers/iron/users/roles_controller.rb
|
|
290
291
|
- app/controllers/iron/users_controller.rb
|
|
291
292
|
- app/helpers/iron/application_helper.rb
|
|
@@ -351,6 +352,7 @@ files:
|
|
|
351
352
|
- app/models/iron/entry/schemable.rb
|
|
352
353
|
- app/models/iron/entry/titlable.rb
|
|
353
354
|
- app/models/iron/entry/web_publishable.rb
|
|
355
|
+
- app/models/iron/exporter.rb
|
|
354
356
|
- app/models/iron/field.rb
|
|
355
357
|
- app/models/iron/field/belongs_to_entry.rb
|
|
356
358
|
- app/models/iron/field_definition.rb
|
|
@@ -380,11 +382,14 @@ files:
|
|
|
380
382
|
- app/models/iron/fields/text_field.rb
|
|
381
383
|
- app/models/iron/first_run.rb
|
|
382
384
|
- app/models/iron/icon_catalog.rb
|
|
385
|
+
- app/models/iron/importer.rb
|
|
383
386
|
- app/models/iron/locale.rb
|
|
384
387
|
- app/models/iron/qr_code_link.rb
|
|
385
388
|
- app/models/iron/reference.rb
|
|
389
|
+
- app/models/iron/seed.rb
|
|
386
390
|
- app/models/iron/session.rb
|
|
387
391
|
- app/models/iron/user.rb
|
|
392
|
+
- app/models/iron/user/deactivatable.rb
|
|
388
393
|
- app/models/iron/user/role.rb
|
|
389
394
|
- app/models/iron/user/transferable.rb
|
|
390
395
|
- app/views/active_storage/blobs/_blob.html.erb
|
|
@@ -517,6 +522,7 @@ files:
|
|
|
517
522
|
- db/migrate/20251209103109_create_iron_account_exports.rb
|
|
518
523
|
- db/migrate/20251209103110_create_iron_account_imports.rb
|
|
519
524
|
- db/migrate/20260207103057_add_language_to_iron_users.rb
|
|
525
|
+
- db/migrate/20260209215027_add_active_to_iron_users.rb
|
|
520
526
|
- lib/generators/iron/pages/pages_generator.rb
|
|
521
527
|
- lib/generators/iron/pages/templates/pages_controller.rb
|
|
522
528
|
- lib/generators/iron/pages/templates/show.html.erb
|