cms42 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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +21 -0
- data/README.md +299 -0
- data/Rakefile +10 -0
- data/app/assets/stylesheets/cms/application.css +3 -0
- data/app/controllers/cms/admin/api_keys_controller.rb +52 -0
- data/app/controllers/cms/admin/base_controller.rb +54 -0
- data/app/controllers/cms/admin/documents_controller.rb +56 -0
- data/app/controllers/cms/admin/form_fields_controller.rb +70 -0
- data/app/controllers/cms/admin/form_submissions_controller.rb +36 -0
- data/app/controllers/cms/admin/images_controller.rb +67 -0
- data/app/controllers/cms/admin/pages_controller.rb +188 -0
- data/app/controllers/cms/admin/sections_controller.rb +177 -0
- data/app/controllers/cms/admin/sites_controller.rb +60 -0
- data/app/controllers/cms/admin/webhook_deliveries_controller.rb +19 -0
- data/app/controllers/cms/admin/webhooks_controller.rb +51 -0
- data/app/controllers/cms/api/base_controller.rb +45 -0
- data/app/controllers/cms/api/v1/base_controller.rb +29 -0
- data/app/controllers/cms/api/v1/pages_controller.rb +21 -0
- data/app/controllers/cms/api/v1/sites_controller.rb +18 -0
- data/app/controllers/cms/application_controller.rb +11 -0
- data/app/controllers/cms/public/base_controller.rb +23 -0
- data/app/controllers/cms/public/form_submissions_controller.rb +63 -0
- data/app/controllers/cms/public/previews_controller.rb +21 -0
- data/app/controllers/cms/public/sites_controller.rb +37 -0
- data/app/controllers/concerns/cms/admin/page_scoped_sections.rb +42 -0
- data/app/controllers/concerns/cms/current_site_resolver.rb +17 -0
- data/app/controllers/concerns/cms/public/page_paths.rb +31 -0
- data/app/controllers/concerns/cms/public/page_rendering.rb +23 -0
- data/app/controllers/concerns/cms/site_resolvable.rb +27 -0
- data/app/helpers/cms/admin/pages_helper.rb +29 -0
- data/app/helpers/cms/admin/sections_helper.rb +86 -0
- data/app/helpers/cms/admin/sites_helper.rb +8 -0
- data/app/helpers/cms/application_helper.rb +51 -0
- data/app/helpers/cms/media_helper.rb +28 -0
- data/app/helpers/cms/pages_helper.rb +16 -0
- data/app/helpers/cms/sections_helper.rb +25 -0
- data/app/helpers/cms/sites_helper.rb +11 -0
- data/app/javascript/cms/controllers/sortable_controller.js +38 -0
- data/app/jobs/cms/application_job.rb +6 -0
- data/app/jobs/cms/deliver_webhook_job.rb +66 -0
- data/app/mailers/cms/application_mailer.rb +9 -0
- data/app/mailers/cms/form_submission_mailer.rb +16 -0
- data/app/models/cms/api_key.rb +27 -0
- data/app/models/cms/application_record.rb +7 -0
- data/app/models/cms/document.rb +48 -0
- data/app/models/cms/form_field.rb +27 -0
- data/app/models/cms/form_submission.rb +42 -0
- data/app/models/cms/image.rb +61 -0
- data/app/models/cms/image_translation.rb +22 -0
- data/app/models/cms/page.rb +228 -0
- data/app/models/cms/page_section.rb +43 -0
- data/app/models/cms/page_translation.rb +22 -0
- data/app/models/cms/section/block_base.rb +32 -0
- data/app/models/cms/section/blocks/call_to_action_block.rb +16 -0
- data/app/models/cms/section/blocks/hero_block.rb +14 -0
- data/app/models/cms/section/blocks/image_block.rb +13 -0
- data/app/models/cms/section/blocks/rich_text_block.rb +12 -0
- data/app/models/cms/section/kind_registry.rb +66 -0
- data/app/models/cms/section.rb +94 -0
- data/app/models/cms/section_image.rb +10 -0
- data/app/models/cms/section_translation.rb +25 -0
- data/app/models/cms/site.rb +87 -0
- data/app/models/cms/webhook.rb +41 -0
- data/app/models/cms/webhook_delivery.rb +12 -0
- data/app/serializers/cms/api/base_serializer.rb +51 -0
- data/app/serializers/cms/api/page_serializer.rb +145 -0
- data/app/serializers/cms/api/site_serializer.rb +45 -0
- data/app/services/cms/locale_resolver.rb +30 -0
- data/app/services/cms/page_resolver.rb +73 -0
- data/app/services/cms/public_page_context.rb +49 -0
- data/app/views/cms/admin/api_keys/_form.html.erb +23 -0
- data/app/views/cms/admin/api_keys/create.html.erb +9 -0
- data/app/views/cms/admin/api_keys/edit.html.erb +5 -0
- data/app/views/cms/admin/api_keys/index.html.erb +36 -0
- data/app/views/cms/admin/api_keys/new.html.erb +5 -0
- data/app/views/cms/admin/documents/_form.html.erb +24 -0
- data/app/views/cms/admin/documents/edit.html.erb +2 -0
- data/app/views/cms/admin/documents/index.html.erb +37 -0
- data/app/views/cms/admin/documents/new.html.erb +2 -0
- data/app/views/cms/admin/form_fields/_form.html.erb +46 -0
- data/app/views/cms/admin/form_fields/edit.html.erb +2 -0
- data/app/views/cms/admin/form_fields/index.html.erb +41 -0
- data/app/views/cms/admin/form_fields/new.html.erb +2 -0
- data/app/views/cms/admin/form_submissions/index.html.erb +38 -0
- data/app/views/cms/admin/images/_form.html.erb +36 -0
- data/app/views/cms/admin/images/edit.html.erb +2 -0
- data/app/views/cms/admin/images/index.html.erb +25 -0
- data/app/views/cms/admin/images/new.html.erb +2 -0
- data/app/views/cms/admin/pages/_attach_section_panel.html.erb +20 -0
- data/app/views/cms/admin/pages/_form.html.erb +116 -0
- data/app/views/cms/admin/pages/_section_editor_frame.html.erb +3 -0
- data/app/views/cms/admin/pages/_sections_list.html.erb +9 -0
- data/app/views/cms/admin/pages/edit.html.erb +2 -0
- data/app/views/cms/admin/pages/index.html.erb +62 -0
- data/app/views/cms/admin/pages/new.html.erb +2 -0
- data/app/views/cms/admin/pages/show.html.erb +111 -0
- data/app/views/cms/admin/sections/_form.html.erb +128 -0
- data/app/views/cms/admin/sections/_section.html.erb +22 -0
- data/app/views/cms/admin/sections/edit.html.erb +9 -0
- data/app/views/cms/admin/sections/index.html.erb +47 -0
- data/app/views/cms/admin/sections/new.html.erb +9 -0
- data/app/views/cms/admin/sections/page_update.turbo_stream.erb +17 -0
- data/app/views/cms/admin/sections/show.html.erb +97 -0
- data/app/views/cms/admin/sites/_form.html.erb +44 -0
- data/app/views/cms/admin/sites/edit.html.erb +3 -0
- data/app/views/cms/admin/sites/new.html.erb +5 -0
- data/app/views/cms/admin/sites/show.html.erb +22 -0
- data/app/views/cms/admin/webhook_deliveries/index.html.erb +29 -0
- data/app/views/cms/admin/webhooks/_form.html.erb +38 -0
- data/app/views/cms/admin/webhooks/edit.html.erb +5 -0
- data/app/views/cms/admin/webhooks/index.html.erb +34 -0
- data/app/views/cms/admin/webhooks/new.html.erb +5 -0
- data/app/views/cms/form_submission_mailer/notify.html.erb +14 -0
- data/app/views/cms/form_submission_mailer/notify.text.erb +7 -0
- data/app/views/cms/public/pages/_content.html.erb +48 -0
- data/app/views/cms/public/pages/show.html.erb +44 -0
- data/app/views/cms/public/pages/templates/_custom.html.erb +3 -0
- data/app/views/cms/public/pages/templates/_form.html.erb +3 -0
- data/app/views/cms/public/pages/templates/_landing.html.erb +3 -0
- data/app/views/cms/public/pages/templates/_standard.html.erb +3 -0
- data/app/views/cms/sections/kinds/_cta.html.erb +13 -0
- data/app/views/cms/sections/kinds/_hero.html.erb +14 -0
- data/app/views/cms/sections/kinds/_image.html.erb +19 -0
- data/app/views/cms/sections/kinds/_rich_text.html.erb +6 -0
- data/app/views/layouts/cms/application.html.erb +13 -0
- data/app/views/layouts/cms/public.html.erb +14 -0
- data/bin/rails +19 -0
- data/bin/rubocop +9 -0
- data/cms.gemspec +29 -0
- data/config/importmap.rb +4 -0
- data/config/locales/activerecord.cms.en.yml +65 -0
- data/config/locales/en.yml +390 -0
- data/config/routes.rb +56 -0
- data/lib/cms/engine.rb +45 -0
- data/lib/cms/version.rb +5 -0
- data/lib/cms.rb +75 -0
- data/lib/cms42.rb +3 -0
- data/lib/generators/cms/install/install_generator.rb +26 -0
- data/lib/generators/cms/install/templates/create_cms_tables.rb +194 -0
- data/lib/generators/cms/install/templates/initializer.rb +21 -0
- data/lib/generators/cms/views/views_generator.rb +79 -0
- data/lib/tasks/cms_tasks.rake +6 -0
- data/lib/tasks/version.rake +8 -0
- metadata +281 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cms
|
|
4
|
+
module Admin
|
|
5
|
+
class PagesController < BaseController
|
|
6
|
+
include Cms::Public::PageRendering
|
|
7
|
+
include Cms::Public::PagePaths
|
|
8
|
+
|
|
9
|
+
helper Cms::ApplicationHelper
|
|
10
|
+
helper Cms::MediaHelper
|
|
11
|
+
helper Cms::SitesHelper
|
|
12
|
+
helper Cms::SectionsHelper
|
|
13
|
+
helper Cms::PagesHelper
|
|
14
|
+
|
|
15
|
+
before_action :set_page, only: %i[show edit update destroy preview]
|
|
16
|
+
|
|
17
|
+
def index
|
|
18
|
+
pages = current_site.pages.kept.includes(:localised).ordered.to_a
|
|
19
|
+
depths = page_depths(pages)
|
|
20
|
+
|
|
21
|
+
@page_rows = if params[:q].present?
|
|
22
|
+
matching_page_rows(pages, depths)
|
|
23
|
+
else
|
|
24
|
+
flattened_page_rows(pages)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def show
|
|
29
|
+
@translation_locale = requested_locale
|
|
30
|
+
@subpages = @page.subpages.sort_by { |page| [page.position || 0, page.id] }
|
|
31
|
+
@page_sections = @page.page_sections.ordered.includes(:section)
|
|
32
|
+
@available_sections = current_site.sections
|
|
33
|
+
.kept
|
|
34
|
+
.global
|
|
35
|
+
.where.not(id: @page.section_ids)
|
|
36
|
+
.ordered
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def preview
|
|
40
|
+
@translation_locale = requested_locale
|
|
41
|
+
@page.page_translations.find_or_initialize_by(locale: @translation_locale)
|
|
42
|
+
I18n.with_locale(@translation_locale) do
|
|
43
|
+
assign_public_page(site: current_site, page: @page)
|
|
44
|
+
render template: "cms/public/pages/show", layout: Cms.config.public_layout
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def new
|
|
49
|
+
@page = current_site.pages.build
|
|
50
|
+
@translation_locale = requested_locale
|
|
51
|
+
@page.page_translations.build(locale: @translation_locale)
|
|
52
|
+
@parent_options = parent_options_for(nil)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def edit
|
|
56
|
+
@translation_locale = requested_locale
|
|
57
|
+
@page.page_translations.find_or_initialize_by(locale: @translation_locale)
|
|
58
|
+
@parent_options = parent_options_for(@page)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def create
|
|
62
|
+
@page = current_site.pages.build(page_params)
|
|
63
|
+
@translation_locale = requested_locale
|
|
64
|
+
purge_media_if_requested(@page)
|
|
65
|
+
|
|
66
|
+
if @page.save
|
|
67
|
+
redirect_to admin_pages_path, notice: t("cms.notices.page_created")
|
|
68
|
+
else
|
|
69
|
+
@parent_options = parent_options_for(nil)
|
|
70
|
+
render :new, status: :unprocessable_content
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def update
|
|
75
|
+
@translation_locale = requested_locale
|
|
76
|
+
@page.assign_attributes(page_params)
|
|
77
|
+
purge_media_if_requested(@page)
|
|
78
|
+
|
|
79
|
+
if @page.save
|
|
80
|
+
redirect_to admin_pages_path, notice: t("cms.notices.page_updated")
|
|
81
|
+
else
|
|
82
|
+
@parent_options = parent_options_for(@page)
|
|
83
|
+
render :edit, status: :unprocessable_content
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def destroy
|
|
88
|
+
@page.discard!
|
|
89
|
+
redirect_to admin_pages_path, notice: t("cms.notices.page_deleted")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
def set_page
|
|
95
|
+
@page = current_site.pages.kept.includes(
|
|
96
|
+
:parent,
|
|
97
|
+
:page_translations,
|
|
98
|
+
{ subpages: :localised },
|
|
99
|
+
{ page_sections: :section },
|
|
100
|
+
{ hero_image_attachment: :blob },
|
|
101
|
+
{ media_files_attachments: :blob }
|
|
102
|
+
).find(params[:id])
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def page_params
|
|
106
|
+
params.require(:page).permit(
|
|
107
|
+
:slug,
|
|
108
|
+
:parent_id,
|
|
109
|
+
:position,
|
|
110
|
+
:home,
|
|
111
|
+
:template_key,
|
|
112
|
+
:status,
|
|
113
|
+
:show_in_header,
|
|
114
|
+
:show_in_footer,
|
|
115
|
+
:nav_group,
|
|
116
|
+
:nav_order,
|
|
117
|
+
:footer_order,
|
|
118
|
+
:hero_image,
|
|
119
|
+
media_files: [],
|
|
120
|
+
page_translations_attributes: %i[id locale title seo_title seo_description]
|
|
121
|
+
)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def requested_locale
|
|
125
|
+
params[:locale].presence || current_site.default_locale.presence || I18n.locale.to_s
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def purge_media_if_requested(page)
|
|
129
|
+
page.hero_image.purge_later if params.dig(:page, :remove_hero_image) == "1"
|
|
130
|
+
page.media_files.purge if params.dig(:page, :remove_media_files) == "1"
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def parent_options_for(current_page)
|
|
134
|
+
pages = current_site.pages.kept.ordered.includes(:localised).to_a
|
|
135
|
+
depths = page_depths(pages)
|
|
136
|
+
excluded_ids = current_page ? [current_page.id] + descendant_ids_for(pages, current_page.id) : []
|
|
137
|
+
|
|
138
|
+
pages.reject { |page| excluded_ids.include?(page.id) }.map do |p|
|
|
139
|
+
["#{'— ' * depths.fetch(p.id, 0)}#{p.display_title}", p.id]
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def descendant_ids_for(pages, page_id)
|
|
144
|
+
children_by_parent = pages.group_by(&:parent_id)
|
|
145
|
+
queue = Array(children_by_parent[page_id])
|
|
146
|
+
ids = []
|
|
147
|
+
|
|
148
|
+
until queue.empty?
|
|
149
|
+
page = queue.shift
|
|
150
|
+
ids << page.id
|
|
151
|
+
queue.concat(Array(children_by_parent[page.id]))
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
ids
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def flattened_page_rows(pages)
|
|
158
|
+
children_by_parent = pages.group_by(&:parent_id)
|
|
159
|
+
rows = []
|
|
160
|
+
|
|
161
|
+
append_page_rows(rows, children_by_parent, children_by_parent[nil], 0)
|
|
162
|
+
rows
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def append_page_rows(rows, children_by_parent, pages, depth)
|
|
166
|
+
Array(pages).each do |page|
|
|
167
|
+
rows << [page, depth]
|
|
168
|
+
append_page_rows(rows, children_by_parent, children_by_parent[page.id], depth + 1)
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def page_depths(pages)
|
|
173
|
+
pages.to_h { |page| [page.id, page.depth] }
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def matching_page_rows(pages, depths)
|
|
177
|
+
pages_by_id = pages.index_by(&:id)
|
|
178
|
+
|
|
179
|
+
current_site.pages.kept.search(params[:q]).pluck(:id).filter_map do |page_id|
|
|
180
|
+
page = pages_by_id[page_id]
|
|
181
|
+
next unless page
|
|
182
|
+
|
|
183
|
+
[page, depths.fetch(page.id, 0)]
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cms
|
|
4
|
+
module Admin
|
|
5
|
+
class SectionsController < BaseController
|
|
6
|
+
include PageScopedSections
|
|
7
|
+
|
|
8
|
+
before_action :set_page, if: :page_scoped_request?
|
|
9
|
+
before_action :set_section, only: %i[show edit update destroy]
|
|
10
|
+
|
|
11
|
+
def index
|
|
12
|
+
@sections = current_site.sections.kept.global.includes(:translations, :section_images).ordered
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def show
|
|
16
|
+
@translation_locale = requested_locale
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def new
|
|
20
|
+
@section = current_site.sections.build(kind: params[:kind].presence || "rich_text", global: !@page)
|
|
21
|
+
@translation_locale = requested_locale
|
|
22
|
+
@section.build_missing_locale_translations
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def edit
|
|
26
|
+
@translation_locale = requested_locale
|
|
27
|
+
@section.build_missing_locale_translations
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def create
|
|
31
|
+
@section = current_site.sections.build(section_params)
|
|
32
|
+
@section.global = !@page
|
|
33
|
+
@translation_locale = requested_locale
|
|
34
|
+
|
|
35
|
+
if @section.save
|
|
36
|
+
sync_section_images(@section)
|
|
37
|
+
if turbo_page_scoped_request?
|
|
38
|
+
attach_to_page!
|
|
39
|
+
flash.now[:notice] = success_notice_for(:create)
|
|
40
|
+
load_page_show_context
|
|
41
|
+
render :page_update
|
|
42
|
+
else
|
|
43
|
+
attach_to_page! if @page
|
|
44
|
+
redirect_to after_save_path, notice: success_notice_for(:create)
|
|
45
|
+
end
|
|
46
|
+
else
|
|
47
|
+
render :new, status: :unprocessable_content
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def update
|
|
52
|
+
@translation_locale = requested_locale
|
|
53
|
+
@section.assign_attributes(section_params)
|
|
54
|
+
|
|
55
|
+
if @section.save
|
|
56
|
+
sync_section_images(@section)
|
|
57
|
+
if turbo_page_scoped_request?
|
|
58
|
+
flash.now[:notice] = success_notice_for(:update)
|
|
59
|
+
load_page_show_context
|
|
60
|
+
render :page_update
|
|
61
|
+
else
|
|
62
|
+
redirect_to after_save_path, notice: success_notice_for(:update)
|
|
63
|
+
end
|
|
64
|
+
else
|
|
65
|
+
render :edit, status: :unprocessable_content
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def destroy
|
|
70
|
+
if turbo_page_scoped_request?
|
|
71
|
+
@page.page_sections.find_by!(section_id: @section.id).destroy
|
|
72
|
+
flash.now[:notice] = success_notice_for(:destroy)
|
|
73
|
+
load_page_show_context
|
|
74
|
+
render :page_update
|
|
75
|
+
elsif @page
|
|
76
|
+
@page.page_sections.find_by!(section_id: @section.id).destroy
|
|
77
|
+
else
|
|
78
|
+
@section.discard!
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
redirect_to after_destroy_path, notice: success_notice_for(:destroy) unless performed?
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def sort
|
|
85
|
+
ids = params[:page_section_ids].to_a.map(&:to_i)
|
|
86
|
+
ids.each_with_index do |id, index|
|
|
87
|
+
@page.page_sections.find(id).update!(position: index)
|
|
88
|
+
end
|
|
89
|
+
head :ok
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def attach
|
|
93
|
+
section = current_site.sections.global.find(params[:section_id])
|
|
94
|
+
@page.page_sections.find_or_create_by!(section: section) do |page_section|
|
|
95
|
+
page_section.position = next_position
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
if turbo_page_scoped_request?
|
|
99
|
+
flash.now[:notice] = t("cms.notices.section_attached")
|
|
100
|
+
load_page_show_context
|
|
101
|
+
render :page_update
|
|
102
|
+
else
|
|
103
|
+
redirect_to admin_page_path(@page), notice: t("cms.notices.section_attached")
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
private
|
|
108
|
+
|
|
109
|
+
def set_section
|
|
110
|
+
@section = current_site.sections.kept.includes(:translations).find(params[:id])
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def requested_locale
|
|
114
|
+
params[:locale].presence ||
|
|
115
|
+
section_locale.presence ||
|
|
116
|
+
current_site.default_locale.presence ||
|
|
117
|
+
I18n.locale.to_s
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def section_locale
|
|
121
|
+
return unless @section
|
|
122
|
+
|
|
123
|
+
@section.available_locales.first
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def section_kind
|
|
127
|
+
params.dig(:section, :kind).presence || @section&.kind || params[:kind].presence || "rich_text"
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def section_params
|
|
131
|
+
permitted = %i[global enabled]
|
|
132
|
+
permitted.unshift(:kind) unless @section&.persisted?
|
|
133
|
+
permitted += permitted_settings_keys
|
|
134
|
+
permitted << { translations_attributes: %i[id locale title subtitle content] }
|
|
135
|
+
|
|
136
|
+
params.require(:section).permit(*permitted)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def permitted_settings_keys
|
|
140
|
+
case section_kind
|
|
141
|
+
when "hero" then %i[background_color cta_url]
|
|
142
|
+
when "cta" then %i[button_url alignment]
|
|
143
|
+
else []
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def sync_section_images(section)
|
|
148
|
+
return unless section_kind == "image"
|
|
149
|
+
|
|
150
|
+
ids = params.dig(:section, :image_ids).to_a.compact_blank.map(&:to_i)
|
|
151
|
+
section.section_images.destroy_all
|
|
152
|
+
ids.each_with_index do |image_id, index|
|
|
153
|
+
section.section_images.create!(image_id: image_id, position: index)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def after_save_path
|
|
158
|
+
@page ? admin_page_path(@page, locale: @translation_locale) : admin_sections_path(locale: @translation_locale)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def after_destroy_path
|
|
162
|
+
@page ? admin_page_path(@page, locale: params[:locale]) : admin_sections_path(locale: params[:locale])
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def success_notice_for(action)
|
|
166
|
+
return t("cms.notices.section_added") if action == :create && @page
|
|
167
|
+
return t("cms.notices.section_removed") if action == :destroy && @page
|
|
168
|
+
|
|
169
|
+
{
|
|
170
|
+
create: t("cms.notices.section_created"),
|
|
171
|
+
update: t("cms.notices.section_updated"),
|
|
172
|
+
destroy: t("cms.notices.section_deleted")
|
|
173
|
+
}.fetch(action)
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cms
|
|
4
|
+
module Admin
|
|
5
|
+
class SitesController < BaseController
|
|
6
|
+
before_action :redirect_bootstrap_requests_when_site_available, only: %i[new create]
|
|
7
|
+
|
|
8
|
+
def new
|
|
9
|
+
@site = Cms::Site.new(default_locale: I18n.default_locale.to_s, published: true)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def create
|
|
13
|
+
@site = Cms::Site.new(site_params)
|
|
14
|
+
purge_media_if_requested(@site)
|
|
15
|
+
|
|
16
|
+
if @site.save
|
|
17
|
+
redirect_to admin_site_path, notice: t("cms.notices.site_created")
|
|
18
|
+
else
|
|
19
|
+
render :new, status: :unprocessable_content
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def show
|
|
24
|
+
@site = current_site
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def edit
|
|
28
|
+
@site = current_site
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def update
|
|
32
|
+
@site = current_site
|
|
33
|
+
purge_media_if_requested(@site)
|
|
34
|
+
|
|
35
|
+
if @site.update(site_params)
|
|
36
|
+
redirect_to admin_site_path, notice: t("cms.notices.site_updated")
|
|
37
|
+
else
|
|
38
|
+
render :edit, status: :unprocessable_content
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def site_params
|
|
45
|
+
params.require(:site).permit(:name, :slug, :published, :default_locale, :logo)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def purge_media_if_requested(site)
|
|
49
|
+
site.logo.purge_later if params.dig(:site, :remove_logo) == "1"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def redirect_bootstrap_requests_when_site_available
|
|
53
|
+
return unless Cms::Site.exists?
|
|
54
|
+
return redirect_to(edit_admin_site_path) if configured_current_site.present?
|
|
55
|
+
|
|
56
|
+
raise_missing_current_site!
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cms
|
|
4
|
+
module Admin
|
|
5
|
+
class WebhookDeliveriesController < BaseController
|
|
6
|
+
before_action :set_webhook
|
|
7
|
+
|
|
8
|
+
def index
|
|
9
|
+
@deliveries = @webhook.deliveries.recent
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def set_webhook
|
|
15
|
+
@webhook = current_site.webhooks.find(params[:webhook_id])
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cms
|
|
4
|
+
module Admin
|
|
5
|
+
class WebhooksController < BaseController
|
|
6
|
+
before_action :set_webhook, only: %i[edit update destroy]
|
|
7
|
+
|
|
8
|
+
def index
|
|
9
|
+
@webhooks = current_site.webhooks.order(created_at: :desc)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def new
|
|
13
|
+
@webhook = current_site.webhooks.build
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def create
|
|
17
|
+
@webhook = current_site.webhooks.build(webhook_params)
|
|
18
|
+
if @webhook.save
|
|
19
|
+
redirect_to admin_webhooks_path, notice: t("cms.notices.webhook_created")
|
|
20
|
+
else
|
|
21
|
+
render :new, status: :unprocessable_content
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def edit; end
|
|
26
|
+
|
|
27
|
+
def update
|
|
28
|
+
if @webhook.update(webhook_params)
|
|
29
|
+
redirect_to admin_webhooks_path, notice: t("cms.notices.webhook_updated")
|
|
30
|
+
else
|
|
31
|
+
render :edit, status: :unprocessable_content
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def destroy
|
|
36
|
+
@webhook.destroy
|
|
37
|
+
redirect_to admin_webhooks_path, notice: t("cms.notices.webhook_deleted")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def set_webhook
|
|
43
|
+
@webhook = current_site.webhooks.find(params[:id])
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def webhook_params
|
|
47
|
+
params.require(:webhook).permit(:url, :secret, :active, events: [])
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cms
|
|
4
|
+
module Api
|
|
5
|
+
class BaseController < ApplicationController
|
|
6
|
+
include Cms::SiteResolvable
|
|
7
|
+
|
|
8
|
+
layout false
|
|
9
|
+
|
|
10
|
+
rescue_from ActiveRecord::RecordNotFound, with: :render_not_found_json
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def render_not_found_json
|
|
15
|
+
render json: { error: t("cms.errors.api.page_not_found") }, status: :not_found
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def api_site_serializer(site, resolved_locale:)
|
|
19
|
+
Cms.api_site_serializer_class.new(
|
|
20
|
+
site: site,
|
|
21
|
+
requested_locale: resolved_locale,
|
|
22
|
+
main_app: main_app
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def api_page_serializer(site, page, resolved_locale:)
|
|
27
|
+
Cms.api_page_serializer_class.new(
|
|
28
|
+
site: site,
|
|
29
|
+
page: page,
|
|
30
|
+
requested_locale: resolved_locale,
|
|
31
|
+
main_app: main_app
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def truthy_param?(value)
|
|
36
|
+
ActiveModel::Type::Boolean.new.cast(value)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def request_locale
|
|
40
|
+
params[:locale].presence ||
|
|
41
|
+
request.headers["Accept-Language"].to_s.split(",").first&.strip
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cms
|
|
4
|
+
module Api
|
|
5
|
+
module V1
|
|
6
|
+
class BaseController < Cms::Api::BaseController
|
|
7
|
+
before_action :authenticate_api_key!
|
|
8
|
+
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
def authenticate_api_key!
|
|
12
|
+
site = configured_current_site || Cms::Site.live.find_by(slug: resolved_site_slug!)
|
|
13
|
+
return render json: { error: t("cms.errors.api.site_not_found") }, status: :not_found unless site
|
|
14
|
+
|
|
15
|
+
token = request.headers["Authorization"].to_s.sub(/\ABearer\s+/, "")
|
|
16
|
+
@api_key = Cms::ApiKey.active.find_by(token: token, site: site)
|
|
17
|
+
|
|
18
|
+
unless @api_key
|
|
19
|
+
render json: { error: t("cms.errors.api.unauthorized") }, status: :unauthorized
|
|
20
|
+
return
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
@resolved_site = site
|
|
24
|
+
@api_key.touch_last_used!
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cms
|
|
4
|
+
module Api
|
|
5
|
+
module V1
|
|
6
|
+
class PagesController < BaseController
|
|
7
|
+
def show
|
|
8
|
+
site = find_site!
|
|
9
|
+
result = page_resolver_class.resolve(site: site, slug: params[:slug], locale: request_locale)
|
|
10
|
+
|
|
11
|
+
return render json: { error: t("cms.errors.api.page_not_found") }, status: :not_found unless result
|
|
12
|
+
|
|
13
|
+
I18n.with_locale(result.locale) do
|
|
14
|
+
serializer = api_page_serializer(site, result.page, resolved_locale: result.locale)
|
|
15
|
+
render json: serializer.as_json(include_site: truthy_param?(params[:include_site]))
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cms
|
|
4
|
+
module Api
|
|
5
|
+
module V1
|
|
6
|
+
class SitesController < BaseController
|
|
7
|
+
def show
|
|
8
|
+
site = find_site!
|
|
9
|
+
resolved_locale = request_locale.presence || site.default_locale
|
|
10
|
+
|
|
11
|
+
I18n.with_locale(resolved_locale) do
|
|
12
|
+
render json: api_site_serializer(site, resolved_locale: resolved_locale).as_json
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cms
|
|
4
|
+
module Public
|
|
5
|
+
class BaseController < ApplicationController
|
|
6
|
+
layout -> { Cms.config.public_layout }
|
|
7
|
+
|
|
8
|
+
helper Cms::ApplicationHelper
|
|
9
|
+
helper Cms::MediaHelper
|
|
10
|
+
helper Cms::SitesHelper
|
|
11
|
+
helper Cms::SectionsHelper
|
|
12
|
+
helper Cms::PagesHelper
|
|
13
|
+
|
|
14
|
+
rescue_from ActiveRecord::RecordNotFound, with: :render_not_found
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def render_not_found
|
|
19
|
+
render plain: t("cms.errors.page_not_found"), status: :not_found
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|