railspress-engine 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 (56) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +67 -0
  4. data/Rakefile +10 -0
  5. data/app/assets/javascripts/railspress/admin.js +210 -0
  6. data/app/assets/stylesheets/actiontext.css +440 -0
  7. data/app/assets/stylesheets/railspress/admin.css +1199 -0
  8. data/app/assets/stylesheets/railspress/application.css +15 -0
  9. data/app/controllers/railspress/admin/base_controller.rb +38 -0
  10. data/app/controllers/railspress/admin/categories_controller.rb +53 -0
  11. data/app/controllers/railspress/admin/dashboard_controller.rb +12 -0
  12. data/app/controllers/railspress/admin/posts_controller.rb +71 -0
  13. data/app/controllers/railspress/admin/tags_controller.rb +50 -0
  14. data/app/controllers/railspress/application_controller.rb +4 -0
  15. data/app/helpers/railspress/application_helper.rb +4 -0
  16. data/app/jobs/railspress/application_job.rb +4 -0
  17. data/app/mailers/railspress/application_mailer.rb +6 -0
  18. data/app/models/railspress/application_record.rb +6 -0
  19. data/app/models/railspress/category.rb +18 -0
  20. data/app/models/railspress/post.rb +74 -0
  21. data/app/models/railspress/post_tag.rb +8 -0
  22. data/app/models/railspress/tag.rb +32 -0
  23. data/app/views/active_storage/blobs/_blob.html.erb +14 -0
  24. data/app/views/layouts/action_text/contents/_content.html.erb +3 -0
  25. data/app/views/layouts/railspress/admin.html.erb +41 -0
  26. data/app/views/layouts/railspress/application.html.erb +17 -0
  27. data/app/views/railspress/admin/categories/_form.html.erb +32 -0
  28. data/app/views/railspress/admin/categories/edit.html.erb +6 -0
  29. data/app/views/railspress/admin/categories/index.html.erb +42 -0
  30. data/app/views/railspress/admin/categories/new.html.erb +6 -0
  31. data/app/views/railspress/admin/dashboard/index.html.erb +45 -0
  32. data/app/views/railspress/admin/posts/_form.html.erb +129 -0
  33. data/app/views/railspress/admin/posts/edit.html.erb +2 -0
  34. data/app/views/railspress/admin/posts/index.html.erb +55 -0
  35. data/app/views/railspress/admin/posts/new.html.erb +2 -0
  36. data/app/views/railspress/admin/posts/show.html.erb +47 -0
  37. data/app/views/railspress/admin/shared/_flash.html.erb +5 -0
  38. data/app/views/railspress/admin/shared/_sidebar.html.erb +38 -0
  39. data/app/views/railspress/admin/tags/_form.html.erb +27 -0
  40. data/app/views/railspress/admin/tags/edit.html.erb +6 -0
  41. data/app/views/railspress/admin/tags/index.html.erb +42 -0
  42. data/app/views/railspress/admin/tags/new.html.erb +6 -0
  43. data/config/routes.rb +9 -0
  44. data/db/migrate/20241218000001_create_railspress_categories.rb +14 -0
  45. data/db/migrate/20241218000002_create_railspress_tags.rb +13 -0
  46. data/db/migrate/20241218000003_create_railspress_posts.rb +19 -0
  47. data/db/migrate/20241218000004_create_railspress_post_tags.rb +12 -0
  48. data/lib/generators/railspress/authors/authors_generator.rb +73 -0
  49. data/lib/generators/railspress/authors/templates/add_author_to_railspress_posts.rb.tt +8 -0
  50. data/lib/generators/railspress/authors/templates/railspress.rb.tt +31 -0
  51. data/lib/generators/railspress/install/install_generator.rb +144 -0
  52. data/lib/railspress/engine.rb +14 -0
  53. data/lib/railspress/version.rb +3 -0
  54. data/lib/railspress.rb +79 -0
  55. data/lib/tasks/railspress_tasks.rake +4 -0
  56. metadata +141 -0
