rivet_cms 0.1.2.pre → 0.3.1

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 (127) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +63 -0
  3. data/COPYING +674 -0
  4. data/COPYING.LESSER +165 -0
  5. data/README.md +173 -10
  6. data/app/assets/builds/rivet_cms.css +2 -2
  7. data/app/assets/builds/rivet_cms.js +395 -9505
  8. data/app/assets/stylesheets/rivet_cms/application.tailwind.css +145 -22
  9. data/app/controllers/concerns/rivet_cms/inertia_props.rb +239 -0
  10. data/app/controllers/rivet_cms/api_docs_controller.rb +47 -0
  11. data/app/controllers/rivet_cms/api_tokens_controller.rb +43 -0
  12. data/app/controllers/rivet_cms/application_controller.rb +220 -2
  13. data/app/controllers/rivet_cms/auth_controller.rb +28 -0
  14. data/app/controllers/rivet_cms/components_controller.rb +93 -2
  15. data/app/controllers/rivet_cms/content_controller.rb +105 -0
  16. data/app/controllers/rivet_cms/content_manager_controller.rb +32 -0
  17. data/app/controllers/rivet_cms/content_types_controller.rb +118 -26
  18. data/app/controllers/rivet_cms/dashboard_controller.rb +55 -5
  19. data/app/controllers/rivet_cms/documents_controller.rb +204 -0
  20. data/app/controllers/rivet_cms/fields_controller.rb +77 -74
  21. data/app/controllers/rivet_cms/invitations_controller.rb +36 -0
  22. data/app/controllers/rivet_cms/media_assets_controller.rb +76 -0
  23. data/app/controllers/rivet_cms/sessions_controller.rb +122 -0
  24. data/app/controllers/rivet_cms/setup_controller.rb +60 -0
  25. data/app/controllers/rivet_cms/trash_controller.rb +68 -0
  26. data/app/controllers/rivet_cms/users_controller.rb +117 -0
  27. data/app/helpers/rivet_cms/application_helper.rb +13 -3
  28. data/app/helpers/rivet_cms/components_helper.rb +4 -0
  29. data/app/helpers/rivet_cms/content_types_helper.rb +4 -0
  30. data/app/helpers/rivet_cms/dashboard_helper.rb +4 -0
  31. data/app/jobs/rivet_cms/webhook_delivery_job.rb +35 -0
  32. data/app/models/concerns/rivet_cms/has_fields.rb +31 -0
  33. data/app/models/concerns/rivet_cms/organization_scoped.rb +17 -0
  34. data/app/models/concerns/rivet_cms/revision_owned.rb +36 -0
  35. data/app/models/concerns/rivet_cms/sluggable.rb +38 -0
  36. data/app/models/concerns/rivet_cms/soft_deletable.rb +42 -0
  37. data/app/models/concerns/rivet_cms/typed_value.rb +51 -0
  38. data/app/models/rivet_cms/api_token.rb +64 -0
  39. data/app/models/rivet_cms/category.rb +20 -0
  40. data/app/models/rivet_cms/component.rb +10 -0
  41. data/app/models/rivet_cms/component_instance.rb +27 -0
  42. data/app/models/rivet_cms/content_type.rb +36 -23
  43. data/app/models/rivet_cms/content_value.rb +19 -0
  44. data/app/models/rivet_cms/current.rb +6 -0
  45. data/app/models/rivet_cms/document.rb +71 -0
  46. data/app/models/rivet_cms/document_revision.rb +128 -0
  47. data/app/models/rivet_cms/entry.rb +73 -0
  48. data/app/models/rivet_cms/entry_collection.rb +41 -0
  49. data/app/models/rivet_cms/field.rb +207 -61
  50. data/app/models/rivet_cms/media_asset.rb +109 -0
  51. data/app/models/rivet_cms/organization.rb +46 -0
  52. data/app/models/rivet_cms/relation.rb +23 -0
  53. data/app/models/rivet_cms/user.rb +61 -0
  54. data/app/serializers/rivet_cms/revision_preloader.rb +146 -0
  55. data/app/serializers/rivet_cms/revision_serializer.rb +123 -0
  56. data/app/services/rivet_cms/content_query.rb +158 -0
  57. data/app/services/rivet_cms/content_validator.rb +150 -0
  58. data/app/services/rivet_cms/draft_writer.rb +74 -0
  59. data/app/services/rivet_cms/open_api_generator.rb +173 -221
  60. data/app/services/rivet_cms/revision_pruner.rb +64 -0
  61. data/app/services/rivet_cms/webhooks.rb +61 -0
  62. data/app/views/layouts/rivet_cms/application.html.erb +35 -42
  63. data/config/routes.rb +68 -45
  64. data/db/migrate/20251119193607_create_rivet_cms_organizations.rb +17 -0
  65. data/db/migrate/20251120050818_create_rivet_cms_content_types.rb +14 -0
  66. data/db/migrate/20251121020625_create_rivet_cms_components.rb +17 -0
  67. data/db/migrate/20251121040115_create_rivet_cms_categories.rb +17 -0
  68. data/db/migrate/20260119185946_create_content_system_tables.rb +97 -0
  69. data/db/migrate/20260722000001_create_rivet_cms_media_assets.rb +15 -0
  70. data/db/migrate/20260722000002_create_rivet_cms_api_tokens.rb +16 -0
  71. data/db/migrate/20260806000001_add_title_and_alt_to_rivet_cms_media_assets.rb +7 -0
  72. data/db/migrate/20260806000002_add_deleted_at_to_rivet_cms_content_types.rb +6 -0
  73. data/db/migrate/20260807000001_add_deleted_at_to_rivet_cms_documents.rb +6 -0
  74. data/db/migrate/20260808000001_create_rivet_cms_users.rb +15 -0
  75. data/db/seeds/README.md +70 -0
  76. data/db/seeds/templates/blog.rb +39 -0
  77. data/db/seeds/templates/events.rb +12 -0
  78. data/db/seeds/templates/faq.rb +9 -0
  79. data/db/seeds/templates/pages.rb +35 -0
  80. data/db/seeds/templates/site_settings.rb +22 -0
  81. data/db/seeds/templates/team.rb +11 -0
  82. data/db/seeds/templates/testimonials.rb +11 -0
  83. data/lib/generators/rivet_cms/install/install_generator.rb +13 -0
  84. data/lib/generators/rivet_cms/install/templates/initializer.rb +77 -0
  85. data/lib/rivet_cms/access_check.rb +12 -0
  86. data/lib/rivet_cms/audit.rb +43 -0
  87. data/lib/rivet_cms/engine.rb +28 -22
  88. data/lib/rivet_cms/hooks.rb +106 -0
  89. data/lib/rivet_cms/navigation.rb +111 -0
  90. data/lib/rivet_cms/safe_pattern.rb +40 -0
  91. data/lib/rivet_cms/seeds.rb +264 -0
  92. data/lib/rivet_cms/version.rb +1 -1
  93. data/lib/rivet_cms.rb +289 -32
  94. data/lib/tasks/rivet_cms_tasks.rake +18 -4
  95. metadata +138 -98
  96. data/MIT-LICENSE +0 -20
  97. data/app/assets/builds/rivet_cms.js.map +0 -7
  98. data/app/assets/stylesheets/rivet_cms/brand_colors.css +0 -168
  99. data/app/controllers/rivet_cms/api/docs_controller.rb +0 -38
  100. data/app/helpers/rivet_cms/brand_color_helper.rb +0 -71
  101. data/app/helpers/rivet_cms/flash_helper.rb +0 -37
  102. data/app/helpers/rivet_cms/sign_out_helper.rb +0 -11
  103. data/app/javascript/controllers/content_type_form_controller.js +0 -53
  104. data/app/javascript/controllers/field_layout_controller.js +0 -709
  105. data/app/javascript/rivet_cms.js +0 -29
  106. data/app/models/rivet_cms/content.rb +0 -4
  107. data/app/models/rivet_cms/field_values/base.rb +0 -11
  108. data/app/models/rivet_cms/field_values/boolean.rb +0 -4
  109. data/app/models/rivet_cms/field_values/integer.rb +0 -4
  110. data/app/models/rivet_cms/field_values/string.rb +0 -4
  111. data/app/models/rivet_cms/field_values/text.rb +0 -4
  112. data/app/views/rivet_cms/api/docs/show.html.erb +0 -47
  113. data/app/views/rivet_cms/content_types/_form.html.erb +0 -98
  114. data/app/views/rivet_cms/content_types/edit.html.erb +0 -27
  115. data/app/views/rivet_cms/content_types/index.html.erb +0 -151
  116. data/app/views/rivet_cms/content_types/new.html.erb +0 -19
  117. data/app/views/rivet_cms/content_types/show.html.erb +0 -147
  118. data/app/views/rivet_cms/dashboard/index.html.erb +0 -263
  119. data/app/views/rivet_cms/fields/_form.html.erb +0 -111
  120. data/app/views/rivet_cms/fields/edit.html.erb +0 -25
  121. data/app/views/rivet_cms/fields/index.html.erb +0 -126
  122. data/app/views/rivet_cms/fields/new.html.erb +0 -25
  123. data/app/views/rivet_cms/shared/_navigation.html.erb +0 -153
  124. data/config/i18n-tasks.yml +0 -178
  125. data/config/locales/en.yml +0 -14
  126. data/db/migrate/20250317194359_create_core_tables.rb +0 -90
  127. data/lib/rivet_cms/routes.rb +0 -49
