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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +53 -0
- data/COPYING +674 -0
- data/COPYING.LESSER +165 -0
- data/README.md +173 -10
- data/app/assets/builds/rivet_cms.css +2 -2
- data/app/assets/builds/rivet_cms.js +395 -9505
- data/app/assets/stylesheets/rivet_cms/application.tailwind.css +145 -22
- data/app/controllers/concerns/rivet_cms/inertia_props.rb +239 -0
- data/app/controllers/rivet_cms/api_docs_controller.rb +47 -0
- data/app/controllers/rivet_cms/api_tokens_controller.rb +43 -0
- data/app/controllers/rivet_cms/application_controller.rb +220 -2
- data/app/controllers/rivet_cms/auth_controller.rb +28 -0
- data/app/controllers/rivet_cms/components_controller.rb +93 -2
- data/app/controllers/rivet_cms/content_controller.rb +105 -0
- data/app/controllers/rivet_cms/content_manager_controller.rb +32 -0
- data/app/controllers/rivet_cms/content_types_controller.rb +118 -26
- data/app/controllers/rivet_cms/dashboard_controller.rb +55 -5
- data/app/controllers/rivet_cms/documents_controller.rb +204 -0
- data/app/controllers/rivet_cms/fields_controller.rb +77 -74
- data/app/controllers/rivet_cms/invitations_controller.rb +36 -0
- data/app/controllers/rivet_cms/media_assets_controller.rb +76 -0
- data/app/controllers/rivet_cms/sessions_controller.rb +122 -0
- data/app/controllers/rivet_cms/setup_controller.rb +60 -0
- data/app/controllers/rivet_cms/trash_controller.rb +68 -0
- data/app/controllers/rivet_cms/users_controller.rb +117 -0
- data/app/helpers/rivet_cms/application_helper.rb +13 -3
- data/app/helpers/rivet_cms/components_helper.rb +4 -0
- data/app/helpers/rivet_cms/content_types_helper.rb +4 -0
- data/app/helpers/rivet_cms/dashboard_helper.rb +4 -0
- data/app/jobs/rivet_cms/webhook_delivery_job.rb +35 -0
- data/app/models/concerns/rivet_cms/has_fields.rb +31 -0
- data/app/models/concerns/rivet_cms/organization_scoped.rb +17 -0
- data/app/models/concerns/rivet_cms/revision_owned.rb +36 -0
- data/app/models/concerns/rivet_cms/sluggable.rb +38 -0
- data/app/models/concerns/rivet_cms/soft_deletable.rb +42 -0
- data/app/models/concerns/rivet_cms/typed_value.rb +51 -0
- data/app/models/rivet_cms/api_token.rb +64 -0
- data/app/models/rivet_cms/category.rb +20 -0
- data/app/models/rivet_cms/component.rb +10 -0
- data/app/models/rivet_cms/component_instance.rb +27 -0
- data/app/models/rivet_cms/content_type.rb +36 -23
- data/app/models/rivet_cms/content_value.rb +19 -0
- data/app/models/rivet_cms/current.rb +6 -0
- data/app/models/rivet_cms/document.rb +71 -0
- data/app/models/rivet_cms/document_revision.rb +128 -0
- data/app/models/rivet_cms/entry.rb +73 -0
- data/app/models/rivet_cms/entry_collection.rb +41 -0
- data/app/models/rivet_cms/field.rb +203 -61
- data/app/models/rivet_cms/media_asset.rb +109 -0
- data/app/models/rivet_cms/organization.rb +46 -0
- data/app/models/rivet_cms/relation.rb +23 -0
- data/app/models/rivet_cms/user.rb +61 -0
- data/app/serializers/rivet_cms/revision_preloader.rb +146 -0
- data/app/serializers/rivet_cms/revision_serializer.rb +123 -0
- data/app/services/rivet_cms/content_query.rb +158 -0
- data/app/services/rivet_cms/content_validator.rb +150 -0
- data/app/services/rivet_cms/draft_writer.rb +74 -0
- data/app/services/rivet_cms/open_api_generator.rb +173 -221
- data/app/services/rivet_cms/revision_pruner.rb +64 -0
- data/app/services/rivet_cms/webhooks.rb +61 -0
- data/app/views/layouts/rivet_cms/application.html.erb +35 -42
- data/config/routes.rb +68 -45
- data/db/migrate/20251119193607_create_rivet_cms_organizations.rb +17 -0
- data/db/migrate/20251120050818_create_rivet_cms_content_types.rb +14 -0
- data/db/migrate/20251121020625_create_rivet_cms_components.rb +14 -0
- data/db/migrate/20251121040115_create_rivet_cms_categories.rb +14 -0
- data/db/migrate/20260119185946_create_content_system_tables.rb +95 -0
- data/db/migrate/20260722000001_create_rivet_cms_media_assets.rb +15 -0
- data/db/migrate/20260722000002_create_rivet_cms_api_tokens.rb +16 -0
- data/db/migrate/20260806000001_add_title_and_alt_to_rivet_cms_media_assets.rb +7 -0
- data/db/migrate/20260806000002_add_deleted_at_to_rivet_cms_content_types.rb +6 -0
- data/db/migrate/20260807000001_add_deleted_at_to_rivet_cms_documents.rb +6 -0
- data/db/migrate/20260808000001_create_rivet_cms_users.rb +15 -0
- data/db/seeds/README.md +70 -0
- data/db/seeds/templates/blog.rb +39 -0
- data/db/seeds/templates/events.rb +12 -0
- data/db/seeds/templates/faq.rb +9 -0
- data/db/seeds/templates/pages.rb +35 -0
- data/db/seeds/templates/site_settings.rb +22 -0
- data/db/seeds/templates/team.rb +11 -0
- data/db/seeds/templates/testimonials.rb +11 -0
- data/lib/generators/rivet_cms/install/install_generator.rb +13 -0
- data/lib/generators/rivet_cms/install/templates/initializer.rb +77 -0
- data/lib/rivet_cms/access_check.rb +12 -0
- data/lib/rivet_cms/audit.rb +43 -0
- data/lib/rivet_cms/engine.rb +28 -22
- data/lib/rivet_cms/hooks.rb +106 -0
- data/lib/rivet_cms/navigation.rb +111 -0
- data/lib/rivet_cms/safe_pattern.rb +40 -0
- data/lib/rivet_cms/seeds.rb +264 -0
- data/lib/rivet_cms/version.rb +1 -1
- data/lib/rivet_cms.rb +289 -32
- data/lib/tasks/rivet_cms_tasks.rake +18 -4
- metadata +138 -98
- data/MIT-LICENSE +0 -20
- data/app/assets/builds/rivet_cms.js.map +0 -7
- data/app/assets/stylesheets/rivet_cms/brand_colors.css +0 -168
- data/app/controllers/rivet_cms/api/docs_controller.rb +0 -38
- data/app/helpers/rivet_cms/brand_color_helper.rb +0 -71
- data/app/helpers/rivet_cms/flash_helper.rb +0 -37
- data/app/helpers/rivet_cms/sign_out_helper.rb +0 -11
- data/app/javascript/controllers/content_type_form_controller.js +0 -53
- data/app/javascript/controllers/field_layout_controller.js +0 -709
- data/app/javascript/rivet_cms.js +0 -29
- data/app/models/rivet_cms/content.rb +0 -4
- data/app/models/rivet_cms/field_values/base.rb +0 -11
- data/app/models/rivet_cms/field_values/boolean.rb +0 -4
- data/app/models/rivet_cms/field_values/integer.rb +0 -4
- data/app/models/rivet_cms/field_values/string.rb +0 -4
- data/app/models/rivet_cms/field_values/text.rb +0 -4
- data/app/views/rivet_cms/api/docs/show.html.erb +0 -47
- data/app/views/rivet_cms/content_types/_form.html.erb +0 -98
- data/app/views/rivet_cms/content_types/edit.html.erb +0 -27
- data/app/views/rivet_cms/content_types/index.html.erb +0 -151
- data/app/views/rivet_cms/content_types/new.html.erb +0 -19
- data/app/views/rivet_cms/content_types/show.html.erb +0 -147
- data/app/views/rivet_cms/dashboard/index.html.erb +0 -263
- data/app/views/rivet_cms/fields/_form.html.erb +0 -111
- data/app/views/rivet_cms/fields/edit.html.erb +0 -25
- data/app/views/rivet_cms/fields/index.html.erb +0 -126
- data/app/views/rivet_cms/fields/new.html.erb +0 -25
- data/app/views/rivet_cms/shared/_navigation.html.erb +0 -153
- data/config/i18n-tasks.yml +0 -178
- data/config/locales/en.yml +0 -14
- data/db/migrate/20250317194359_create_core_tables.rb +0 -90
- 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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
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,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
|
data/db/seeds/README.md
ADDED
|
@@ -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
|