helios-press 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +122 -0
  4. data/Rakefile +3 -0
  5. data/app/assets/stylesheets/helios/press/application.css +15 -0
  6. data/app/assets/stylesheets/helios/press/blocks.css +231 -0
  7. data/app/controllers/helios/press/admin/base_controller.rb +9 -0
  8. data/app/controllers/helios/press/admin/block_images_controller.rb +75 -0
  9. data/app/controllers/helios/press/admin/blocks_controller.rb +103 -0
  10. data/app/controllers/helios/press/admin/posts_controller.rb +72 -0
  11. data/app/controllers/helios/press/api/base_controller.rb +25 -0
  12. data/app/controllers/helios/press/api/posts_controller.rb +54 -0
  13. data/app/controllers/helios/press/application_controller.rb +6 -0
  14. data/app/controllers/helios/press/posts_controller.rb +9 -0
  15. data/app/helpers/helios/press/application_helper.rb +39 -0
  16. data/app/javascript/helios/press/controllers/blocks_controller.js +276 -0
  17. data/app/javascript/helios/press/controllers/image_block_controller.js +178 -0
  18. data/app/javascript/helios/press/controllers/text_block_controller.js +63 -0
  19. data/app/javascript/helios/press/index.js +6 -0
  20. data/app/jobs/helios/press/application_job.rb +6 -0
  21. data/app/mailers/helios/press/application_mailer.rb +8 -0
  22. data/app/models/helios/press/application_record.rb +7 -0
  23. data/app/models/helios/press/block.rb +34 -0
  24. data/app/models/helios/press/block_image.rb +16 -0
  25. data/app/models/helios/press/post.rb +39 -0
  26. data/app/views/helios/press/admin/blocks/_block.html.erb +25 -0
  27. data/app/views/helios/press/admin/blocks/_image_block.html.erb +47 -0
  28. data/app/views/helios/press/admin/blocks/_image_item.html.erb +38 -0
  29. data/app/views/helios/press/admin/blocks/_text_block.html.erb +39 -0
  30. data/app/views/helios/press/admin/blocks/_video_block.html.erb +43 -0
  31. data/app/views/helios/press/admin/posts/_blocks_container.html.erb +27 -0
  32. data/app/views/helios/press/admin/posts/_form.html.erb +48 -0
  33. data/app/views/helios/press/admin/posts/edit.html.erb +26 -0
  34. data/app/views/helios/press/admin/posts/index.html.erb +31 -0
  35. data/app/views/helios/press/admin/posts/new.html.erb +12 -0
  36. data/app/views/helios/press/posts/blocks/_image_container.html.erb +18 -0
  37. data/app/views/helios/press/posts/blocks/_text.html.erb +3 -0
  38. data/app/views/helios/press/posts/blocks/_video_container.html.erb +12 -0
  39. data/app/views/helios/press/posts/show.html.erb +45 -0
  40. data/app/views/layouts/helios/press/admin.html.erb +1 -0
  41. data/app/views/layouts/helios/press/application.html.erb +17 -0
  42. data/config/routes.rb +21 -0
  43. data/db/migrate/20250510000001_create_helios_press_posts.rb +18 -0
  44. data/db/migrate/20250510000002_create_helios_press_blocks.rb +14 -0
  45. data/db/migrate/20250510000003_create_helios_press_block_images.rb +13 -0
  46. data/lib/helios/press/configuration.rb +17 -0
  47. data/lib/helios/press/engine.rb +11 -0
  48. data/lib/helios/press/version.rb +5 -0
  49. data/lib/helios/press.rb +23 -0
  50. data/lib/tasks/helios/press_tasks.rake +4 -0
  51. metadata +148 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e3b7ae24fbb56776cc12ecba0e5c5f8c4fe86add758aceeddf4a2556233f65ad
