iron-cms 0.1.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 (240) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +174 -0
  4. data/Rakefile +8 -0
  5. data/app/assets/builds/iron.css +2 -0
  6. data/app/assets/config/iron_manifest.js +1 -0
  7. data/app/assets/images/iron/icons/sprite.svg +9425 -0
  8. data/app/assets/tailwind/iron/actiontext.css +550 -0
  9. data/app/assets/tailwind/iron/application.css +66 -0
  10. data/app/assets/tailwind/iron/components/button.css +74 -0
  11. data/app/assets/tailwind/iron/components/dropdown.css +42 -0
  12. data/app/assets/tailwind/iron/components/fieldset.css +18 -0
  13. data/app/assets/tailwind/iron/components/form.css +14 -0
  14. data/app/assets/tailwind/iron/components/icon-picker.css +9 -0
  15. data/app/assets/tailwind/iron/components/input.css +44 -0
  16. data/app/assets/tailwind/iron/components/page.css +7 -0
  17. data/app/assets/tailwind/iron/components/select.css +36 -0
  18. data/app/assets/tailwind/iron/components/sidebar.css +21 -0
  19. data/app/assets/tailwind/iron/components/textarea.css +27 -0
  20. data/app/controllers/concerns/iron/authentication.rb +66 -0
  21. data/app/controllers/concerns/iron/authorization.rb +11 -0
  22. data/app/controllers/concerns/iron/locale_aware.rb +20 -0
  23. data/app/controllers/concerns/iron/web_page.rb +18 -0
  24. data/app/controllers/iron/application_controller.rb +7 -0
  25. data/app/controllers/iron/block_definitions/field_definitions_controller.rb +8 -0
  26. data/app/controllers/iron/block_definitions_controller.rb +52 -0
  27. data/app/controllers/iron/blocks_controller.rb +12 -0
  28. data/app/controllers/iron/content_types/field_definitions_controller.rb +8 -0
  29. data/app/controllers/iron/content_types_controller.rb +52 -0
  30. data/app/controllers/iron/entries_controller.rb +103 -0
  31. data/app/controllers/iron/field_definitions_controller.rb +55 -0
  32. data/app/controllers/iron/first_runs_controller.rb +29 -0
  33. data/app/controllers/iron/home_controller.rb +6 -0
  34. data/app/controllers/iron/icons_controller.rb +10 -0
  35. data/app/controllers/iron/locales_controller.rb +52 -0
  36. data/app/controllers/iron/passwords_controller.rb +36 -0
  37. data/app/controllers/iron/references_controller.rb +11 -0
  38. data/app/controllers/iron/schemas_controller.rb +32 -0
  39. data/app/controllers/iron/sessions_controller.rb +32 -0
  40. data/app/controllers/iron/settings_controller.rb +23 -0
  41. data/app/controllers/iron/users_controller.rb +66 -0
  42. data/app/helpers/iron/application_helper.rb +25 -0
  43. data/app/helpers/iron/avatar_helper.rb +35 -0
  44. data/app/helpers/iron/block_helper.rb +4 -0
  45. data/app/helpers/iron/buttons_helper.rb +14 -0
  46. data/app/helpers/iron/components/badge_helper.rb +70 -0
  47. data/app/helpers/iron/components/dropdown_helper.rb +161 -0
  48. data/app/helpers/iron/content_types_helper.rb +11 -0
  49. data/app/helpers/iron/entries_helper.rb +13 -0
  50. data/app/helpers/iron/field_definitions_helper.rb +5 -0
  51. data/app/helpers/iron/form_builder.rb +43 -0
  52. data/app/helpers/iron/icons_helper.rb +30 -0
  53. data/app/helpers/iron/image_helper.rb +50 -0
  54. data/app/javascript/iron/application.js +6 -0
  55. data/app/javascript/iron/controllers/application.js +9 -0
  56. data/app/javascript/iron/controllers/element_controller.js +11 -0
  57. data/app/javascript/iron/controllers/file_upload_controller.js +18 -0
  58. data/app/javascript/iron/controllers/form_controller.js +7 -0
  59. data/app/javascript/iron/controllers/handle_controller.js +27 -0
  60. data/app/javascript/iron/controllers/icon_picker_controller.js +48 -0
  61. data/app/javascript/iron/controllers/index.js +4 -0
  62. data/app/javascript/iron/controllers/sortable_list_controller.js +125 -0
  63. data/app/javascript/iron/controllers/toggle_controller.js +14 -0
  64. data/app/javascript/iron/controllers/trix_controller.js +19 -0
  65. data/app/javascript/iron/lib/lexorank.js +51 -0
  66. data/app/jobs/iron/application_job.rb +4 -0
  67. data/app/jobs/iron/generate_entry_routes_job.rb +15 -0
  68. data/app/mailers/iron/application_mailer.rb +6 -0
  69. data/app/mailers/passwords_mailer.rb +6 -0
  70. data/app/models/concerns/iron/csv_serializable.rb +28 -0
  71. data/app/models/iron/account/joinable.rb +16 -0
  72. data/app/models/iron/account.rb +13 -0
  73. data/app/models/iron/application_record.rb +5 -0
  74. data/app/models/iron/block_definition/portable.rb +20 -0
  75. data/app/models/iron/block_definition.rb +10 -0
  76. data/app/models/iron/content_type/field_queryable.rb +101 -0
  77. data/app/models/iron/content_type/portable.rb +30 -0
  78. data/app/models/iron/content_type/titlable.rb +13 -0
  79. data/app/models/iron/content_type/web_publishable.rb +47 -0
  80. data/app/models/iron/content_type.rb +39 -0
  81. data/app/models/iron/content_types/collection.rb +6 -0
  82. data/app/models/iron/content_types/single.rb +9 -0
  83. data/app/models/iron/current.rb +12 -0
  84. data/app/models/iron/entry/deep_validation.rb +32 -0
  85. data/app/models/iron/entry/presentable.rb +40 -0
  86. data/app/models/iron/entry/schemable.rb +16 -0
  87. data/app/models/iron/entry/titlable.rb +35 -0
  88. data/app/models/iron/entry/web_publishable.rb +85 -0
  89. data/app/models/iron/entry.rb +16 -0
  90. data/app/models/iron/field/belongs_to_entry.rb +29 -0
  91. data/app/models/iron/field.rb +20 -0
  92. data/app/models/iron/field_definition/portable.rb +36 -0
  93. data/app/models/iron/field_definition.rb +42 -0
  94. data/app/models/iron/field_definitions/block.rb +23 -0
  95. data/app/models/iron/field_definitions/block_list.rb +8 -0
  96. data/app/models/iron/field_definitions/boolean.rb +7 -0
  97. data/app/models/iron/field_definitions/date.rb +7 -0
  98. data/app/models/iron/field_definitions/file.rb +67 -0
  99. data/app/models/iron/field_definitions/number.rb +7 -0
  100. data/app/models/iron/field_definitions/reference.rb +8 -0
  101. data/app/models/iron/field_definitions/reference_list.rb +8 -0
  102. data/app/models/iron/field_definitions/rich_text_area.rb +4 -0
  103. data/app/models/iron/field_definitions/text_area.rb +7 -0
  104. data/app/models/iron/field_definitions/text_field.rb +25 -0
  105. data/app/models/iron/fields/block.rb +40 -0
  106. data/app/models/iron/fields/block_list.rb +16 -0
  107. data/app/models/iron/fields/boolean.rb +7 -0
  108. data/app/models/iron/fields/date.rb +7 -0
  109. data/app/models/iron/fields/file.rb +29 -0
  110. data/app/models/iron/fields/number.rb +7 -0
  111. data/app/models/iron/fields/reference.rb +14 -0
  112. data/app/models/iron/fields/reference_list.rb +17 -0
  113. data/app/models/iron/fields/rich_text_area.rb +9 -0
  114. data/app/models/iron/fields/text_area.rb +7 -0
  115. data/app/models/iron/fields/text_field.rb +19 -0
  116. data/app/models/iron/first_run.rb +13 -0
  117. data/app/models/iron/icon_catalog.rb +45 -0
  118. data/app/models/iron/locale.rb +30 -0
  119. data/app/models/iron/reference.rb +8 -0
  120. data/app/models/iron/schema_archive.rb +71 -0
  121. data/app/models/iron/schema_exporter.rb +15 -0
  122. data/app/models/iron/schema_importer.rb +278 -0
  123. data/app/models/iron/session.rb +5 -0
  124. data/app/models/iron/user/role.rb +21 -0
  125. data/app/models/iron/user.rb +37 -0
  126. data/app/views/active_storage/blobs/_blob.html.erb +14 -0
  127. data/app/views/iron/block_definitions/_block_definition.html.erb +21 -0
  128. data/app/views/iron/block_definitions/_form.html.erb +46 -0
  129. data/app/views/iron/block_definitions/edit.html.erb +8 -0
  130. data/app/views/iron/block_definitions/index.html.erb +18 -0
  131. data/app/views/iron/block_definitions/new.html.erb +8 -0
  132. data/app/views/iron/block_definitions/show.html.erb +34 -0
  133. data/app/views/iron/blocks/new.turbo_stream.erb +6 -0
  134. data/app/views/iron/content_types/_content_type.html.erb +21 -0
  135. data/app/views/iron/content_types/_form.html.erb +137 -0
  136. data/app/views/iron/content_types/edit.html.erb +8 -0
  137. data/app/views/iron/content_types/index.html.erb +28 -0
  138. data/app/views/iron/content_types/new.html.erb +8 -0
  139. data/app/views/iron/content_types/show.html.erb +49 -0
  140. data/app/views/iron/entries/_entry.html.erb +17 -0
  141. data/app/views/iron/entries/_entry_option.html.erb +19 -0
  142. data/app/views/iron/entries/_form.html.erb +63 -0
  143. data/app/views/iron/entries/edit.html.erb +52 -0
  144. data/app/views/iron/entries/entry.html.erb +21 -0
  145. data/app/views/iron/entries/fields/_block.html.erb +92 -0
  146. data/app/views/iron/entries/fields/_block_list.html.erb +45 -0
  147. data/app/views/iron/entries/fields/_boolean.html.erb +13 -0
  148. data/app/views/iron/entries/fields/_date.html.erb +11 -0
  149. data/app/views/iron/entries/fields/_file.html.erb +60 -0
  150. data/app/views/iron/entries/fields/_number.html.erb +11 -0
  151. data/app/views/iron/entries/fields/_reference.html.erb +12 -0
  152. data/app/views/iron/entries/fields/_reference_item.html.erb +28 -0
  153. data/app/views/iron/entries/fields/_reference_list.html.erb +44 -0
  154. data/app/views/iron/entries/fields/_rich_text_area.html.erb +13 -0
  155. data/app/views/iron/entries/fields/_text_area.html.erb +11 -0
  156. data/app/views/iron/entries/fields/_text_field.html.erb +28 -0
  157. data/app/views/iron/entries/index.html.erb +21 -0
  158. data/app/views/iron/entries/new.html.erb +8 -0
  159. data/app/views/iron/entries/search.html.erb +33 -0
  160. data/app/views/iron/field_definitions/_field_definition.html.erb +37 -0
  161. data/app/views/iron/field_definitions/block/_form.html.erb +16 -0
  162. data/app/views/iron/field_definitions/block_list/_form.html.erb +13 -0
  163. data/app/views/iron/field_definitions/boolean/_form.html.erb +2 -0
  164. data/app/views/iron/field_definitions/date/_form.html.erb +2 -0
  165. data/app/views/iron/field_definitions/edit.html.erb +16 -0
  166. data/app/views/iron/field_definitions/file/_form.html.erb +40 -0
  167. data/app/views/iron/field_definitions/index.html.erb +14 -0
  168. data/app/views/iron/field_definitions/layouts/_form.html.erb +47 -0
  169. data/app/views/iron/field_definitions/new.html.erb +22 -0
  170. data/app/views/iron/field_definitions/number/_form.html.erb +2 -0
  171. data/app/views/iron/field_definitions/reference/_form.html.erb +13 -0
  172. data/app/views/iron/field_definitions/reference_list/_form.html.erb +13 -0
  173. data/app/views/iron/field_definitions/rich_text_area/_form.html.erb +2 -0
  174. data/app/views/iron/field_definitions/text_area/_form.html.erb +2 -0
  175. data/app/views/iron/field_definitions/text_field/_form.html.erb +17 -0
  176. data/app/views/iron/first_runs/show.html.erb +44 -0
  177. data/app/views/iron/home/show.html.erb +2 -0
  178. data/app/views/iron/icons/index.html.erb +21 -0
  179. data/app/views/iron/locales/_form.html.erb +24 -0
  180. data/app/views/iron/locales/_locale.html.erb +15 -0
  181. data/app/views/iron/locales/edit.html.erb +8 -0
  182. data/app/views/iron/locales/index.html.erb +12 -0
  183. data/app/views/iron/locales/new.html.erb +8 -0
  184. data/app/views/iron/passwords/edit.html.erb +7 -0
  185. data/app/views/iron/passwords/new.html.erb +26 -0
  186. data/app/views/iron/passwords_mailer/reset.html.erb +4 -0
  187. data/app/views/iron/passwords_mailer/reset.text.erb +2 -0
  188. data/app/views/iron/published_pages/show.html.erb +10 -0
  189. data/app/views/iron/references/new.turbo_stream.erb +6 -0
  190. data/app/views/iron/schemas/new.html.erb +57 -0
  191. data/app/views/iron/sessions/new.html.erb +36 -0
  192. data/app/views/iron/settings/show.html.erb +38 -0
  193. data/app/views/iron/shared/_icon_picker.html.erb +60 -0
  194. data/app/views/iron/users/_form.html.erb +21 -0
  195. data/app/views/iron/users/_user.html.erb +27 -0
  196. data/app/views/iron/users/edit.html.erb +12 -0
  197. data/app/views/iron/users/index.html.erb +21 -0
  198. data/app/views/iron/users/new.html.erb +29 -0
  199. data/app/views/iron/users/show.html.erb +21 -0
  200. data/app/views/layouts/action_text/contents/_content.html.erb +3 -0
  201. data/app/views/layouts/iron/_sidebar.html.erb +83 -0
  202. data/app/views/layouts/iron/_sidebar_item.html.erb +14 -0
  203. data/app/views/layouts/iron/_toast.html.erb +29 -0
  204. data/app/views/layouts/iron/application.html.erb +46 -0
  205. data/app/views/layouts/iron/authentication.html.erb +41 -0
  206. data/config/importmap.rb +9 -0
  207. data/config/routes.rb +40 -0
  208. data/db/migrate/20250422131656_initial_iron_schema.rb +105 -0
  209. data/db/migrate/20250427100754_create_iron_accounts.rb +10 -0
  210. data/db/migrate/20250427104020_add_role_to_iron_users.rb +5 -0
  211. data/db/migrate/20250428094923_add_default_iron_locale_to_iron_account.rb +5 -0
  212. data/db/migrate/20250504144731_create_join_table_content_types_field_definitions.rb +25 -0
  213. data/db/migrate/20250505090716_add_title_field_definition_to_iron_content_types.rb +5 -0
  214. data/db/migrate/20250512210352_create_iron_references.rb +11 -0
  215. data/db/migrate/20250524220355_add_referenced_entry_id_to_iron_fields.rb +5 -0
  216. data/db/migrate/20250529211703_add_metadata_to_iron_field_definitions.rb +5 -0
  217. data/db/migrate/20250529211704_add_value_boolean_to_iron_fields.rb +5 -0
  218. data/db/migrate/20250531172837_add_type_to_iron_content_types.rb +5 -0
  219. data/db/migrate/20250601080146_add_icon_to_iron_content_types.rb +5 -0
  220. data/db/migrate/20250609091605_add_web_publishing_to_iron_content_types.rb +7 -0
  221. data/db/migrate/20250609091813_add_route_to_iron_entries.rb +6 -0
  222. data/lib/generators/iron/pages/pages_generator.rb +34 -0
  223. data/lib/generators/iron/pages/templates/pages_controller.rb +14 -0
  224. data/lib/generators/iron/pages/templates/show.html.erb +9 -0
  225. data/lib/generators/iron/template/template_generator.rb +35 -0
  226. data/lib/generators/iron/template/templates/content_type.html.erb +5 -0
  227. data/lib/iron/cva.rb +69 -0
  228. data/lib/iron/engine.rb +42 -0
  229. data/lib/iron/image_analyzer.rb +26 -0
  230. data/lib/iron/lexorank/exceptions.rb +4 -0
  231. data/lib/iron/lexorank/rankable.rb +185 -0
  232. data/lib/iron/lexorank/rebalance_rank_job.rb +10 -0
  233. data/lib/iron/lexorank/utils.rb +77 -0
  234. data/lib/iron/lexorank.rb +7 -0
  235. data/lib/iron/routing.rb +66 -0
  236. data/lib/iron/sdk.rb +19 -0
  237. data/lib/iron/version.rb +3 -0
  238. data/lib/iron.rb +8 -0
  239. data/lib/puma/plugin/iron_tailwindcss.rb +78 -0
  240. metadata +458 -0