data/config/routes.rb CHANGED
@@ -1,58 +1,81 @@
1
1
  RivetCms::Engine.routes.draw do
2
- RivetCms::Routes.add_routes do
3
- get '/', to: "dashboard#index"
4
-
5
- # Content Type Management
6
- resources :content_types, path: 'content-types' do
7
- resources :fields, except: [:show] do
8
- collection do
9
- post :update_positions
10
- end
11
- member do
12
- patch :update_width
13
- end
14
- end
15
- resources :contents
2
+ root to: "dashboard#show"
3
+
4
+ # Built-in authentication; these controllers 404 when the host wires its own
5
+ get "login" => "sessions#new", as: :login
6
+ post "login" => "sessions#create"
7
+ delete "logout" => "sessions#destroy", as: :logout
8
+ get "setup" => "setup#new", as: :setup
9
+ post "setup" => "setup#create"
10
+ get "join/:token" => "invitations#show", as: :invitation
11
+ patch "join/:token" => "invitations#update"
12
+
13
+ resources :users, only: [ :index, :create, :update ] do
14
+ member do
15
+ patch :deactivate
16
+ patch :reactivate
17
+ post :reset_link
16
18
  end
19
+ end
17
20
 
18
- # Component Management
19
- resources :components do
20
- resources :fields, except: [:show] do
21
- collection do
22
- post :update_positions
23
- end
21
+ resources :content_types, except: [ :edit ] do
22
+ collection do
23
+ get :trash
24
+ end
25
+ member do
26
+ patch :restore
27
+ delete :purge
28
+ end
29
+ resources :fields, only: [ :create, :update, :destroy ] do
30
+ member do
31
+ patch :toggle_width
32
+ patch :unpair
33
+ patch :pair
34
+ end
35
+ collection do
36
+ post :update_layout
24
37
  end