4
+ data.tar.gz: 27ec84d4133ac49444df9b70f145214e1c57757f8098e19af4d3261271a08eef
5
+ SHA512:
6
+ metadata.gz: d0224ef4872b8727d78d1dc9e1aba083b254bf6380a0e1cf5ea5ffbd4b30a8903b1847253963620f7a2f84a4da93a621d31fba0a81b0297c7e70b9f68236fddb
7
+ data.tar.gz: 26d02bcc128f38ead744f8dacc50005caa749bb2f8da86249af09053aed2461766f32594e439242a4bf91765816dfcb6c5e3492d91281bf1d5a2a8a590c75b38
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright TODO: Write your name
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,122 @@
1
+ # Helios::Press
2
+
3
+ A Rails engine providing a WordPress-like block editor for blog posts. Supports text (ActionText), image stacks with configurable grid layouts, and video blocks (via helios-videos) with drag-to-reorder.
4
+
5
+ ## Installation
6
+
7
+ Add to your Gemfile:
8
+
9
+ ```ruby
10
+ gem "helios-press"
11
+ gem "helios-videos" # Optional: enables video blocks
12
+ ```
13
+
14
+ Then:
15
+
16
+ ```bash
17
+ bundle install
18
+ bin/rails helios_press:install:migrations
19
+ bin/rails db:migrate
20
+ ```
21
+
22
+ **Prerequisites:**
23
+ - ActionText must be installed in your host app (`bin/rails action_text:install`)
24
+ - ActiveStorage must be installed (`bin/rails active_storage:install`)
25
+
26
+ ## Configuration
27
+
28
+ Create `config/initializers/helios_press.rb`:
29
+
30
+ ```ruby
31
+ Helios::Press.configure do |config|
32
+ # Parent controller for admin views (must provide authentication)
33
+ config.admin_parent_controller = "Admin::BaseController"
34
+
35
+ # Optional slug prefix for posts
36
+ config.post_slug_prefix = nil # e.g., "blog/" for /blog/my-post
37
+
38
+ # API authentication (for external post ingestion)
39
+ # Default: checks X-API-Key header against BLOG_INGEST_API_KEY env var
40
+ config.api_authentication = ->(controller) {
41
+ expected = ENV["BLOG_INGEST_API_KEY"]
42
+ provided = controller.request.headers["X-API-Key"]
43
+ unless expected.present? && ActiveSupport::SecurityUtils.secure_compare(expected, provided.to_s)
44
+ controller.render json: { error: "unauthorized" }, status: :unauthorized
45
+ end
46
+ }
47
+ end
48
+ ```
49
+
50
+ ## Routes
51
+
52
+ Mount the engine:
53
+
54
+ ```ruby
55
+ mount Helios::Press::Engine, at: "/helios_press"
56
+ ```
57
+
58
+ This provides:
59
+ - **Admin UI**: `/helios_press/admin/posts` - Full block editor
60
+ - **API**: `POST /helios_press/api/posts` - Ingest posts from external sources
61
+ - **Public**: Define your own catch-all route pointing to `Helios::Press::PostsController#show`
62
+
63
+ ## JavaScript Setup
64
+
65
+ Register the Stimulus controllers in your host app:
66
+
67
+ ```javascript
68
+ import {
69
+ HeliosPressBlocksController,
70
+ HeliosPressTextBlockController,
71
+ HeliosPressImageBlockController
72
+ } from "helios-press"
73
+
74
+ application.register("helios-press-blocks", HeliosPressBlocksController)
75
+ application.register("helios-press-text-block", HeliosPressTextBlockController)
76
+ application.register("helios-press-image-block", HeliosPressImageBlockController)
77
+
78
+ // If using helios-videos:
79
+ import { HeliosVideoBlockController } from "helios-videos"
80
+ application.register("helios-video-block", HeliosVideoBlockController)
81
+ ```
82
+
83
+ **npm dependencies** (add to your host app's package.json):
84
+ - `sortablejs`
85
+ - `@hotwired/stimulus`
86
+ - `@rails/activestorage`
87
+ - `trix` and `@rails/actiontext`
88
+
89
+ ## CSS
90
+
91
+ Include the block editor styles in your asset pipeline:
92
+
93
+ ```css
94
+ /* In your application CSS */
95
+ @import "helios/press/blocks";
96
+ ```
97
+
98
+ ## Block Types
99
+
100
+ - **Text**: Rich text editing via ActionText. Double-click to edit, Save/Cancel buttons.
101
+ - **Image Container**: Drag images to add. Configurable grid (1-6 images per row). Reorder by dragging. Click captions to edit.
102
+ - **Video Container** (requires helios-videos): Drag a video file to create. Direct upload to S3, automatic processing via Mux/Cloudflare.
103
+
104
+ ## API Ingestion
105
+
106
+ POST to `/helios_press/api/posts` with `X-API-Key` header:
107
+
108
+ ```json
109
+ {
110
+ "external_id": "my-post-123",
111
+ "title": "My Post Title",
112
+ "slug": "my-post-title",
113
+ "description": "Meta description",
114
+ "keywords": "keyword1, keyword2",
115
+ "body_html": "<p>Post content...</p>",
116
+ "published": true
117
+ }
118
+ ```
119
+
120
+ ## License
121
+
122
+ Proprietary. All rights reserved.
data/Rakefile ADDED
@@ -0,0 +1,3 @@
1
+ require "bundler/setup"
2
+
3
+ require "bundler/gem_tasks"
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,231 @@
1
+ /* Helios Press - Block Editor Styles */
2
+
3
+ .block-placeholder {
4
+ position: relative;
5
+ height: 16px;
6
+ margin: 16px 0;
7
+ cursor: pointer;
8
+ transition: all 0.2s ease;
9
+ }
10
+
11
+ .block-placeholder .placeholder-line {
12
+ position: absolute;
13
+ top: 50%;
14
+ left: 0;
15
+ right: 0;
16
+ height: 2px;
17
+ background-color: transparent;
18
+ transition: all 0.2s ease;
19
+ transform: translateY(-50%);
20
+ }
21
+
22
+ .block-placeholder:hover .placeholder-line,
23
+ .block-placeholder.drag-over .placeholder-line {
24
+ height: 4px;
25
+ background-color: #0d6efd;
26
+ box-shadow: 0 0 8px rgba(13, 110, 253, 0.3);
27
+ }
28
+
29
+ .content-block {
30
+ position: relative;
31
+ background: white;
32
+ border: 1px solid #dee2e6;
33
+ border-radius: 0.375rem;
34
+ margin: 0 0 0 2.5rem;
35
+ padding: 1rem;
36
+ transition: box-shadow 0.2s ease;
37
+ }
38
+
39
+ .content-block:hover {
40
+ box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
41
+ }
42
+
43
+ .content-block .drag-handle {
44
+ position: absolute;
45
+ left: -2.5rem;
46
+ top: 0.5rem;
47
+ cursor: grab;
48
+ color: #6c757d;
49
+ font-size: 1.25rem;
50
+ padding: 0.25rem;
51
+ transition: color 0.2s ease;
52
+ }
53
+
54
+ .content-block .drag-handle:active {
55
+ cursor: grabbing;
56
+ }
57
+
58
+ .content-block .drag-handle:hover {
59
+ color: #495057;
60
+ }
61
+
62
+ .content-block .block-content {
63
+ min-height: 50px;
64
+ }
65
+
66
+ .block-ghost {
67
+ opacity: 0.4;
68
+ background-color: #f8f9fa;
69
+ }
70
+
71
+ .block-dragging {
72
+ opacity: 0.8;
73
+ cursor: grabbing;
74
+ }
75
+
76
+ /* Block View/Edit Mode */
77
+ .block-view-mode {
78
+ min-height: 50px;
79
+ padding: 0.5rem;
80
+ cursor: pointer;
81
+ border-radius: 0.25rem;
82
+ transition: background-color 0.2s ease;
83
+ }
84
+
85
+ .block-view-mode:hover {
86
+ background-color: #f8f9fa;
87
+ }
88
+
89
+ .block-view-mode p:last-child {
90
+ margin-bottom: 0;
91
+ }
92
+
93
+ .block-edit-mode trix-editor {
94
+ min-height: 100px;
95
+ }
96
+
97
+ /* Image Block */
98
+ .image-block-container .images-grid {
99
+ display: grid;
100
+ grid-template-columns: repeat(var(--grid-columns, 3), 1fr);
101
+ gap: 1rem;
102
+ margin-bottom: 1rem;
103
+ }
104
+
105
+ .image-block-container .image-item {
106
+ position: relative;
107
+ display: flex;
108
+ flex-direction: column;
109
+ gap: 0.5rem;
110
+ }
111
+
112
+ .image-block-container .image-wrapper {
113
+ position: relative;
114
+ aspect-ratio: 1;
115
+ overflow: hidden;
116
+ border-radius: 0.375rem;
117
+ background-color: #f8f9fa;
118
+ }
119
+
120
+ .image-block-container .image-wrapper .block-image {
121
+ width: 100%;
122
+ height: 100%;
123
+ object-fit: cover;
124
+ }
125
+
126
+ .image-block-container .image-wrapper .image-delete {
127
+ position: absolute;
128
+ top: 0.5rem;
129
+ right: 0.5rem;
130
+ opacity: 0;
131
+ transition: opacity 0.2s ease;
132
+ }
133
+
134
+ .image-block-container .image-wrapper:hover .image-delete {
135
+ opacity: 1;
136
+ }
137
+
138
+ .image-block-container .image-caption .caption-view {
139
+ min-height: 1.5rem;
140
+ padding: 0.25rem 0.5rem;
141
+ cursor: pointer;
142
+ border-radius: 0.25rem;
143
+ font-size: 0.875rem;
144
+ color: #6c757d;
145
+ transition: background-color 0.2s ease;
146
+ }
147
+
148
+ .image-block-container .image-caption .caption-view:hover {
149
+ background-color: #f8f9fa;
150
+ }
151
+
152
+ .image-block-container .image-caption .caption-edit input {
153
+ font-size: 0.875rem;
154
+ }
155
+
156
+ .image-block-container .image-dropzone {
157
+ display: flex;
158
+ flex-direction: column;
159
+ align-items: center;
160
+ justify-content: center;
161
+ aspect-ratio: 1;
162
+ padding: 1rem;
163
+ border: 2px dashed #dee2e6;
164
+ border-radius: 0.375rem;
165
+ background-color: #f8f9fa;
166
+ color: #6c757d;
167
+ cursor: pointer;
168
+ transition: all 0.2s ease;
169
+ }
170
+
171
+ .image-block-container .image-dropzone i {
172
+ font-size: 1.5rem;
173
+ margin-bottom: 0.25rem;
174
+ }
175
+
176
+ .image-block-container .image-dropzone p {
177
+ margin: 0;
178
+ font-size: 0.75rem;
179
+ text-align: center;
180
+ }
181
+
182
+ .image-block-container .image-dropzone:hover,
183
+ .image-block-container .image-dropzone.drag-over {
184
+ border-color: #0d6efd;
185
+ background-color: rgba(13, 110, 253, 0.05);
186
+ color: #0d6efd;
187
+ }
188
+
189
+ .image-ghost {
190
+ opacity: 0.4;
191
+ }
192
+
193
+ /* Public Post View */
194
+ .post {
195
+ max-width: 800px;
196
+ margin: 0 auto;
197
+ }
198
+
199
+ .post .post-header {
200
+ border-bottom: 2px solid #dee2e6;
201
+ padding-bottom: 1.5rem;
202
+ }
203
+
204
+ .post .post-header h1 {
205
+ font-weight: 700;
206
+ line-height: 1.2;
207
+ }
208
+
209
+ .post .post-content .text-block {
210
+ font-size: 1.125rem;
211
+ line-height: 1.75;
212
+ color: #212529;
213
+ }
214
+
215
+ .post .post-content .image-block .images-grid {
216
+ display: grid;
217
+ grid-template-columns: repeat(var(--grid-columns, 3), 1fr);
218
+ gap: 1.5rem;
219
+ }
220
+
221
+ .post .post-content .image-block .images-grid .image-item img {
222
+ width: 100%;
223
+ height: auto;
224
+ object-fit: cover;
225
+ }
226
+
227
+ .post .post-content .image-block .images-grid .image-item .image-caption {
228
+ font-size: 0.875rem;
229
+ font-style: italic;
230
+ text-align: center;
231
+ }
@@ -0,0 +1,9 @@
1
+ module Helios
2
+ module Press
3
+ module Admin
4
+ class BaseController < ::ApplicationController
5
+ layout "helios/press/admin"
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,75 @@
1
+ module Helios
2
+ module Press
3
+ module Admin
4
+ class BlockImagesController < Helios::Press::Admin::BaseController
5
+ before_action :set_post
6
+ before_action :set_block
7
+ before_action :set_block_image, only: [:update, :destroy]
8
+
9
+ def create
10
+ position = @block.block_images.maximum(:position).to_i + 1
11
+ @block_image = @block.block_images.build(block_image_params)
12
+ @block_image.position = position
13
+
14
+ if @block_image.save
15
+ respond_to do |format|
16
+ format.turbo_stream do
17
+ render turbo_stream: turbo_stream.replace(
18
+ "block-#{@block.id}",
19
+ partial: "helios/press/admin/blocks/block",
20
+ locals: { block: @block.reload, post: @post }
21
+ )
22
+ end
23
+ end
24
+ else
25
+ head :unprocessable_entity
26
+ end
27
+ end
28
+
29
+ def update
30
+ if @block_image.update(block_image_params)
31
+ head :ok
32
+ else
33
+ head :unprocessable_entity
34
+ end
35
+ end
36
+
37
+ def destroy
38
+ position = @block_image.position
39
+ @block_image.destroy
40
+
41
+ @block.block_images.where("position > ?", position).update_all("position = position - 1")
42
+
43
+ head :ok
44
+ end
45
+
46
+ def reorder
47
+ image_ids = params[:image_ids]
48
+ image_ids.each_with_index do |image_id, index|
49
+ @block.block_images.find(image_id).update_column(:position, index)
50
+ end
51
+
52
+ head :ok
53
+ end
54
+
55
+ private
56
+
57
+ def set_post
58
+ @post = Post.find(params[:post_id])
59
+ end
60
+
61
+ def set_block
62
+ @block = @post.blocks.find(params[:block_id])
63
+ end
64
+
65
+ def set_block_image
66
+ @block_image = @block.block_images.find(params[:id])
67
+ end
68
+
69
+ def block_image_params
70
+ params.require(:block_image).permit(:file, :caption)
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,103 @@
1
+ module Helios
2
+ module Press
3
+ module Admin
4
+ class BlocksController < Helios::Press::Admin::BaseController
5
+ before_action :set_post
6
+ before_action :set_block, only: [:update, :destroy]
7
+
8
+ def create
9
+ target_position = block_params[:position].to_i
10
+
11
+ # Shift positions of blocks at and after the new position
12
+ @post.blocks.where("position >= ?", target_position).update_all("position = position + 1")
13
+
14
+ @block = @post.blocks.build(block_params.except(:position))
15
+ @block.position = target_position
16
+
17
+ if @block.save
18
+ # Handle image uploads for image_container blocks
19
+ if @block.image_container? && params[:images].present?
20
+ params[:images].each do |key, image_data|
21
+ file = image_data[:file]
22
+ next unless file.present?
23
+
24
+ position = key.to_i
25
+ block_image = @block.block_images.build(position: position)
26
+ block_image.file.attach(file)
27
+ block_image.save!
28
+ end
29
+ end
30
+
31
+ # Handle video upload for video_container blocks (when helios-videos is present)
32
+ if @block.video_container? && params[:video_signed_id].present? && Helios::Press.videos_enabled?
33
+ video = Helios::Videos::Video.new(name: params[:video_name], block: @block)
34
+ video.video_file.attach(params[:video_signed_id])
35
+ video.save!
36
+ end
37
+
38
+ respond_to do |format|
39
+ format.turbo_stream do
40
+ render turbo_stream: turbo_stream.replace(
41
+ "blocks-container",
42
+ partial: "helios/press/admin/posts/blocks_container",
43
+ locals: { post: @post.reload }
44
+ )
45
+ end
46
+ end
47
+ else
48
+ head :unprocessable_entity
49
+ end
50
+ end
51
+
52
+ def update
53
+ if @block.update(block_params)
54
+ respond_to do |format|
55
+ format.turbo_stream do
56
+ render turbo_stream: turbo_stream.replace(
57
+ "block-#{@block.id}",
58
+ partial: "helios/press/admin/blocks/block",
59
+ locals: { block: @block, post: @post }
60
+ )
61
+ end
62
+ format.html { head :ok }
63
+ end
64
+ else
65
+ head :unprocessable_entity
66
+ end
67
+ end
68
+
69
+ def destroy
70
+ position = @block.position
71
+ @block.destroy
72
+
73
+ @post.blocks.where("position > ?", position).update_all("position = position - 1")
74
+
75
+ head :ok
76
+ end
77
+
78
+ def reorder
79
+ block_ids = params[:block_ids]
80
+ block_ids.each_with_index do |block_id, index|
81
+ @post.blocks.find(block_id).update_column(:position, index)
82
+ end
83
+
84
+ head :ok
85
+ end
86
+
87
+ private
88
+
89
+ def set_post
90
+ @post = Post.find(params[:post_id])
91
+ end
92
+
93
+ def set_block
94
+ @block = @post.blocks.find(params[:id])
95
+ end
96
+
97
+ def block_params
98
+ params.require(:block).permit(:block_type, :position, :content, :columns)
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,72 @@
1
+ module Helios
2
+ module Press
3
+ module Admin
4
+ class PostsController < Helios::Press::Admin::BaseController
5
+ before_action :set_post, only: [:edit, :update, :destroy]
6
+
7
+ def index
8
+ @posts = Post.reverse_sorted
9
+
10
+ if params[:search].present?
11
+ search_term = "%#{params[:search]}%"
12
+ @posts = @posts.where("name ILIKE ? OR keywords ILIKE ?", search_term, search_term)
13
+ end
14
+
15
+ @posts = @posts.order(created_at: :desc)
16
+ end
17
+
18
+ def new
19
+ @post = Post.new
20
+ end
21
+
22
+ def create
23
+ @post = Post.new(post_params)
24
+
25
+ if @post.save
26
+ redirect_to helios_press.edit_admin_post_path(@post), notice: "Post was successfully created."
27
+ else
28
+ render :new, status: :unprocessable_entity
29
+ end
30
+ end
31
+
32
+ def edit
33
+ end
34
+
35
+ def update
36
+ if params[:publish].present?
37
+ update_params = post_params.merge(published: true)
38
+ success_message = "Post was successfully published."
39
+ else
40
+ update_params = post_params
41
+ success_message = "Post was successfully updated."
42
+ end
43
+
44
+ if @post.update(update_params)
45
+ redirect_to helios_press.edit_admin_post_path(@post), notice: success_message
46
+ else
47
+ render :edit, status: :unprocessable_entity
48
+ end
49
+ end
50
+
51
+ def destroy
52
+ @post.destroy
53
+ redirect_to helios_press.admin_posts_path, notice: "Post was successfully deleted."
54
+ end
55
+
56
+ private
57
+
58
+ def set_post
59
+ @post = Post.find(params[:id])
60
+ end
61
+
62
+ def post_params
63
+ params.require(:post).permit(:name, :published, :slug, :keywords, :description)
64
+ end
65
+
66
+ def helios_press
67
+ Helios::Press::Engine.routes.url_helpers
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,25 @@
1
+ module Helios
2
+ module Press
3
+ module Api
4
+ class BaseController < ActionController::API
5
+ before_action :authenticate_ingest!
6
+
7
+ private
8
+
9
+ def authenticate_ingest!
10
+ auth_proc = Helios::Press.configuration.api_authentication
11
+ if auth_proc
12
+ auth_proc.call(self)
13
+ else
14
+ # Default: check X-API-Key header against BLOG_INGEST_API_KEY env var
15
+ expected = ENV["BLOG_INGEST_API_KEY"]
16
+ provided = request.headers["X-API-Key"]
17
+ return if expected.present? && ActiveSupport::SecurityUtils.secure_compare(expected, provided.to_s)
18
+
19
+ render json: { error: "unauthorized" }, status: :unauthorized
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end