rivet_cms 0.1.2.pre → 0.3.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 (127) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +53 -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 +203 -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 +14 -0
  67. data/db/migrate/20251121040115_create_rivet_cms_categories.rb +14 -0
  68. data/db/migrate/20260119185946_create_content_system_tables.rb +95 -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,14 @@
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
+ t.references :category, foreign_key: { to_table: :rivet_cms_categories }
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,14 @@
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
+ end
14
+ end
@@ -0,0 +1,95 @@
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
+ t.json :config, default: {}
15
+ t.integer :position, default: 0, null: false
16
+ t.integer :row, default: 0, null: false
17
+ t.string :width, default: "full", null: false
18
+ t.datetime :deleted_at
19
+ t.timestamps
20
+
21
+ t.index [ :content_type_id, :key ]
22
+ t.index [ :component_id, :key ]
23
+ t.index :deleted_at
24
+ t.index :position
25
+ end
26
+
27
+ create_table :rivet_cms_documents do |t|
28
+ t.references :organization, null: false, foreign_key: { to_table: :rivet_cms_organizations }
29
+ t.references :content_type, null: false, foreign_key: { to_table: :rivet_cms_content_types }
30
+ t.string :slug, null: false
31
+ t.string :singleton_key
32
+ t.bigint :published_revision_id
33
+ t.bigint :draft_revision_id
34
+ t.timestamps
35
+
36
+ t.index [ :content_type_id, :slug ], unique: true
37
+ t.index [ :content_type_id, :singleton_key ], unique: true
38
+ t.index :published_revision_id
39
+ t.index :draft_revision_id
40
+ end
41
+
42
+ create_table :rivet_cms_document_revisions do |t|
43
+ t.references :document, null: false, foreign_key: { to_table: :rivet_cms_documents }
44
+ t.string :locale, null: false, default: "en"
45
+ t.integer :schema_version, null: false, default: 1
46
+ t.references :author, polymorphic: true
47
+ t.string :author_name
48
+ t.integer :state, null: false, default: 0
49
+ t.datetime :published_at
50
+ t.timestamps
51
+
52
+ t.index [ :document_id, :state ]
53
+ t.index [ :document_id, :locale ]
54
+ end
55
+
56
+ add_foreign_key :rivet_cms_documents, :rivet_cms_document_revisions, column: :published_revision_id
57
+ add_foreign_key :rivet_cms_documents, :rivet_cms_document_revisions, column: :draft_revision_id
58
+
59
+ create_table :rivet_cms_content_values do |t|
60
+ t.references :owner, polymorphic: true, null: false, index: false
61
+ t.references :field, null: false, foreign_key: { to_table: :rivet_cms_fields }
62
+ t.string :string_value
63
+ t.text :text_value
64
+ t.integer :integer_value
65
+ t.boolean :boolean_value
66
+ t.decimal :decimal_value, precision: 19, scale: 6
67
+ t.datetime :datetime_value
68
+ t.date :date_value
69
+ t.json :json_value
70
+ t.timestamps
71
+
72
+ t.index [ :owner_type, :owner_id, :field_id ], unique: true, name: "idx_content_values_owner_field"
73
+ end
74
+
75
+ create_table :rivet_cms_relations do |t|
76
+ t.references :owner, polymorphic: true, null: false, index: false
77
+ t.references :field, null: false, foreign_key: { to_table: :rivet_cms_fields }
78
+ t.references :target_document, null: false, foreign_key: { to_table: :rivet_cms_documents }
79
+ t.integer :position, null: false, default: 0
80
+ t.timestamps
81
+
82
+ t.index [ :owner_type, :owner_id, :field_id, :position ], name: "idx_relations_owner_field_pos"
83
+ end
84
+
85
+ create_table :rivet_cms_component_instances do |t|
86
+ t.references :owner, polymorphic: true, null: false, index: false
87
+ t.references :field, null: false, foreign_key: { to_table: :rivet_cms_fields }
88
+ t.references :component, null: false, foreign_key: { to_table: :rivet_cms_components }
89
+ t.integer :position, null: false, default: 0
90
+ t.timestamps
91
+
92
+ t.index [ :owner_type, :owner_id, :field_id, :position ], name: "idx_cmpi_owner_field_pos"
93
+ end
94
+ end
95
+ 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