25
38
  end
39
+ end
26
40
 
27
- # API Documentation
28
- namespace :api do
29
- resource :docs, only: [:show]
41
+ resources :components, except: [ :edit ] do
42
+ collection do
43
+ post :create_category
30
44
  end
45
+ resources :fields, only: [ :create, :update, :destroy ] do
46
+ member do
47
+ patch :toggle_width
48
+ patch :unpair
49
+ patch :pair
50
+ end
51
+ collection do
52
+ post :update_layout
53
+ end
54
+ end
55
+ end
31
56
 
32
- # Helper method to check if content type exists and matches the collection/single type
33
- content_type_exists = ->(slug, is_collection) {
34
- RivetCMS::ContentType.exists?(slug: slug, is_single: !is_collection)
35
- }
36
-
37
- # API routes with versioning
38
- namespace :api do
39
- namespace :v1 do
40
- # Dynamic routes for content types
41
- get ':slug', to: 'content#index', constraints: ->(req) { content_type_exists.call(req.params[:slug], true) }
42
- post ':slug', to: 'content#create', constraints: ->(req) { content_type_exists.call(req.params[:slug], true) }
43
- get ':slug/:id', to: 'content#show', constraints: ->(req) { content_type_exists.call(req.params[:slug], true) }
44
- put ':slug/:id', to: 'content#update', constraints: ->(req) { content_type_exists.call(req.params[:slug], true) }
45
- patch ':slug/:id', to: 'content#update', constraints: ->(req) { content_type_exists.call(req.params[:slug], true) }
46
- delete ':slug/:id', to: 'content#destroy', constraints: ->(req) { content_type_exists.call(req.params[:slug], true) }
47
-
48
- # Routes for single type content
49
- get ':slug', to: 'single#show', constraints: ->(req) { content_type_exists.call(req.params[:slug], false) }
50
- put ':slug', to: 'single#update', constraints: ->(req) { content_type_exists.call(req.params[:slug], false) }
51
- patch ':slug', to: 'single#update', constraints: ->(req) { content_type_exists.call(req.params[:slug], false) }
52
- post ':slug', to: 'single#update', constraints: ->(req) { content_type_exists.call(req.params[:slug], false) }
57
+ get "content", to: "content_manager#index", as: :content
58
+ get "trash", to: "trash#show", as: :trash
59
+
60
+ resources :media_assets, only: [ :index, :create, :update, :destroy ]
61
+ resources :api_tokens, only: [ :index, :create, :destroy ]
62
+
63
+ get "api-docs", to: "api_docs#show", as: :api_docs
64
+ get "api-docs/openapi", to: "api_docs#spec", as: :api_openapi
65
+
66
+ resources :content_types, only: [] do
67
+ resources :documents, except: [ :show ] do
68
+ collection do
69
+ get :trash
70
+ end
71
+ member do
72
+ post :publish
73
+ patch :restore
74
+ delete :purge
53
75
  end