@@ -0,0 +1,129 @@
1
+ <%= form_with model: [:admin, @post], class: "rp-form" do |form| %>
2
+ <% if @post.errors.any? %>
3
+ <div class="rp-form-errors">
4
+ <ul>
5
+ <% @post.errors.full_messages.each do |msg| %>
6
+ <li><%= msg %></li>
7
+ <% end %>
8
+ </ul>
9
+ </div>
10
+ <% end %>
11
+
12
+ <div class="rp-form-layout">
13
+ <!-- Main content column -->
14
+ <div class="rp-form-main">
15
+ <div class="rp-form-group">
16
+ <%= form.label :title, class: "rp-label" %>
17
+ <%= form.text_field :title,
18
+ class: "rp-input rp-input--title",
19
+ placeholder: "Enter post title...",
20
+ data: { slug_source: true },
21
+ required: true %>
22
+ </div>
23
+
24
+ <div class="rp-form-group">
25
+ <%= form.label :slug, class: "rp-label" %>
26
+ <%= form.text_field :slug,
27
+ class: "rp-input rp-input--mono",
28
+ placeholder: "auto-generated-from-title",
29
+ data: { slug_target: true } %>
30
+ <p class="rp-hint">Leave blank to auto-generate from title</p>
31
+ </div>
32
+
33
+ <div class="rp-form-group">
34
+ <%= form.label :content, class: "rp-label" %>
35
+ <%= form.rich_text_area :content, class: "rp-rich-text" %>
36
+ <p class="rp-hint">Rich text editor powered by Lexxy</p>
37
+ </div>
38
+ </div>
39
+
40
+ <!-- Sidebar column -->
41
+ <div class="rp-form-sidebar">
42
+ <div class="rp-sidebar-section">
43
+ <h3 class="rp-sidebar-title">Publishing</h3>
44
+
45
+ <div class="rp-form-group">
46
+ <%= form.label :status, class: "rp-label" %>
47
+ <%= form.select :status,
48
+ Railspress::Post.statuses.keys.map { |s| [s.titleize, s] },
49
+ {},
50
+ class: "rp-select" %>
51
+ </div>
52
+
53
+ <div class="rp-form-group">
54
+ <%= form.label :category_id, "Category", class: "rp-label" %>
55
+ <%= form.collection_select :category_id, @categories, :id, :name,
56
+ { prompt: "No category" },
57
+ { class: "rp-select" } %>
58
+ </div>
59
+
60
+ <% if authors_enabled? %>
61
+ <div class="rp-form-group">
62
+ <%= form.label :author_id, "Author", class: "rp-label" %>
63
+ <%= form.collection_select :author_id, available_authors, :id, Railspress.author_display_method,
64
+ { prompt: "No author", selected: @post.author_id },
65
+ { class: "rp-select" } %>
66
+ </div>
67
+ <% end %>
68
+
69
+ <div class="rp-form-group">
70
+ <%= form.label :tag_list, "Tags", class: "rp-label" %>
71
+ <%= form.text_field :tag_list,
72
+ class: "rp-input",
73
+ placeholder: "ruby, rails, tutorial",
74
+ value: @post.tag_list %>
75
+ <p class="rp-hint">Comma-separated. New tags created automatically.</p>
76
+ </div>
77
+ </div>
78
+
79
+ <% if header_images_enabled? %>
80
+ <div class="rp-sidebar-section">
81
+ <h3 class="rp-sidebar-title">Header Image</h3>
82
+
83
+ <div class="rp-form-group">
84
+ <% if @post.header_image.attached? %>
85
+ <div class="rp-header-image-preview">
86
+ <%= image_tag @post.header_image.variant(resize_to_limit: [400, 300]), class: "rp-header-image-thumb" %>
87
+ </div>
88
+ <% end %>
89
+
90
+ <%= form.label :header_image, class: "rp-label" %>
91
+ <%= form.file_field :header_image, accept: "image/*", class: "rp-file-input" %>
92
+ <p class="rp-hint">Recommended: 1200x630px for social sharing</p>
93
+
94
+ <% if @post.header_image.attached? %>
95
+ <label class="rp-checkbox-label">
96
+ <%= form.check_box :remove_header_image, {}, "1", "0" %>
97
+ Remove current image
98
+ </label>
99
+ <% end %>
100
+ </div>
101
+ </div>
102
+ <% end %>
103
+
104
+ <div class="rp-sidebar-section">
105
+ <h3 class="rp-sidebar-title">SEO</h3>
106
+
107
+ <div class="rp-form-group">
108
+ <%= form.label :meta_title, class: "rp-label" %>
109
+ <%= form.text_field :meta_title,
110
+ class: "rp-input",
111
+ placeholder: "Defaults to post title" %>
112
+ </div>
113
+
114
+ <div class="rp-form-group">
115
+ <%= form.label :meta_description, class: "rp-label" %>
116
+ <%= form.text_area :meta_description,
117
+ class: "rp-input",
118
+ rows: 3,
119
+ placeholder: "Brief description for search engines" %>
120
+ </div>
121
+ </div>
122
+ </div>
123
+ </div>
124
+
125
+ <div class="rp-form-actions">
126
+ <%= form.submit class: "rp-btn rp-btn--primary" %>
127
+ <%= link_to "Cancel", admin_posts_path, class: "rp-btn rp-btn--secondary" %>
128
+ </div>
129
+ <% end %>
@@ -0,0 +1,2 @@
1
+ <h1 class="rp-page-title rp-page-title--standalone">Edit Post</h1>
2
+ <%= render "form" %>
@@ -0,0 +1,55 @@
1
+ <div class="rp-page-header">
2
+ <h1 class="rp-page-title">Posts</h1>
3
+ <div class="rp-page-actions">
4
+ <%= link_to "New Post", new_admin_post_path, class: "rp-btn rp-btn--primary" %>
5
+ </div>
6
+ </div>
7
+
8
+ <div class="rp-card">
9
+ <div class="rp-table--responsive">
10
+ <table class="rp-table">
11
+ <thead>
12
+ <tr>
13
+ <th>Title</th>
14
+ <th>Category</th>
15
+ <% if authors_enabled? %><th>Author</th><% end %>
16
+ <th>Status</th>
17
+ <th>Date</th>
18
+ <th class="rp-table-actions">Actions</th>
19
+ </tr>
20
+ </thead>
21
+ <tbody>
22
+ <% @posts.each do |post| %>
23
+ <tr>
24
+ <td>
25
+ <%= link_to post.title, admin_post_path(post), class: "rp-link rp-table-primary" %>
26
+ <% if post.tags.any? %>
27
+ <div class="rp-tag-list">
28
+ <% post.tags.each do |tag| %>
29
+ <span class="rp-tag"><%= tag.name %></span>
30
+ <% end %>
31
+ </div>
32
+ <% end %>
33
+ </td>
34
+ <td><%= post.category&.name || "—" %></td>
35
+ <% if authors_enabled? %>
36
+ <td><%= post.author&.public_send(Railspress.author_display_method) || "—" %></td>
37
+ <% end %>
38
+ <td>
39
+ <span class="rp-badge rp-badge--<%= post.status %>">
40
+ <%= post.status.titleize %>
41
+ </span>
42
+ </td>
43
+ <td class="rp-table-secondary"><%= post.created_at.strftime("%b %d, %Y") %></td>
44
+ <td class="rp-table-actions">
45
+ <%= link_to "Edit", edit_admin_post_path(post), class: "rp-link" %>
46
+ <%= button_to "Delete", admin_post_path(post), method: :delete,
47
+ data: { turbo_confirm: "Delete this post?" },
48
+ class: "rp-link rp-link--danger" %>
49
+ </td>
50
+ </tr>
51
+ <% end %>
52
+ </tbody>
53
+ </table>
54
+ </div>
55
+ </div>
@@ -0,0 +1,2 @@
1
+ <h1 class="rp-page-title rp-page-title--standalone">New Post</h1>
2
+ <%= render "form" %>
@@ -0,0 +1,47 @@
1
+ <div class="rp-page-header">
2
+ <div>
3
+ <h1 class="rp-page-title"><%= @post.title %></h1>
4
+ <div class="rp-post-meta">
5
+ <span class="rp-badge rp-badge--<%= @post.status %>"><%= @post.status.titleize %></span>
6
+ <% if @post.category %>
7
+ <span><%= @post.category.name %></span>
8
+ <% end %>
9
+ <% if authors_enabled? && @post.author %>
10
+ <span>by <%= @post.author.public_send(Railspress.author_display_method) %></span>
11
+ <% end %>
12
+ <span><%= @post.created_at.strftime("%b %d, %Y") %></span>
13
+ </div>
14
+ </div>
15
+ <div class="rp-page-actions">
16
+ <%= link_to "Edit", edit_admin_post_path(@post), class: "rp-btn rp-btn--primary" %>
17
+ <%= link_to "Back", admin_posts_path, class: "rp-btn rp-btn--secondary" %>
18
+ </div>
19
+ </div>
20
+
21
+ <% if @post.tags.any? %>
22
+ <div class="rp-tag-list rp-post-tags">
23
+ <% @post.tags.each do |tag| %>
24
+ <span class="rp-tag"><%= tag.name %></span>
25
+ <% end %>
26
+ </div>
27
+ <% end %>
28
+
29
+ <% if header_images_enabled? && @post.header_image.attached? %>
30
+ <div class="rp-header-image">
31
+ <%= image_tag @post.header_image.variant(resize_to_limit: [1200, 630]), class: "rp-header-image-full" %>
32
+ </div>
33
+ <% end %>
34
+
35
+ <article class="rp-card rp-card--padded rp-prose">
36
+ <%= @post.content %>
37
+ </article>
38
+
39
+ <% if @post.meta_title.present? || @post.meta_description.present? %>
40
+ <div class="rp-seo-preview">
41
+ <h3 class="rp-seo-preview-title">SEO Preview</h3>
42
+ <div class="rp-seo-title"><%= @post.meta_title.presence || @post.title %></div>
43
+ <% if @post.meta_description.present? %>
44
+ <div class="rp-seo-description"><%= @post.meta_description %></div>
45
+ <% end %>
46
+ </div>
47
+ <% end %>
@@ -0,0 +1,5 @@
1
+ <% flash.each do |type, message| %>
2
+ <div class="rp-flash rp-flash--<%= type %>">
3
+ <%= message %>
4
+ </div>
5
+ <% end %>
@@ -0,0 +1,38 @@
1
+ <div class="rp-sidebar-content">
2
+ <h1 class="rp-logo">RailsPress</h1>
3
+
4
+ <nav class="rp-nav">
5
+ <%= link_to railspress.admin_root_path, class: "rp-nav-link #{current_page?(railspress.admin_root_path) ? 'rp-nav-link--active' : ''}" do %>
6
+ <svg class="rp-nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
7
+ <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
8
+ <polyline points="9 22 9 12 15 12 15 22"/>
9
+ </svg>
10
+ Dashboard
11
+ <% end %>
12
+
13
+ <%= link_to railspress.admin_posts_path, class: "rp-nav-link #{controller_name == 'posts' ? 'rp-nav-link--active' : ''}" do %>
14
+ <svg class="rp-nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
15
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
16
+ <polyline points="14 2 14 8 20 8"/>
17
+ <line x1="16" y1="13" x2="8" y2="13"/>
18
+ <line x1="16" y1="17" x2="8" y2="17"/>
19
+ </svg>
20
+ Posts
21
+ <% end %>
22
+
23
+ <%= link_to railspress.admin_categories_path, class: "rp-nav-link #{controller_name == 'categories' ? 'rp-nav-link--active' : ''}" do %>
24
+ <svg class="rp-nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
25
+ <path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/>
26
+ </svg>
27
+ Categories
28
+ <% end %>
29
+
30
+ <%= link_to railspress.admin_tags_path, class: "rp-nav-link #{controller_name == 'tags' ? 'rp-nav-link--active' : ''}" do %>
31
+ <svg class="rp-nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
32
+ <path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"/>
33
+ <line x1="7" y1="7" x2="7.01" y2="7"/>
34
+ </svg>
35
+ Tags
36
+ <% end %>
37
+ </nav>
38
+ </div>
@@ -0,0 +1,27 @@
1
+ <%= form_with model: [:admin, @tag], class: "rp-form rp-form--narrow" do |form| %>
2
+ <% if @tag.errors.any? %>
3
+ <div class="rp-form-errors">
4
+ <ul>
5
+ <% @tag.errors.full_messages.each do |msg| %>
6
+ <li><%= msg %></li>
7
+ <% end %>
8
+ </ul>
9
+ </div>
10
+ <% end %>
11
+
12
+ <div class="rp-form-group">
13
+ <%= form.label :name, class: "rp-label rp-label--lg rp-label--required" %>
14
+ <%= form.text_field :name, class: "rp-input rp-input--lg", placeholder: "Tag name", data: { slug_source: true }, required: true %>
15
+ </div>
16
+
17
+ <div class="rp-form-group">
18
+ <%= form.label :slug, class: "rp-label" %>
19
+ <%= form.text_field :slug, class: "rp-input rp-input--mono", placeholder: "auto-generated-if-blank", data: { slug_target: true } %>
20
+ <p class="rp-hint">Leave blank to auto-generate from name</p>
21
+ </div>
22
+
23
+ <div class="rp-form-actions">
24
+ <%= form.submit class: "rp-btn rp-btn--primary" %>
25
+ <%= link_to "Cancel", admin_tags_path, class: "rp-btn rp-btn--secondary" %>
26
+ </div>
27
+ <% end %>
@@ -0,0 +1,6 @@
1
+ <% content_for :title, "Edit Tag" %>
2
+
3
+ <h1 class="rp-page-title rp-page-title--standalone">Edit Tag</h1>
4
+ <div class="rp-card rp-card--padded">
5
+ <%= render "form" %>
6
+ </div>
@@ -0,0 +1,42 @@
1
+ <% content_for :title, "Tags" %>
2
+
3
+ <div class="rp-page-header">
4
+ <h1 class="rp-page-title">Tags</h1>
5
+ <div class="rp-page-actions">
6
+ <%= link_to "New Tag", new_admin_tag_path, class: "rp-btn rp-btn--primary" %>
7
+ </div>
8
+ </div>
9
+
10
+ <div class="rp-card">
11
+ <% if @tags.any? %>
12
+ <div class="rp-table--responsive">
13
+ <table class="rp-table">
14
+ <thead>
15
+ <tr>
16
+ <th>Name</th>
17
+ <th>Slug</th>
18
+ <th>Posts</th>
19
+ <th class="rp-table-actions">Actions</th>
20
+ </tr>
21
+ </thead>
22
+ <tbody>
23
+ <% @tags.each do |tag| %>
24
+ <tr>
25
+ <td class="rp-table-primary"><%= tag.name %></td>
26
+ <td class="rp-table-secondary"><%= tag.slug %></td>
27
+ <td><%= tag.posts.count %></td>
28
+ <td class="rp-table-actions">
29
+ <%= link_to "Edit", edit_admin_tag_path(tag), class: "rp-link" %>
30
+ <%= button_to "Delete", admin_tag_path(tag), method: :delete,
31
+ data: { turbo_confirm: "Delete this tag?" },
32
+ class: "rp-link rp-link--danger" %>
33
+ </td>
34
+ </tr>
35
+ <% end %>
36
+ </tbody>
37
+ </table>
38
+ </div>
39
+ <% else %>
40
+ <p class="rp-empty-state">No tags yet. <%= link_to "Create your first tag", new_admin_tag_path, class: "rp-link" %>.</p>
41
+ <% end %>
42
+ </div>
@@ -0,0 +1,6 @@
1
+ <% content_for :title, "New Tag" %>
2
+
3
+ <h1 class="rp-page-title rp-page-title--standalone">New Tag</h1>
4
+ <div class="rp-card rp-card--padded">
5
+ <%= render "form" %>
6
+ </div>
data/config/routes.rb ADDED
@@ -0,0 +1,9 @@
1
+ Railspress::Engine.routes.draw do
2
+ root to: redirect("admin")
3
+ namespace :admin do
4
+ root "dashboard#index"
5
+ resources :categories, except: [ :show ]
6
+ resources :tags, except: [ :show ]
7
+ resources :posts
8
+ end
9
+ end
@@ -0,0 +1,14 @@
1
+ class CreateRailspressCategories < ActiveRecord::Migration[8.0]
2
+ def change
3
+ create_table :railspress_categories do |t|
4
+ t.string :name, null: false
5
+ t.string :slug, null: false
6
+ t.text :description
7
+
8
+ t.timestamps
9
+ end
10
+
11
+ add_index :railspress_categories, :name, unique: true
12
+ add_index :railspress_categories, :slug, unique: true
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ class CreateRailspressTags < ActiveRecord::Migration[8.0]
2
+ def change
3
+ create_table :railspress_tags do |t|
4
+ t.string :name, null: false
5
+ t.string :slug, null: false
6
+
7
+ t.timestamps
8
+ end
9
+
10
+ add_index :railspress_tags, :name, unique: true
11
+ add_index :railspress_tags, :slug, unique: true
12
+ end
13
+ end
@@ -0,0 +1,19 @@
1
+ class CreateRailspressPosts < ActiveRecord::Migration[8.0]
2
+ def change
3
+ create_table :railspress_posts do |t|
4
+ t.string :title, null: false
5
+ t.string :slug, null: false
6
+ t.references :category, null: true, foreign_key: { to_table: :railspress_categories }
7
+ t.integer :status, default: 0, null: false
8
+ t.datetime :published_at
9
+ t.string :meta_title
10
+ t.text :meta_description
11
+
12
+ t.timestamps
13
+ end
14
+
15
+ add_index :railspress_posts, :slug, unique: true
16
+ add_index :railspress_posts, :status
17
+ add_index :railspress_posts, :published_at
18
+ end
19
+ end
@@ -0,0 +1,12 @@
1
+ class CreateRailspressPostTags < ActiveRecord::Migration[8.0]
2
+ def change
3
+ create_table :railspress_post_tags do |t|
4
+ t.references :post, null: false, foreign_key: { to_table: :railspress_posts }
5
+ t.references :tag, null: false, foreign_key: { to_table: :railspress_tags }
6
+
7
+ t.timestamps
8
+ end
9
+
10
+ add_index :railspress_post_tags, [:post_id, :tag_id], unique: true
11
+ end
12
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require "rails/generators/base"
5
+
6
+ module Railspress
7
+ module Generators
8
+ class AuthorsGenerator < Rails::Generators::Base
9
+ source_root File.expand_path("templates", __dir__)
10
+
11
+ desc "Enable author support for RailsPress posts"
12
+
13
+ def create_migration
14
+ migration_template(
15
+ "add_author_to_railspress_posts.rb.tt",
16
+ "db/migrate/add_author_to_railspress_posts.rb",
17
+ migration_version: migration_version
18
+ )
19
+ end
20
+
21
+ def create_initializer
22
+ if initializer_exists?
23
+ say_status :skip, "config/initializers/railspress.rb already exists", :yellow
24
+ say ""
25
+ say "Add the following to your existing initializer:", :yellow
26
+ say ""
27
+ say " config.enable_authors", :cyan
28
+ say " config.author_class_name = \"User\"", :cyan
29
+ say " # config.author_scope = :admins", :cyan
30
+ say " # config.author_display_method = :name", :cyan
31
+ say ""
32
+ else
33
+ template "railspress.rb.tt", "config/initializers/railspress.rb"
34
+ end
35
+ end
36
+
37
+ def show_post_install_message
38
+ say ""
39
+ say "=" * 60, :green
40
+ say " RailsPress Authors enabled!", :green
41
+ say "=" * 60, :green
42
+ say ""
43
+ say "Next steps:", :yellow
44
+ say ""
45
+ say " 1. Run migrations:"
46
+ say " $ rails db:migrate", :cyan
47
+ say ""
48
+ say " 2. Configure authors in config/initializers/railspress.rb", :cyan
49
+ say ""
50
+ say " 3. (Optional) Add an author scope to your User model:"
51
+ say " scope :authors, -> { where(role: :admin) }", :cyan
52
+ say ""
53
+ say "=" * 60, :green
54
+ end
55
+
56
+ private
57
+
58
+ def initializer_exists?
59
+ File.exist?(Rails.root.join("config", "initializers", "railspress.rb"))
60
+ end
61
+
62
+ def migration_version
63
+ "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
64
+ end
65
+
66
+ # Required for migration_template to work
67
+ def self.next_migration_number(dirname)
68
+ next_migration_number = current_migration_number(dirname) + 1
69
+ ActiveRecord::Migration.next_migration_number(next_migration_number)
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddAuthorToRailspressPosts < ActiveRecord::Migration<%= migration_version %>
4
+ def change
5
+ add_column :railspress_posts, :author_id, :bigint
6
+ add_index :railspress_posts, :author_id
7
+ end
8
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ Railspress.configure do |config|
4
+ # ==========================================================================
5
+ # Authors
6
+ # ==========================================================================
7
+
8
+ # Enable author tracking on posts
9
+ # When enabled, posts can have an author and the admin form shows an author select
10
+ config.enable_authors
11
+
12
+ # The class name of your author model (default: "User")
13
+ config.author_class_name = "User"
14
+
15
+ # Controller method that returns the current user (default: :current_user)
16
+ # Used to pre-select the author on new posts
17
+ # config.current_author_method = :current_user
18
+
19
+ # Scope for which users appear in the author dropdown (default: nil = all)
20
+ # Pass a symbol to call a scope on the author class, or a lambda for custom logic
21
+ #
22
+ # Examples:
23
+ # config.author_scope = :admins # calls User.admins
24
+ # config.author_scope = :authors # calls User.authors
25
+ # config.author_scope = ->(klass) { klass.active } # custom filtering
26
+ #
27
+ # config.author_scope = nil
28
+
29
+ # Method called on author to display in the dropdown (default: :name)
30
+ # config.author_display_method = :name
31
+ end