@@ -0,0 +1,67 @@
1
+ module Iron
2
+ class FieldDefinitions::File < FieldDefinition
3
+ FILE_TYPE_PRESETS = {
4
+ "images" => {
5
+ extensions: %w[jpg jpeg png gif webp svg heic],
6
+ mime_types: %w[image/jpeg image/png image/gif image/webp image/svg+xml image/heic]
7
+ },
8
+ "videos" => {
9
+ extensions: %w[mp4 mov avi webm],
10
+ mime_types: %w[video/mp4 video/quicktime video/x-msvideo video/webm]
11
+ },
12
+ "documents" => {
13
+ extensions: %w[pdf doc docx xls xlsx ppt pptx txt],
14
+ mime_types: %w[
15
+ application/pdf
16
+ application/msword
17
+ application/vnd.openxmlformats-officedocument.wordprocessingml.document
18
+ application/vnd.ms-excel
19
+ application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
20
+ application/vnd.ms-powerpoint
21
+ application/vnd.openxmlformats-officedocument.presentationml.presentation
22
+ text/plain
23
+ ]
24
+ },
25
+ "audio" => {
26
+ extensions: %w[mp3 wav ogg m4a],
27
+ mime_types: %w[audio/mpeg audio/wav audio/ogg audio/mp4]
28
+ }
29
+ }.freeze
30
+
31
+ store_accessor :metadata, :file_scope, :selected_presets
32
+
33
+ def file_scope
34
+ super || "all"
35
+ end
36
+
37
+ def selected_presets
38
+ super || []
39
+ end
40
+
41
+ def accepted_extensions
42
+ return nil if file_scope == "all"
43
+
44
+ extensions = []
45
+ selected_presets.each do |preset|
46
+ extensions.concat(FILE_TYPE_PRESETS[preset][:extensions]) if FILE_TYPE_PRESETS[preset]
47
+ end
48
+ extensions.uniq
49
+ end
50
+
51
+ def accepted_mime_types
52
+ return nil if file_scope == "all"
53
+
54
+ mime_types = []
55
+ selected_presets.each do |preset|
56
+ mime_types.concat(FILE_TYPE_PRESETS[preset][:mime_types]) if FILE_TYPE_PRESETS[preset]
57
+ end
58
+ mime_types.uniq
59
+ end
60
+
61
+ def validation_description
62
+ return "All file types accepted" if file_scope == "all"
63
+
64
+ "Accepted formats: #{accepted_extensions.join(', ')}" if accepted_extensions.present?
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,7 @@
1
+ module Iron
2
+ class FieldDefinitions::Number < FieldDefinition
3
+ def value_column
4
+ :value_float
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,8 @@
1
+ module Iron
2
+ class FieldDefinitions::Reference < FieldDefinition
3
+ has_and_belongs_to_many :supported_content_types,
4
+ class_name: "::Iron::ContentType",
5
+ foreign_key: "field_definition_id",
6
+ association_foreign_key: "content_type_id"
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module Iron
2
+ class FieldDefinitions::ReferenceList < FieldDefinition
3
+ has_and_belongs_to_many :supported_content_types,
4
+ class_name: "::Iron::ContentType",
5
+ foreign_key: "field_definition_id",
6
+ association_foreign_key: "content_type_id"
7
+ end
8
+ end
@@ -0,0 +1,4 @@
1
+ module Iron
2
+ class FieldDefinitions::RichTextArea < FieldDefinition
3
+ end
4
+ end
@@ -0,0 +1,7 @@
1
+ module Iron
2
+ class FieldDefinitions::TextArea < FieldDefinition
3
+ def value_column
4
+ :value_text
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,25 @@
1
+ module Iron
2
+ class FieldDefinitions::TextField < FieldDefinition
3
+ store_accessor :metadata, :allowed_values, :required
4
+
5
+ def allowed_values_text
6
+ Array(allowed_values).join("\n")
7
+ end
8
+
9
+ def allowed_values_text=(text)
10
+ self.allowed_values = text.to_s.lines.map(&:strip).reject(&:blank?)
11
+ end
12
+
13
+ def required
14
+ ActiveModel::Type::Boolean.new.cast(super)
15
+ end
16
+
17
+ def required=(value)
18
+ super(ActiveModel::Type::Boolean.new.cast(value))
19
+ end
20
+
21
+ def value_column
22
+ :value_string
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,40 @@
1
+ module Iron
2
+ class Fields::Block < Field
3
+ has_many :fields, as: :parent, inverse_of: :parent, dependent: :destroy
4
+ accepts_nested_attributes_for :fields, allow_destroy: true
5
+
6
+ has_rank scoped_by: :parent
7
+
8
+ def block_list
9
+ parent.is_a?(Iron::Fields::BlockList) ? parent : nil
10
+ end
11
+
12
+ def find_or_build_field(definition)
13
+ fields.find { |f| f.definition == definition } || fields.build(type: definition.field_type, entry:, definition: definition, locale:)
14
+ end
15
+
16
+ def value
17
+ locale = Iron::Current.locale || Iron::Locale.default
18
+
19
+ block_definition = if definition.is_a?(Iron::FieldDefinitions::Block)
20
+ definition.supported_block_definition
21
+ else
22
+ definition
23
+ end
24
+
25
+ result = OpenStruct.new(id: id, _type: block_definition.handle)
26
+
27
+ block_definition.field_definitions.each do |field_definition|
28
+ field = fields.find do |f|
29
+ f.definition_id == field_definition.id &&
30
+ f.definition_type == field_definition.class.base_class.to_s &&
31
+ f.locale_id == locale.id
32
+ end
33
+
34
+ result[field_definition.handle] = field&.value
35
+ end
36
+
37
+ result
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,16 @@
1
+ module Iron
2
+ class Fields::BlockList < Field
3
+ has_many :blocks, -> { ranked }, as: :parent, inverse_of: :parent, dependent: :destroy do
4
+ def build_for(block_definition)
5
+ block_list = proxy_association.owner
6
+ build(entry: block_list.entry, definition: block_definition, locale: block_list.locale)
7
+ end
8
+ end
9
+
10
+ accepts_nested_attributes_for :blocks, allow_destroy: true
11
+
12
+ def value
13
+ blocks.map(&:value)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,7 @@
1
+ module Iron
2
+ class Fields::Boolean < Field
3
+ def value
4
+ value_boolean
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Iron
2
+ class Fields::Date < Field
3
+ def value
4
+ value_datetime
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,29 @@
1
+ module Iron
2
+ class Fields::File < Field
3
+ has_one_attached :file
4
+
5
+ validate :file_type_matches_restrictions, if: :definition_restricts_file_type?
6
+
7
+ def value
8
+ file.attached? ? file : nil
9
+ end
10
+
11
+ def image?
12
+ file.attached? && file.blob.content_type&.start_with?("image/")
13
+ end
14
+
15
+ private
16
+
17
+ def definition_restricts_file_type?
18
+ file.attached? &&
19
+ definition.file_scope == "specific" &&
20
+ definition.accepted_mime_types.present?
21
+ end
22
+
23
+ def file_type_matches_restrictions
24
+ unless file.content_type.in? definition.accepted_mime_types
25
+ errors.add(:base, "must be one of the accepted formats: #{definition.accepted_extensions.join(', ')}")
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,7 @@
1
+ module Iron
2
+ class Fields::Number < Field
3
+ def value
4
+ value_float
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,14 @@
1
+ module Iron
2
+ class Fields::Reference < Field
3
+ belongs_to :referenced_entry, class_name: "Iron::Entry", optional: true
4
+
5
+ def value
6
+ if referenced_entry.present?
7
+ OpenStruct.new(
8
+ id: referenced_entry_id,
9
+ _type: referenced_entry.content_type.handle
10
+ )
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,17 @@
1
+ module Iron
2
+ class Fields::ReferenceList < Field
3
+ has_many :references, -> { ranked }, foreign_key: :list_id, dependent: :destroy, class_name: "Iron::Reference", inverse_of: "list"
4
+ has_many :entries, through: :references
5
+
6
+ accepts_nested_attributes_for :references, allow_destroy: true
7
+
8
+ def value
9
+ references.map do |reference|
10
+ OpenStruct.new(
11
+ id: reference.entry_id,
12
+ _type: reference.entry.content_type.handle
13
+ )
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,9 @@
1
+ module Iron
2
+ class Fields::RichTextArea < Field
3
+ has_rich_text :rich_text
4
+
5
+ def value
6
+ rich_text&.to_s
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ module Iron
2
+ class Fields::TextArea < Field
3
+ def value
4
+ value_text
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,19 @@
1
+ module Iron
2
+ class Fields::TextField < Field
3
+ validate :validate_required_field
4
+
5
+ def value
6
+ value_string
7
+ end
8
+
9
+ private
10
+
11
+ def validate_required_field
12
+ return unless definition.required
13
+
14
+ if value_string.blank?
15
+ errors.add(:base, :blank)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,13 @@
1
+ module Iron
2
+ class FirstRun
3
+ ACCOUNT_NAME = "Iron"
4
+
5
+ def self.create!(user_params)
6
+ english_locale = Locale.create!(code: "en", name: "English")
7
+
8
+ account = Account.create!(name: ACCOUNT_NAME, default_locale: english_locale)
9
+
10
+ User.create!(user_params.merge(role: :administrator))
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,45 @@
1
+ module Iron
2
+ class IconCatalog
3
+ class << self
4
+ def all
5
+ @cached_icons ||= parse_sprite_icons
6
+ end
7
+
8
+ def clear_cache!
9
+ @cached_icons = nil
10
+ end
11
+
12
+ def refresh!
13
+ clear_cache!
14
+ all
15
+ end
16
+
17
+ def include?(icon_name)
18
+ all.include?(icon_name.to_s)
19
+ end
20
+
21
+ def cache_key
22
+ raise "Icon sprite file not found at #{sprite_file_path}" unless File.exist?(sprite_file_path)
23
+
24
+ mtime = File.mtime(sprite_file_path).to_i
25
+ "iron/icon_catalog/#{mtime}"
26
+ end
27
+
28
+ private
29
+
30
+ def parse_sprite_icons
31
+ return [] unless File.exist?(sprite_file_path)
32
+
33
+ sprite_content = File.read(sprite_file_path)
34
+ sprite_content.scan(/id="([^"]+)"/).flatten.sort
35
+ rescue => e
36
+ Rails.logger.warn "Failed to parse icon sprite: #{e.message}"
37
+ []
38
+ end
39
+
40
+ def sprite_file_path
41
+ Iron::Engine.root.join("app", "assets", "images", "iron", "icons", "sprite.svg")
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,30 @@
1
+ module Iron
2
+ class Locale < ApplicationRecord
3
+ validates :code, presence: true
4
+ validates :name, presence: true
5
+
6
+ before_destroy :ensure_not_default_locale
7
+
8
+ def self.detect(request)
9
+ locale_param = request.params[:locale]
10
+ Locale.find_by(code: locale_param) if locale_param.present?
11
+ end
12
+
13
+ def self.default
14
+ Account.first&.default_locale
15
+ end
16
+
17
+ def default?
18
+ self == Locale.default
19
+ end
20
+
21
+ private
22
+
23
+ def ensure_not_default_locale
24
+ if default?
25
+ errors.add(:base, "Cannot delete locale while it is set as default for an account")
26
+ throw :abort
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,8 @@
1
+ module Iron
2
+ class Reference < ApplicationRecord
3
+ belongs_to :entry
4
+ belongs_to :list, class_name: "Iron::Fields::ReferenceList", inverse_of: "references"
5
+
6
+ has_rank scoped_by: :list_id
7
+ end
8
+ end
@@ -0,0 +1,71 @@
1
+ require "csv"
2
+ require "rubygems/package"
3
+ require "stringio"
4
+
5
+ module Iron
6
+ class SchemaArchive
7
+ REQUIRED_FILES = %w[content_types.csv block_definitions.csv field_definitions.csv].freeze
8
+
9
+ attr_reader :errors
10
+
11
+ def self.from_file(file)
12
+ new(file.read)
13
+ end
14
+
15
+ def initialize(tar_content = nil)
16
+ @tar_content = tar_content
17
+ @csv_files = {}
18
+ @errors = []
19
+ extract_files if tar_content
20
+ end
21
+
22
+ def valid?
23
+ return false if @tar_content.nil?
24
+
25
+ missing_files = REQUIRED_FILES - @csv_files.keys
26
+ if missing_files.any?
27
+ @errors << "Missing required files: #{missing_files.join(', ')}"
28
+ end
29
+
30
+ @errors.empty?
31
+ end
32
+
33
+ def [](filename)
34
+ @csv_files[filename]
35
+ end
36
+
37
+ def add_file(filename, csv_content)
38
+ @csv_files[filename] = csv_content
39
+ end
40
+
41
+ def to_tar
42
+ tarfile = StringIO.new
43
+
44
+ Gem::Package::TarWriter.new(tarfile) do |tar|
45
+ @csv_files.each do |filename, content|
46
+ tar.add_file_simple(filename, 0644, content.bytesize) do |io|
47
+ io.write(content)
48
+ end
49
+ end
50
+ end
51
+
52
+ tarfile.string
53
+ end
54
+
55
+ private
56
+
57
+ def extract_files
58
+ tar_io = StringIO.new(@tar_content)
59
+
60
+ Gem::Package::TarReader.new(tar_io) do |tar|
61
+ tar.each do |entry|
62
+ if entry.file? && entry.full_name.end_with?(".csv")
63
+ @csv_files[entry.full_name] = entry.read
64
+ end
65
+ end
66
+ end
67
+ rescue StandardError => e
68
+ @errors << "Failed to extract tar archive: #{e.message}"
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,15 @@
1
+ module Iron
2
+ class SchemaExporter
3
+ def self.export
4
+ new.export
5
+ end
6
+
7
+ def export
8
+ archive = SchemaArchive.new
9
+ archive.add_file("content_types.csv", ContentType.to_csv)
10
+ archive.add_file("block_definitions.csv", BlockDefinition.to_csv)
11
+ archive.add_file("field_definitions.csv", FieldDefinition.to_csv)
12
+ archive.to_tar
13
+ end
14
+ end
15
+ end