54
76
  end
55
77
  end
56
78
 
57
- RivetCms::Routes.draw_routes
79
+ get "api/:content_type_slug", to: "content#index", as: :content_index
80
+ get "api/:content_type_slug/:slug", to: "content#show", as: :content_show
58
81
  end
@@ -0,0 +1,17 @@
1
+ class CreateRivetCmsOrganizations < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :rivet_cms_organizations do |t|
4
+ t.string :name, null: false
5
+ t.string :domain, null: false
6
+ t.string :subdomain
7
+ t.boolean :default, default: false, null: false
8
+ t.string :timezone, default: "UTC"
9
+
10
+ t.timestamps
11
+
12
+ t.index :domain, unique: true
13
+ # Index on subdomain for lookups (uniqueness handled at model level)
14
+ t.index :subdomain
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,14 @@
1
+ class CreateRivetCmsContentTypes < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :rivet_cms_content_types do |t|
4
+ t.string :name, null: false, index: true
5
+ t.string :slug, null: false
6
+ t.text :description
7
+ t.boolean :single, default: false
8
+ t.references :organization, foreign_key: { to_table: :rivet_cms_organizations }, null: false
9
+ t.timestamps
10
+
11
+ t.index [ :organization_id, :slug ], unique: true
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,17 @@
1
+ class CreateRivetCmsComponents < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :rivet_cms_components do |t|
4
+ t.string :name, null: false, index: true
5
+ t.string :slug, null: false
6
+ t.text :description
7
+ # The FK is added in the categories migration, which runs later; adding
8
+ # it here would reference a table that does not exist yet (fine on
9
+ # SQLite, fatal on Postgres).
10
+ t.references :category
11
+ t.references :organization, foreign_key: { to_table: :rivet_cms_organizations }, null: false
12
+ t.timestamps
13
+
14
+ t.index [ :organization_id, :slug ], unique: true
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ class CreateRivetCmsCategories < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :rivet_cms_categories do |t|
4
+ t.string :name, null: false, index: true
5
+ t.string :slug, null: false
6
+ t.integer :position, null: false, default: 0
7
+ t.boolean :system, default: false, null: false
8
+ t.references :organization, foreign_key: { to_table: :rivet_cms_organizations }, null: false
9
+
10
+ t.timestamps
11
+ t.index [ :organization_id, :slug ], unique: true
12
+ end
13
+
14
+ # Deferred from the components migration, which runs before this table exists
15
+ add_foreign_key :rivet_cms_components, :rivet_cms_categories, column: :category_id
16
+ end
17
+ end
@@ -0,0 +1,97 @@
1
+ class CreateContentSystemTables < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :rivet_cms_fields do |t|
4
+ t.references :organization, null: false, foreign_key: { to_table: :rivet_cms_organizations }
5
+ t.references :content_type, foreign_key: { to_table: :rivet_cms_content_types }
6
+ t.references :component, foreign_key: { to_table: :rivet_cms_components }
7
+ t.string :key, null: false
8
+ t.string :label, null: false
9
+ t.integer :field_type, null: false, default: 0
10
+ t.text :description
11
+ t.boolean :required, default: false, null: false
12
+ t.integer :min_items
13
+ t.integer :max_items
14
+ # No DB default: MySQL rejects defaults on JSON columns. The Field model
15
+ # defaults it to {} instead.
16
+ t.json :config
17
+ t.integer :position, default: 0, null: false
18
+ t.integer :row, default: 0, null: false
19
+ t.string :width, default: "full", null: false
20
+ t.datetime :deleted_at
21
+ t.timestamps
22
+
23
+ t.index [ :content_type_id, :key ]
24
+ t.index [ :component_id, :key ]
25
+ t.index :deleted_at
26
+ t.index :position
27
+ end
28
+
29
+ create_table :rivet_cms_documents do |t|
30
+ t.references :organization, null: false, foreign_key: { to_table: :rivet_cms_organizations }
31
+ t.references :content_type, null: false, foreign_key: { to_table: :rivet_cms_content_types }
32
+ t.string :slug, null: false
33
+ t.string :singleton_key
34
+ t.bigint :published_revision_id
35
+ t.bigint :draft_revision_id
36
+ t.timestamps
37
+
38
+ t.index [ :content_type_id, :slug ], unique: true
39
+ t.index [ :content_type_id, :singleton_key ], unique: true
40
+ t.index :published_revision_id
41
+ t.index :draft_revision_id
42
+ end
43
+
44
+ create_table :rivet_cms_document_revisions do |t|
45
+ t.references :document, null: false, foreign_key: { to_table: :rivet_cms_documents }
46
+ t.string :locale, null: false, default: "en"
47
+ t.integer :schema_version, null: false, default: 1
48
+ t.references :author, polymorphic: true
49
+ t.string :author_name
50
+ t.integer :state, null: false, default: 0
51
+ t.datetime :published_at
52
+ t.timestamps
53
+
54
+ t.index [ :document_id, :state ]
55
+ t.index [ :document_id, :locale ]
56
+ end
57
+
58
+ add_foreign_key :rivet_cms_documents, :rivet_cms_document_revisions, column: :published_revision_id
59
+ add_foreign_key :rivet_cms_documents, :rivet_cms_document_revisions, column: :draft_revision_id
60
+
61
+ create_table :rivet_cms_content_values do |t|
62
+ t.references :owner, polymorphic: true, null: false, index: false
63
+ t.references :field, null: false, foreign_key: { to_table: :rivet_cms_fields }
64
+ t.string :string_value
65
+ t.text :text_value
66
+ t.integer :integer_value
67
+ t.boolean :boolean_value
68
+ t.decimal :decimal_value, precision: 19, scale: 6
69
+ t.datetime :datetime_value
70
+ t.date :date_value
71
+ t.json :json_value
72
+ t.timestamps
73
+
74
+ t.index [ :owner_type, :owner_id, :field_id ], unique: true, name: "idx_content_values_owner_field"
75
+ end
76
+
77
+ create_table :rivet_cms_relations do |t|
78
+ t.references :owner, polymorphic: true, null: false, index: false
79
+ t.references :field, null: false, foreign_key: { to_table: :rivet_cms_fields }
80
+ t.references :target_document, null: false, foreign_key: { to_table: :rivet_cms_documents }
81
+ t.integer :position, null: false, default: 0
82
+ t.timestamps
83
+
84
+ t.index [ :owner_type, :owner_id, :field_id, :position ], name: "idx_relations_owner_field_pos"
85
+ end
86
+
87
+ create_table :rivet_cms_component_instances do |t|
88
+ t.references :owner, polymorphic: true, null: false, index: false
89
+ t.references :field, null: false, foreign_key: { to_table: :rivet_cms_fields }
90
+ t.references :component, null: false, foreign_key: { to_table: :rivet_cms_components }
91
+ t.integer :position, null: false, default: 0
92
+ t.timestamps
93
+
94
+ t.index [ :owner_type, :owner_id, :field_id, :position ], name: "idx_cmpi_owner_field_pos"
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,15 @@
1
+ class CreateRivetCmsMediaAssets < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :rivet_cms_media_assets do |t|
4
+ t.references :organization, null: false, foreign_key: { to_table: :rivet_cms_organizations }
5
+ t.string :filename
6
+ t.string :content_type
7
+ t.bigint :byte_size
8
+ t.integer :kind, null: false, default: 0
9
+ t.timestamps
10
+ end
11
+
12
+ add_reference :rivet_cms_content_values, :media_asset,
13
+ foreign_key: { to_table: :rivet_cms_media_assets }
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ class CreateRivetCmsApiTokens < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :rivet_cms_api_tokens do |t|
4
+ t.references :organization, null: false, foreign_key: { to_table: :rivet_cms_organizations }
5
+ t.string :name, null: false
6
+ t.string :token_digest, null: false
7
+ t.string :token_last4, null: false
8
+ t.integer :scope, null: false, default: 0
9
+ t.datetime :last_used_at
10
+ t.datetime :expires_at
11
+ t.timestamps
12
+
13
+ t.index :token_digest, unique: true
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,7 @@
1
+ class AddTitleAndAltToRivetCmsMediaAssets < ActiveRecord::Migration[7.0]
2
+ def change
3
+ add_column :rivet_cms_media_assets, :title, :string
4
+ add_column :rivet_cms_media_assets, :alt, :string
5
+ add_column :rivet_cms_media_assets, :description, :text
6
+ end
7
+ end
@@ -0,0 +1,6 @@
1
+ class AddDeletedAtToRivetCmsContentTypes < ActiveRecord::Migration[7.2]
2
+ def change
3
+ add_column :rivet_cms_content_types, :deleted_at, :datetime
4
+ add_index :rivet_cms_content_types, :deleted_at
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ class AddDeletedAtToRivetCmsDocuments < ActiveRecord::Migration[7.2]
2
+ def change
3
+ add_column :rivet_cms_documents, :deleted_at, :datetime
4
+ add_index :rivet_cms_documents, :deleted_at
5
+ end
6
+ end
@@ -0,0 +1,15 @@
1
+ class CreateRivetCmsUsers < ActiveRecord::Migration[7.2]
2
+ def change
3
+ create_table :rivet_cms_users do |t|
4
+ t.references :organization, null: false, foreign_key: { to_table: :rivet_cms_organizations }
5
+ t.string :name, null: false
6
+ t.string :email, null: false
7
+ # Null until the invite link is used; a pending user cannot sign in
8
+ t.string :password_digest
9
+ t.boolean :active, null: false, default: true
10
+ t.timestamps
11
+ end
12
+
13
+ add_index :rivet_cms_users, [ :organization_id, :email ], unique: true
14
+ end
15
+ end
@@ -0,0 +1,70 @@
1
+ # Content-type templates
2
+
3
+ Reusable schema you can load into an organization as a starting point — content
4
+ types, fields, components, and categories, ready to fill with content. They are
5
+ **schema only** (no sample entries), and applied **idempotently**: loading a
6
+ template again upserts fields rather than duplicating them, so it is safe to
7
+ re-run in production.
8
+
9
+ ## Available templates
10
+
11
+ | Template | Adds |
12
+ |-----------------|--------------------------------------------------------------------------|
13
+ | `blog` | Blog Post, Author, Category content types + an SEO component |
14
+ | `pages` | Page content type + Hero, Call to Action, and SEO components |
15
+ | `site_settings` | Site Settings (single type) + a Social Link component |
16
+ | `faq` | FAQ content type |
17
+ | `team` | Team Member content type |
18
+ | `events` | Event content type (uses the date/time field types) |
19
+ | `testimonials` | Testimonial content type |
20
+
21
+ ## Usage
22
+
23
+ From the host app:
24
+
25
+ ```bash
26
+ # Everything, into the default organization
27
+ bin/rails rivet_cms:seed
28
+
29
+ # Specific templates
30
+ bin/rails rivet_cms:seed TEMPLATES=blog,pages
31
+
32
+ # A specific organization (by domain)
33
+ bin/rails rivet_cms:seed ORG=example.com
34
+
35
+ # List what's available
36
+ bin/rails rivet_cms:templates
37
+ ```
38
+
39
+ Or from Ruby (e.g. in the host's `db/seeds.rb`):
40
+
41
+ ```ruby
42
+ require "rivet_cms/seeds"
43
+
44
+ org = RivetCms::Organization.find_by!(domain: "example.com")
45
+ RivetCms::Seeds.load!(organization: org, only: %w[blog pages])
46
+ ```
47
+
48
+ ## Adding your own
49
+
50
+ Drop a file in `db/seeds/templates/`. Half-width fields declared next to each
51
+ other are paired onto the same form row.
52
+
53
+ ```ruby
54
+ RivetCms::Seeds.template "products" do
55
+ category "Commerce", system: true
56
+
57
+ component "Price", category: "Commerce", slug: "price" do
58
+ integer :amount_cents, width: :half
59
+ string :currency, width: :half
60
+ end
61
+
62
+ content_type "Product", slug: "products", description: "Things you sell" do
63
+ string :name, required: true
64
+ rich_text :description
65
+ image :photo
66
+ component :price, use: "Price", max_items: 1
67
+ reference :related, to: "products" # omit max_items for a many-relation
68
+ end
69
+ end
70
+ ```
@@ -0,0 +1,39 @@
1
+ # Blog — the classic starting point. Posts are backed by the authors and
2
+ # categories they relate to, plus a reusable SEO component.
3
+ RivetCms::Seeds.template "blog" do
4
+ category "Content", system: true
5
+
6
+ component "SEO", category: "Content", slug: "seo", description: "Search-engine and social sharing metadata" do
7
+ string :meta_title, width: :half
8
+ string :meta_description, width: :half
9
+ image :og_image, label: "Social share image"
10
+ boolean :no_index, label: "Hide from search engines"
11
+ end
12
+
13
+ content_type "Author", slug: "authors", description: "People who write your content" do
14
+ string :name, required: true, width: :half
15
+ string :email, width: :half, config: { "pattern" => "^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$" }
16
+ image :avatar
17
+ rich_text :bio
18
+ string :x_handle, label: "X (Twitter)", width: :half
19
+ string :website, width: :half
20
+ end
21
+
22
+ content_type "Category", slug: "categories", description: "Topics used to group posts" do
23
+ string :name, required: true, width: :half
24
+ string :color, label: "Color (hex)", width: :half
25
+ text :description
26
+ end
27
+
28
+ content_type "Blog Post", slug: "posts", description: "Articles and news for your blog" do
29
+ string :title, required: true
30
+ text :excerpt, description: "Short summary shown in listings and link previews"
31
+ image :cover_image
32
+ rich_text :body, required: true
33
+ reference :author, to: "authors", max_items: 1
34
+ reference :categories, to: "categories"
35
+ datetime :published_at, width: :half
36
+ boolean :featured, label: "Feature this post", width: :half
37
+ component :seo, use: "SEO", max_items: 1
38
+ end
39
+ end
@@ -0,0 +1,12 @@
1
+ # Events — scheduled happenings. Showcases the date/datetime field types.
2
+ RivetCms::Seeds.template "events" do
3
+ content_type "Event", slug: "events", description: "Scheduled events, conferences, and webinars" do
4
+ string :title, required: true
5
+ rich_text :description
6
+ datetime :starts_at, required: true, width: :half
7
+ datetime :ends_at, width: :half
8
+ string :location, description: "Venue or a link for online events"
9
+ string :registration_url, label: "Registration URL"
10
+ image :cover_image
11
+ end
12
+ end
@@ -0,0 +1,9 @@
1
+ # FAQ — frequently asked questions, groupable and orderable.
2
+ RivetCms::Seeds.template "faq" do
3
+ content_type "FAQ", slug: "faqs", description: "Frequently asked questions" do
4
+ string :question, required: true
5
+ rich_text :answer, required: true
6
+ string :topic, description: "Optional grouping, e.g. Billing or Shipping", width: :half
7
+ integer :position, label: "Sort order", width: :half
8
+ end
9
+ end
@@ -0,0 +1,35 @@
1
+ # Pages — flexible marketing pages assembled from reusable section components.
2
+ RivetCms::Seeds.template "pages" do
3
+ category "Layout", system: true
4
+ category "Content", system: true
5
+
6
+ component "Hero", category: "Layout", slug: "hero", description: "Full-width banner with a headline and call to action" do
7
+ string :heading, required: true
8
+ text :subheading
9
+ image :background_image
10
+ string :cta_label, label: "Button label", width: :half
11
+ string :cta_url, label: "Button URL", width: :half
12
+ end
13
+
14
+ component "Call to Action", category: "Layout", slug: "call-to-action", description: "Prompt block with a button" do
15
+ string :heading
16
+ text :body
17
+ string :button_label, width: :half
18
+ string :button_url, width: :half
19
+ end
20
+
21
+ component "SEO", category: "Content", slug: "seo", description: "Search-engine and social sharing metadata" do
22
+ string :meta_title, width: :half
23
+ string :meta_description, width: :half
24
+ image :og_image, label: "Social share image"
25
+ boolean :no_index, label: "Hide from search engines"
26
+ end
27
+
28
+ content_type "Page", slug: "pages", description: "Standalone pages such as About, Pricing, or Contact" do
29
+ string :title, required: true
30
+ component :hero, use: "Hero", max_items: 1
31
+ rich_text :body
32
+ component :call_to_action, use: "Call to Action", max_items: 1
33
+ component :seo, use: "SEO", max_items: 1
34
+ end
35
+ end
@@ -0,0 +1,22 @@
1
+ # Site settings — a single global record for site-wide values, plus a repeatable
2
+ # social-link component. Demonstrates a single-type content type.
3
+ RivetCms::Seeds.template "site_settings" do
4
+ category "Global", system: true
5
+
6
+ component "Social Link", category: "Global", slug: "social-link", description: "One social network profile" do
7
+ string :platform, width: :half
8
+ string :url, width: :half
9
+ end
10
+
11
+ content_type "Site Settings", slug: "site-settings", single: true, description: "Global values shared across the whole site" do
12
+ string :site_name, required: true
13
+ string :tagline
14
+ image :logo, width: :half
15
+ image :favicon, width: :half
16
+ text :description
17
+ component :social_links, use: "Social Link"
18
+ string :contact_email, width: :half, config: { "pattern" => "^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$" }
19
+ string :contact_phone, width: :half
20
+ text :footer_text
21
+ end
22
+ end
@@ -0,0 +1,11 @@
1
+ # Team — staff or contributor profiles for an About/Team page.
2
+ RivetCms::Seeds.template "team" do
3
+ content_type "Team Member", slug: "team", description: "Staff and contributor profiles" do
4
+ string :name, required: true, width: :half
5
+ string :role, label: "Job title", width: :half
6
+ image :photo
7
+ text :bio
8
+ string :email, width: :half
9
+ string :linkedin, label: "LinkedIn URL", width: :half
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # Testimonials — customer quotes and social proof.
2
+ RivetCms::Seeds.template "testimonials" do
3
+ content_type "Testimonial", slug: "testimonials", description: "Customer quotes and reviews" do
4
+ text :quote, required: true
5
+ string :author_name, required: true, width: :half
6
+ string :author_role, label: "Role", width: :half
7
+ string :company, width: :half
8
+ image :avatar, width: :half
9
+ decimal :rating, description: "1 to 5 stars", config: { "min" => 1, "max" => 5, "step" => 0.5 }
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ module RivetCms
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+ source_root File.expand_path("templates", __dir__)
5
+
6
+ desc "Creates the RivetCms initializer with the auth delegation template"
7
+
8
+ def copy_initializer
9
+ copy_file "initializer.rb", "config/initializers/rivet_cms.rb"
10
+ end
11
+ end
12
+ end
13
+ end