open_blog 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/CHANGELOG.md +52 -0
- data/LICENSE.txt +21 -0
- data/README.md +101 -0
- data/app/assets/builds/open_blog/blog.css +2 -0
- data/app/assets/javascripts/open_blog/controllers/code_copy_controller.js +40 -0
- data/app/assets/javascripts/open_blog/controllers/reading_progress_controller.js +36 -0
- data/app/assets/javascripts/open_blog/controllers/search_controller.js +49 -0
- data/app/assets/javascripts/open_blog/controllers/share_controller.js +48 -0
- data/app/assets/javascripts/open_blog/controllers/theme_controller.js +51 -0
- data/app/assets/javascripts/open_blog/controllers/toc_controller.js +33 -0
- data/app/controllers/concerns/open_blog/reader_controller.rb +49 -0
- data/app/controllers/open_blog/api/v1/adoptions_controller.rb +27 -0
- data/app/controllers/open_blog/api/v1/approvals_controller.rb +15 -0
- data/app/controllers/open_blog/api/v1/authors_controller.rb +62 -0
- data/app/controllers/open_blog/api/v1/base_controller.rb +100 -0
- data/app/controllers/open_blog/api/v1/categories_controller.rb +33 -0
- data/app/controllers/open_blog/api/v1/connections_controller.rb +45 -0
- data/app/controllers/open_blog/api/v1/doctor_controller.rb +13 -0
- data/app/controllers/open_blog/api/v1/faq_extractions_controller.rb +13 -0
- data/app/controllers/open_blog/api/v1/findings_controller.rb +13 -0
- data/app/controllers/open_blog/api/v1/images_controller.rb +22 -0
- data/app/controllers/open_blog/api/v1/pages_controller.rb +67 -0
- data/app/controllers/open_blog/api/v1/posts_controller.rb +114 -0
- data/app/controllers/open_blog/api/v1/previews_controller.rb +13 -0
- data/app/controllers/open_blog/api/v1/records_controller.rb +13 -0
- data/app/controllers/open_blog/api/v1/redirects_controller.rb +51 -0
- data/app/controllers/open_blog/api/v1/report_controller.rb +20 -0
- data/app/controllers/open_blog/api/v1/resources_controller.rb +39 -0
- data/app/controllers/open_blog/api/v1/series_controller.rb +32 -0
- data/app/controllers/open_blog/api/v1/tags_controller.rb +12 -0
- data/app/controllers/open_blog/api/v1/views_controller.rb +19 -0
- data/app/controllers/open_blog/application_controller.rb +5 -0
- data/app/controllers/open_blog/authors_controller.rb +14 -0
- data/app/controllers/open_blog/categories_controller.rb +15 -0
- data/app/controllers/open_blog/errors_controller.rb +7 -0
- data/app/controllers/open_blog/feeds_controller.rb +22 -0
- data/app/controllers/open_blog/mcp_controller.rb +38 -0
- data/app/controllers/open_blog/media_controller.rb +20 -0
- data/app/controllers/open_blog/pages_controller.rb +12 -0
- data/app/controllers/open_blog/posts_controller.rb +57 -0
- data/app/controllers/open_blog/previews_controller.rb +35 -0
- data/app/controllers/open_blog/search_controller.rb +32 -0
- data/app/controllers/open_blog/series_controller.rb +15 -0
- data/app/controllers/open_blog/sitemaps_controller.rb +10 -0
- data/app/controllers/open_blog/tags_controller.rb +15 -0
- data/app/helpers/open_blog/content_helper.rb +79 -0
- data/app/helpers/open_blog/dates_helper.rb +18 -0
- data/app/helpers/open_blog/faq_helper.rb +59 -0
- data/app/helpers/open_blog/head_helper.rb +75 -0
- data/app/helpers/open_blog/icons_helper.rb +10 -0
- data/app/helpers/open_blog/notices_helper.rb +47 -0
- data/app/helpers/open_blog/pagination_helper.rb +18 -0
- data/app/helpers/open_blog/structured_data_helper.rb +63 -0
- data/app/helpers/open_blog/theme_helper.rb +52 -0
- data/app/helpers/open_blog/url_helper.rb +58 -0
- data/app/jobs/open_blog/publish_scheduled_post_job.rb +14 -0
- data/app/models/concerns/open_blog/immutable.rb +13 -0
- data/app/models/concerns/open_blog/immutable_image_attachment.rb +43 -0
- data/app/models/concerns/open_blog/previewable.rb +38 -0
- data/app/models/open_blog/api_token.rb +34 -0
- data/app/models/open_blog/application_record.rb +5 -0
- data/app/models/open_blog/approval.rb +19 -0
- data/app/models/open_blog/author.rb +11 -0
- data/app/models/open_blog/baseline.rb +26 -0
- data/app/models/open_blog/category.rb +10 -0
- data/app/models/open_blog/connection_declaration.rb +16 -0
- data/app/models/open_blog/faq.rb +22 -0
- data/app/models/open_blog/image.rb +45 -0
- data/app/models/open_blog/page.rb +23 -0
- data/app/models/open_blog/page_view.rb +7 -0
- data/app/models/open_blog/post.rb +132 -0
- data/app/models/open_blog/publication.rb +17 -0
- data/app/models/open_blog/redirect.rb +16 -0
- data/app/models/open_blog/revision.rb +12 -0
- data/app/models/open_blog/series.rb +10 -0
- data/app/models/open_blog/tag.rb +11 -0
- data/app/models/open_blog/tagging.rb +7 -0
- data/app/queries/open_blog/api_post_query.rb +54 -0
- data/app/serializers/open_blog/adoption_result_serializer.rb +9 -0
- data/app/serializers/open_blog/author_serializer.rb +10 -0
- data/app/serializers/open_blog/category_serializer.rb +7 -0
- data/app/serializers/open_blog/doctor_serializer.rb +7 -0
- data/app/serializers/open_blog/error_serializer.rb +7 -0
- data/app/serializers/open_blog/extraction_result_serializer.rb +7 -0
- data/app/serializers/open_blog/findings_serializer.rb +13 -0
- data/app/serializers/open_blog/image_serializer.rb +9 -0
- data/app/serializers/open_blog/page_serializer.rb +9 -0
- data/app/serializers/open_blog/post_serializer.rb +59 -0
- data/app/serializers/open_blog/preview_serializer.rb +11 -0
- data/app/serializers/open_blog/records_serializer.rb +28 -0
- data/app/serializers/open_blog/redirect_serializer.rb +7 -0
- data/app/serializers/open_blog/series_serializer.rb +7 -0
- data/app/serializers/open_blog/tag_serializer.rb +7 -0
- data/app/serializers/open_blog/write_result_serializer.rb +9 -0
- data/app/views/open_blog/attachments/_blob.html.erb +1 -0
- data/app/views/open_blog/feeds/atom.xml.builder +27 -0
- data/app/views/open_blog/sitemaps/show.xml.builder +9 -0
- data/config/locales/open_blog.en.yml +95 -0
- data/config/routes.rb +52 -0
- data/db/migrate/20261003012512_create_open_blog_authors.rb +16 -0
- data/db/migrate/20261003012513_create_open_blog_categories.rb +13 -0
- data/db/migrate/20261003012514_create_open_blog_series.rb +11 -0
- data/db/migrate/20261003012515_create_open_blog_images.rb +17 -0
- data/db/migrate/20261003012516_create_open_blog_posts.rb +47 -0
- data/db/migrate/20261003012517_create_open_blog_faqs.rb +13 -0
- data/db/migrate/20261003012518_create_open_blog_tags.rb +17 -0
- data/db/migrate/20261003012519_create_open_blog_redirects.rb +14 -0
- data/db/migrate/20261003012520_create_open_blog_revisions.rb +14 -0
- data/db/migrate/20261003012521_create_open_blog_approvals.rb +21 -0
- data/db/migrate/20261003012522_create_open_blog_publications.rb +18 -0
- data/db/migrate/20261003012523_create_open_blog_baselines.rb +24 -0
- data/db/migrate/20261003012524_create_open_blog_connection_declarations.rb +13 -0
- data/db/migrate/20261003031410_add_body_image_manifest_to_open_blog_posts.rb +7 -0
- data/db/migrate/20261003164418_create_open_blog_api_tokens.rb +15 -0
- data/db/migrate/20261003175454_create_open_blog_pages.rb +16 -0
- data/db/migrate/20261003182919_create_open_blog_page_views.rb +11 -0
- data/lib/generators/open_blog/admin_suite/admin_suite_generator.rb +27 -0
- data/lib/generators/open_blog/admin_suite/templates/author_resource.rb.tt +29 -0
- data/lib/generators/open_blog/admin_suite/templates/blog_portal.rb.tt +16 -0
- data/lib/generators/open_blog/admin_suite/templates/category_resource.rb.tt +28 -0
- data/lib/generators/open_blog/admin_suite/templates/initializer.rb.tt +19 -0
- data/lib/generators/open_blog/admin_suite/templates/page_resource.rb.tt +32 -0
- data/lib/generators/open_blog/admin_suite/templates/post_resource.rb.tt +58 -0
- data/lib/generators/open_blog/generator_support.rb +64 -0
- data/lib/generators/open_blog/install/install_generator.rb +188 -0
- data/lib/generators/open_blog/install/templates/initializer.rb.tt +16 -0
- data/lib/generators/open_blog/install/templates/sample_cover.png +0 -0
- data/lib/generators/open_blog/install/templates/sample_post.md +54 -0
- data/lib/generators/open_blog/install/templates/theme/blog.css +193 -0
- data/lib/generators/open_blog/install/templates/theme/open_blog_theme.css +82 -0
- data/lib/generators/open_blog/install/templates/theme/syntax.css +358 -0
- data/lib/generators/open_blog/install/templates/theme/theme.css +120 -0
- data/lib/generators/open_blog/install/templates/views/layouts/open_blog.html.erb +16 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/authors/show.html.erb +17 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/categories/show.html.erb +13 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/errors/not_found.html.erb +11 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/errors/preview_expired.html.erb +5 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/icons/_check.svg.erb +1 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/icons/_copy.svg.erb +1 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/icons/_link.svg.erb +1 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/icons/_monitor.svg.erb +1 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/icons/_moon.svg.erb +1 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/icons/_share.svg.erb +1 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/icons/_sun.svg.erb +1 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/pages/show.html.erb +5 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/posts/_faq.html.erb +1 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/posts/_highlights.html.erb +7 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/posts/_preview_bar.html.erb +1 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/posts/_series_nav.html.erb +12 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/posts/_tags.html.erb +5 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/posts/_toc.html.erb +1 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/posts/index.html.erb +13 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/posts/show.html.erb +29 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/search/show.html.erb +13 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/series/show.html.erb +13 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/shared/_author_box.html.erb +6 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/shared/_breadcrumbs.html.erb +1 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/shared/_call_to_action.html.erb +7 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/shared/_featured_card.html.erb +11 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/shared/_footer.html.erb +11 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/shared/_header.html.erb +7 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/shared/_pagination.html.erb +1 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/shared/_post_card.html.erb +9 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/shared/_related_posts.html.erb +10 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/shared/_search.html.erb +9 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/shared/_share.html.erb +13 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/shared/_sidebar.html.erb +38 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/shared/_theme_toggle.html.erb +10 -0
- data/lib/generators/open_blog/install/templates/views/open_blog/tags/show.html.erb +13 -0
- data/lib/generators/open_blog/views/views_generator.rb +21 -0
- data/lib/open_blog/actor.rb +7 -0
- data/lib/open_blog/admin_suite.rb +73 -0
- data/lib/open_blog/adopt/contract.rb +152 -0
- data/lib/open_blog/adopt/snapshot.rb +35 -0
- data/lib/open_blog/adopt.rb +159 -0
- data/lib/open_blog/api_fields.rb +40 -0
- data/lib/open_blog/approve.rb +18 -0
- data/lib/open_blog/authentication.rb +18 -0
- data/lib/open_blog/build_css.rb +53 -0
- data/lib/open_blog/configuration.rb +185 -0
- data/lib/open_blog/configuration_error.rb +4 -0
- data/lib/open_blog/content_guard.rb +77 -0
- data/lib/open_blog/doctor/files.rb +61 -0
- data/lib/open_blog/doctor/javascript.rb +57 -0
- data/lib/open_blog/doctor.rb +177 -0
- data/lib/open_blog/engine.rb +45 -0
- data/lib/open_blog/errors.rb +40 -0
- data/lib/open_blog/faq_extraction.rb +184 -0
- data/lib/open_blog/findings/body_checks.rb +58 -0
- data/lib/open_blog/findings/check.rb +19 -0
- data/lib/open_blog/findings/link_check.rb +78 -0
- data/lib/open_blog/findings/record_checks.rb +94 -0
- data/lib/open_blog/findings/registry.rb +18 -0
- data/lib/open_blog/findings.rb +27 -0
- data/lib/open_blog/image_fetch.rb +115 -0
- data/lib/open_blog/image_import.rb +164 -0
- data/lib/open_blog/image_resolution.rb +171 -0
- data/lib/open_blog/instructions.rb +7 -0
- data/lib/open_blog/json_feed.rb +36 -0
- data/lib/open_blog/label_policy.rb +41 -0
- data/lib/open_blog/markdown_view.rb +57 -0
- data/lib/open_blog/mcp/definition.rb +28 -0
- data/lib/open_blog/mcp/dispatcher.rb +33 -0
- data/lib/open_blog/mcp/image_input.rb +30 -0
- data/lib/open_blog/mcp/registry.rb +139 -0
- data/lib/open_blog/mcp/schemas.rb +72 -0
- data/lib/open_blog/mcp.rb +24 -0
- data/lib/open_blog/not_found.rb +4 -0
- data/lib/open_blog/operation.rb +36 -0
- data/lib/open_blog/page_views.rb +72 -0
- data/lib/open_blog/pagination.rb +18 -0
- data/lib/open_blog/plain_text.rb +36 -0
- data/lib/open_blog/policy_url.rb +9 -0
- data/lib/open_blog/post_attributes.rb +172 -0
- data/lib/open_blog/post_identity.rb +28 -0
- data/lib/open_blog/publish.rb +7 -0
- data/lib/open_blog/reader_dates.rb +21 -0
- data/lib/open_blog/reader_page.rb +21 -0
- data/lib/open_blog/reader_queries.rb +90 -0
- data/lib/open_blog/record_release.rb +66 -0
- data/lib/open_blog/redirect_target.rb +58 -0
- data/lib/open_blog/remove.rb +43 -0
- data/lib/open_blog/renderer/highlighter.rb +44 -0
- data/lib/open_blog/renderer/markdown.rb +14 -0
- data/lib/open_blog/renderer/rich_text.rb +19 -0
- data/lib/open_blog/renderer/sanitizer.rb +72 -0
- data/lib/open_blog/renderer/scrubber.rb +35 -0
- data/lib/open_blog/renderer/transform.rb +115 -0
- data/lib/open_blog/renderer.rb +51 -0
- data/lib/open_blog/result.rb +11 -0
- data/lib/open_blog/revision_payload.rb +67 -0
- data/lib/open_blog/rich_text_guard.rb +26 -0
- data/lib/open_blog/sample.rb +56 -0
- data/lib/open_blog/save_draft.rb +7 -0
- data/lib/open_blog/search.rb +21 -0
- data/lib/open_blog/sitemap_entries.rb +66 -0
- data/lib/open_blog/surface_report/expected_content.rb +27 -0
- data/lib/open_blog/surface_report/http.rb +92 -0
- data/lib/open_blog/surface_report/page_checks.rb +303 -0
- data/lib/open_blog/surface_report/reach_checks.rb +141 -0
- data/lib/open_blog/surface_report/record_checks.rb +248 -0
- data/lib/open_blog/surface_report/robots.rb +49 -0
- data/lib/open_blog/surface_report.rb +223 -0
- data/lib/open_blog/syntax_css.rb +55 -0
- data/lib/open_blog/unpublish.rb +13 -0
- data/lib/open_blog/version.rb +3 -0
- data/lib/open_blog/write_post.rb +205 -0
- data/lib/open_blog.rb +91 -0
- data/lib/tasks/open_blog.rake +71 -0
- data/skills/open-blog-adopt/SKILL.md +12 -0
- data/skills/open-blog-install/SKILL.md +10 -0
- data/skills/open-blog-policy-pages/SKILL.md +10 -0
- data/skills/open-blog-publish/SKILL.md +14 -0
- data/skills/open-blog-report/SKILL.md +10 -0
- data/skills/open-blog-update/SKILL.md +16 -0
- metadata +515 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
class CreateOpenBlogFaqs < ActiveRecord::Migration[8.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :open_blog_faqs do |t|
|
|
4
|
+
t.references :post, null: false, foreign_key: { to_table: :open_blog_posts }
|
|
5
|
+
t.integer :position, null: false
|
|
6
|
+
t.text :question, null: false
|
|
7
|
+
t.text :answer, null: false
|
|
8
|
+
t.timestamps
|
|
9
|
+
end
|
|
10
|
+
add_index :open_blog_faqs, [ :post_id, :position ], unique: true
|
|
11
|
+
add_check_constraint :open_blog_faqs, "position > 0", name: "open_blog_faqs_position"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
class CreateOpenBlogTags < ActiveRecord::Migration[8.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :open_blog_tags do |t|
|
|
4
|
+
t.string :name, null: false, **(connection.adapter_name == "SQLite" ? { collation: "NOCASE" } : {})
|
|
5
|
+
t.string :slug, null: false
|
|
6
|
+
t.datetime :created_at, null: false
|
|
7
|
+
end
|
|
8
|
+
add_index :open_blog_tags, connection.adapter_name == "PostgreSQL" ? "lower(name)" : :name, unique: true, name: "index_open_blog_tags_on_name"
|
|
9
|
+
add_index :open_blog_tags, :slug, unique: true
|
|
10
|
+
create_table :open_blog_taggings do |t|
|
|
11
|
+
t.references :post, null: false, foreign_key: { to_table: :open_blog_posts }
|
|
12
|
+
t.references :tag, null: false, foreign_key: { to_table: :open_blog_tags }
|
|
13
|
+
t.datetime :created_at, null: false
|
|
14
|
+
end
|
|
15
|
+
add_index :open_blog_taggings, [ :post_id, :tag_id ], unique: true
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
class CreateOpenBlogRedirects < ActiveRecord::Migration[8.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :open_blog_redirects do |t|
|
|
4
|
+
t.string :old_path, null: false
|
|
5
|
+
t.string :new_path
|
|
6
|
+
t.string :source, null: false
|
|
7
|
+
t.date :occurred_on, null: false
|
|
8
|
+
t.references :post, foreign_key: { to_table: :open_blog_posts }
|
|
9
|
+
t.timestamps
|
|
10
|
+
end
|
|
11
|
+
add_index :open_blog_redirects, :old_path, unique: true
|
|
12
|
+
add_check_constraint :open_blog_redirects, "source IN ('slug_change', 'removal', 'unpublish', 'adoption', 'manual')", name: "open_blog_redirects_source"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
class CreateOpenBlogRevisions < ActiveRecord::Migration[8.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :open_blog_revisions do |t|
|
|
4
|
+
t.references :post, null: false, foreign_key: { to_table: :open_blog_posts }
|
|
5
|
+
t.string :identifier, limit: 64, null: false
|
|
6
|
+
t.text :payload, null: false
|
|
7
|
+
t.string :actor
|
|
8
|
+
t.boolean :made_by_ai
|
|
9
|
+
t.datetime :created_at, null: false
|
|
10
|
+
end
|
|
11
|
+
add_index :open_blog_revisions, [ :post_id, :identifier ], unique: true
|
|
12
|
+
add_foreign_key :open_blog_posts, :open_blog_revisions, column: :public_revision_id
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
class CreateOpenBlogApprovals < ActiveRecord::Migration[8.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :open_blog_approvals do |t|
|
|
4
|
+
t.references :post, null: false, foreign_key: { to_table: :open_blog_posts }
|
|
5
|
+
t.references :revision, null: false, foreign_key: { to_table: :open_blog_revisions }
|
|
6
|
+
t.string :kind, null: false
|
|
7
|
+
t.string :reviewer_name, null: false
|
|
8
|
+
t.boolean :facts_checked, null: false
|
|
9
|
+
t.datetime :approved_at, null: false
|
|
10
|
+
t.date :declared_on
|
|
11
|
+
t.string :declared_by
|
|
12
|
+
t.string :confirmed_by
|
|
13
|
+
t.text :evidence
|
|
14
|
+
t.string :recorded_by
|
|
15
|
+
t.datetime :created_at, null: false
|
|
16
|
+
end
|
|
17
|
+
add_check_constraint :open_blog_approvals, "kind IN ('sent', 'imported', 'declared')", name: "open_blog_approvals_kind"
|
|
18
|
+
add_check_constraint :open_blog_approvals, "kind != 'declared' OR (declared_on IS NOT NULL AND declared_by IS NOT NULL)", name: "open_blog_approvals_declaration"
|
|
19
|
+
add_check_constraint :open_blog_approvals, "kind != 'imported' OR (confirmed_by IS NOT NULL AND evidence IS NOT NULL)", name: "open_blog_approvals_import"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
class CreateOpenBlogPublications < ActiveRecord::Migration[8.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :open_blog_publications do |t|
|
|
4
|
+
t.references :post, null: false, foreign_key: { to_table: :open_blog_posts }
|
|
5
|
+
t.references :revision, null: false, foreign_key: { to_table: :open_blog_revisions }
|
|
6
|
+
t.string :entry_type, null: false
|
|
7
|
+
t.datetime :occurred_at, null: false
|
|
8
|
+
t.string :released_by
|
|
9
|
+
t.string :description
|
|
10
|
+
t.text :note
|
|
11
|
+
t.datetime :created_at, null: false
|
|
12
|
+
end
|
|
13
|
+
add_index :open_blog_publications, [ :post_id, :occurred_at ]
|
|
14
|
+
add_index :open_blog_publications, :post_id, unique: true, where: "entry_type = 'first'", name: "index_open_blog_publications_first"
|
|
15
|
+
add_check_constraint :open_blog_publications, "entry_type IN ('first', 'substantive', 'correction', 'maintenance', 'adopted')", name: "open_blog_publications_entry_type"
|
|
16
|
+
add_check_constraint :open_blog_publications, "entry_type != 'correction' OR note IS NOT NULL", name: "open_blog_publications_correction"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
class CreateOpenBlogBaselines < ActiveRecord::Migration[8.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :open_blog_baselines do |t|
|
|
4
|
+
t.references :post, null: false, index: { unique: true }, foreign_key: { to_table: :open_blog_posts }
|
|
5
|
+
t.datetime :adopted_at, null: false
|
|
6
|
+
t.references :adopted_revision, null: false, foreign_key: { to_table: :open_blog_revisions }
|
|
7
|
+
t.string :provenance, null: false
|
|
8
|
+
t.text :provenance_evidence
|
|
9
|
+
t.datetime :first_published_at
|
|
10
|
+
t.text :first_published_evidence
|
|
11
|
+
t.datetime :declared_first_published_at
|
|
12
|
+
t.datetime :last_modified_at
|
|
13
|
+
t.text :last_modified_evidence
|
|
14
|
+
t.string :source_system
|
|
15
|
+
t.string :source_id
|
|
16
|
+
t.string :source_body_sha256, limit: 64
|
|
17
|
+
t.string :adopted_by
|
|
18
|
+
t.datetime :created_at, null: false
|
|
19
|
+
end
|
|
20
|
+
add_index :open_blog_baselines, [ :source_system, :source_id ], unique: true
|
|
21
|
+
add_check_constraint :open_blog_baselines, "first_published_at IS NULL OR declared_first_published_at IS NULL", name: "open_blog_baselines_first_date"
|
|
22
|
+
add_check_constraint :open_blog_baselines, "provenance IN ('ai_assisted', 'human_written', 'unknown')", name: "open_blog_baselines_provenance"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
class CreateOpenBlogConnectionDeclarations < ActiveRecord::Migration[8.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :open_blog_connection_declarations do |t|
|
|
4
|
+
t.references :post, null: false, foreign_key: { to_table: :open_blog_posts }
|
|
5
|
+
t.public_send(connection.adapter_name == "PostgreSQL" ? :jsonb : :json, :connections, null: false, default: [])
|
|
6
|
+
t.boolean :third_party_paid, null: false
|
|
7
|
+
t.string :declared_by, null: false
|
|
8
|
+
t.date :declared_on, null: false
|
|
9
|
+
t.string :recorded_by
|
|
10
|
+
t.datetime :created_at, null: false
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
class AddBodyImageManifestToOpenBlogPosts < ActiveRecord::Migration[8.0]
|
|
2
|
+
def change
|
|
3
|
+
format = connection.adapter_name == "PostgreSQL" ? :jsonb : :json
|
|
4
|
+
add_column :open_blog_posts, :body_image_manifest, format, null: false, default: []
|
|
5
|
+
add_column :open_blog_posts, :body_image_source_digest, :string, limit: 64
|
|
6
|
+
end
|
|
7
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class CreateOpenBlogApiTokens < ActiveRecord::Migration[8.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :open_blog_api_tokens do |t|
|
|
4
|
+
t.string :name, null: false
|
|
5
|
+
t.string :token_digest, limit: 64, null: false
|
|
6
|
+
t.string :token_prefix, limit: 8, null: false
|
|
7
|
+
t.public_send(connection.adapter_name == "PostgreSQL" ? :jsonb : :json, :scopes, null: false, default: %w[read write publish])
|
|
8
|
+
t.datetime :last_used_at
|
|
9
|
+
t.datetime :expires_at
|
|
10
|
+
t.datetime :revoked_at
|
|
11
|
+
t.timestamps
|
|
12
|
+
end
|
|
13
|
+
add_index :open_blog_api_tokens, :token_digest, unique: true
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
class CreateOpenBlogPages < ActiveRecord::Migration[8.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :open_blog_pages do |t|
|
|
4
|
+
t.string :kind, null: false
|
|
5
|
+
t.string :slug, null: false
|
|
6
|
+
t.string :title, null: false
|
|
7
|
+
t.text :body_markdown, null: false, default: ""
|
|
8
|
+
t.string :status, null: false, default: "draft"
|
|
9
|
+
t.string :approved_by
|
|
10
|
+
t.date :approved_on
|
|
11
|
+
t.datetime :updated_at, null: false
|
|
12
|
+
end
|
|
13
|
+
add_index :open_blog_pages, :kind, unique: true
|
|
14
|
+
add_index :open_blog_pages, :slug, unique: true
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
class CreateOpenBlogPageViews < ActiveRecord::Migration[8.0]
|
|
2
|
+
def change
|
|
3
|
+
create_table :open_blog_page_views do |t|
|
|
4
|
+
t.references :post, null: false, foreign_key: { to_table: :open_blog_posts }, index: false
|
|
5
|
+
t.date :day, null: false
|
|
6
|
+
t.integer :views, null: false, default: 0
|
|
7
|
+
end
|
|
8
|
+
add_index :open_blog_page_views, [ :post_id, :day ], unique: true
|
|
9
|
+
add_index :open_blog_page_views, :day
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require "rails/generators"
|
|
2
|
+
require "pathname"
|
|
3
|
+
require_relative "../generator_support"
|
|
4
|
+
|
|
5
|
+
module OpenBlog
|
|
6
|
+
module Generators
|
|
7
|
+
class AdminSuiteGenerator < Rails::Generators::Base
|
|
8
|
+
include GeneratorSupport
|
|
9
|
+
source_root File.expand_path("templates", __dir__)
|
|
10
|
+
class_option :dir, type: :string, default: "app/admin", desc: "Host directory for admin definitions"
|
|
11
|
+
|
|
12
|
+
def copy_definitions
|
|
13
|
+
path = Pathname.new(options[:dir])
|
|
14
|
+
if path.absolute? || path.each_filename.include?("..") || options[:dir].match?(/[\x00-\x1f*?\[\]{}]/)
|
|
15
|
+
raise Thor::Error, "Choose a relative host directory without traversal or glob characters."
|
|
16
|
+
end
|
|
17
|
+
@admin_dir = path.cleanpath.to_s
|
|
18
|
+
%w[post category author page].each do |name|
|
|
19
|
+
template "#{name}_resource.rb.tt", "#{@admin_dir}/resources/open_blog/#{name}_resource.rb", **copy_options
|
|
20
|
+
end
|
|
21
|
+
template "blog_portal.rb.tt", "#{@admin_dir}/portals/open_blog_portal.rb", **copy_options
|
|
22
|
+
template "initializer.rb.tt", "config/initializers/open_blog_admin_suite.rb", **copy_options
|
|
23
|
+
say "Admin definitions written. Install AdminSuite 0.6.1 or later to activate them; restart the host after adding definitions."
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Admin
|
|
2
|
+
module Resources
|
|
3
|
+
module OpenBlog
|
|
4
|
+
class AuthorResource < ::Admin::Base::Resource
|
|
5
|
+
model ::OpenBlog::Author
|
|
6
|
+
portal :open_blog
|
|
7
|
+
section :content
|
|
8
|
+
def self.resource_name = "open_blog_author"
|
|
9
|
+
|
|
10
|
+
index do
|
|
11
|
+
searchable :name, :slug
|
|
12
|
+
sortable :name, default: :name, direction: :asc
|
|
13
|
+
columns do
|
|
14
|
+
column :name
|
|
15
|
+
column :slug
|
|
16
|
+
column :author_type
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
form do
|
|
20
|
+
field :name, required: true
|
|
21
|
+
field :slug
|
|
22
|
+
field :author_type, type: :select, collection: %w[person organization]
|
|
23
|
+
field :bio, type: :textarea
|
|
24
|
+
field :url, type: :url
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module Admin
|
|
2
|
+
module Resources
|
|
3
|
+
module OpenBlog
|
|
4
|
+
class CategoryResource < ::Admin::Base::Resource
|
|
5
|
+
model ::OpenBlog::Category
|
|
6
|
+
portal :open_blog
|
|
7
|
+
section :content
|
|
8
|
+
def self.resource_name = "open_blog_category"
|
|
9
|
+
|
|
10
|
+
index do
|
|
11
|
+
searchable :name, :slug
|
|
12
|
+
sortable :position, default: :position, direction: :asc
|
|
13
|
+
columns do
|
|
14
|
+
column :name
|
|
15
|
+
column :slug
|
|
16
|
+
column :position
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
form do
|
|
20
|
+
field :name, required: true
|
|
21
|
+
field :slug
|
|
22
|
+
field :description, type: :textarea
|
|
23
|
+
field :position, type: :number
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Rails.autoloaders.main.ignore(
|
|
2
|
+
Rails.root.join(<%= "#{@admin_dir}/resources/open_blog".inspect %>),
|
|
3
|
+
Rails.root.join(<%= "#{@admin_dir}/portals/open_blog_portal.rb".inspect %>)
|
|
4
|
+
)
|
|
5
|
+
|
|
6
|
+
if defined?(::AdminSuite)
|
|
7
|
+
Rails.application.config.after_initialize do
|
|
8
|
+
require "open_blog/admin_suite"
|
|
9
|
+
::OpenBlog::AdminSuite.install!
|
|
10
|
+
::AdminSuite.configure do |config|
|
|
11
|
+
config.resource_globs |= [ Rails.root.join(<%= "#{@admin_dir}/resources/open_blog/*_resource.rb".inspect %>).to_s ]
|
|
12
|
+
config.portal_globs |= [ Rails.root.join(<%= "#{@admin_dir}/portals/open_blog_portal.rb".inspect %>).to_s ]
|
|
13
|
+
end
|
|
14
|
+
::AdminSuite::DefinitionLoader.load!(:resources)
|
|
15
|
+
::AdminSuite::DefinitionLoader.load!(:portals)
|
|
16
|
+
Dir[Rails.root.join(<%= "#{@admin_dir}/resources/open_blog/*_resource.rb".inspect %>)].sort.each { |path| require path }
|
|
17
|
+
require Rails.root.join(<%= "#{@admin_dir}/portals/open_blog_portal.rb".inspect %>).to_s
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module Admin
|
|
2
|
+
module Resources
|
|
3
|
+
module OpenBlog
|
|
4
|
+
class PageResource < ::Admin::Base::Resource
|
|
5
|
+
model ::OpenBlog::Page
|
|
6
|
+
portal :open_blog
|
|
7
|
+
section :content
|
|
8
|
+
def self.resource_name = "open_blog_page"
|
|
9
|
+
|
|
10
|
+
index do
|
|
11
|
+
searchable :title, :slug
|
|
12
|
+
sortable :updated_at, default: :updated_at, direction: :desc
|
|
13
|
+
columns do
|
|
14
|
+
column :title
|
|
15
|
+
column :kind
|
|
16
|
+
column :status
|
|
17
|
+
column :updated_at
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
form do
|
|
21
|
+
field :kind, type: :select, required: true, collection: ::OpenBlog::Page::KINDS
|
|
22
|
+
field :title, required: true
|
|
23
|
+
field :slug
|
|
24
|
+
field :body_markdown, type: :textarea, rows: 18
|
|
25
|
+
field :status, type: :select, collection: %w[draft published]
|
|
26
|
+
field :approved_by
|
|
27
|
+
field :approved_on, type: :date
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
require "open_blog/admin_suite"
|
|
2
|
+
::OpenBlog::AdminSuite.install!
|
|
3
|
+
|
|
4
|
+
module Admin
|
|
5
|
+
module Resources
|
|
6
|
+
module OpenBlog
|
|
7
|
+
class PostResource < ::Admin::Base::Resource
|
|
8
|
+
model ::OpenBlog::Post
|
|
9
|
+
portal :open_blog
|
|
10
|
+
section :content
|
|
11
|
+
def self.resource_name = "open_blog_post"
|
|
12
|
+
|
|
13
|
+
index do
|
|
14
|
+
searchable :title, :slug
|
|
15
|
+
sortable :updated_at, default: :updated_at, direction: :desc
|
|
16
|
+
columns do
|
|
17
|
+
column :title
|
|
18
|
+
column :status
|
|
19
|
+
column :author_name
|
|
20
|
+
column :updated_at
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
form do
|
|
25
|
+
field :title, required: true
|
|
26
|
+
field :slug, required: true, if: ->(post) { post.new_record? }
|
|
27
|
+
field :description, type: :textarea
|
|
28
|
+
field :author_id, type: :select, required: true, collection: -> { ::OpenBlog::Author.order(:name).pluck(:name, :id) }
|
|
29
|
+
field :author_name, required: true, label: "Byline"
|
|
30
|
+
field :category_id, type: :select, collection: -> { ::OpenBlog::Category.order(:name).pluck(:name, :id) }
|
|
31
|
+
field :body_markdown, type: :textarea, rows: 18, if: ->(post) { post.markdown? }
|
|
32
|
+
field :rich_body, type: :rich_text, if: ->(post) { post.rich_text? }
|
|
33
|
+
field :faqs_attributes, type: :open_blog_faq, help: "Each FAQ needs a unique positive position."
|
|
34
|
+
field :search_title
|
|
35
|
+
field :search_description, type: :textarea
|
|
36
|
+
field :featured, type: :toggle
|
|
37
|
+
field :status, type: :select, collection: %w[draft published archived], unless: ->(post) { post.scheduled? }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
show do
|
|
41
|
+
panel :article, fields: %i[title slug status author_name published_at modified_at]
|
|
42
|
+
panel :revisions, title: "Recent revisions", association: :revisions, render: :table_from,
|
|
43
|
+
source: ->(post) { post.revisions.order(id: :desc).limit(20).map { |row| row.attributes.symbolize_keys } },
|
|
44
|
+
columns: %i[id identifier actor created_at]
|
|
45
|
+
panel :approvals, title: "Recent approvals", association: :approvals, render: :table_from,
|
|
46
|
+
source: ->(post) { post.approvals.order(id: :desc).limit(20).map { |row| row.attributes.symbolize_keys } },
|
|
47
|
+
columns: %i[id revision_id kind reviewer_name facts_checked approved_at]
|
|
48
|
+
panel :publications, title: "Recent publications", association: :publications, render: :table_from,
|
|
49
|
+
source: ->(post) { post.publications.order(id: :desc).limit(20).map { |row| row.attributes.symbolize_keys } },
|
|
50
|
+
columns: %i[id revision_id entry_type occurred_at released_by note]
|
|
51
|
+
panel :baseline, association: :baseline, render: :table_from,
|
|
52
|
+
source: ->(post) { Array(post.baseline).map { |row| row.attributes.symbolize_keys } },
|
|
53
|
+
columns: %i[id source_system source_id provenance first_published_at declared_first_published_at adopted_at]
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
module OpenBlog
|
|
4
|
+
module Generators
|
|
5
|
+
module GeneratorSupport
|
|
6
|
+
private
|
|
7
|
+
|
|
8
|
+
def host_file(path)
|
|
9
|
+
File.join(destination_root, path)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def host_read(path)
|
|
13
|
+
File.file?(host_file(path)) ? File.read(host_file(path)) : ""
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def javascript_setup
|
|
17
|
+
return :importmap if File.file?(host_file("config/importmap.rb"))
|
|
18
|
+
return :bundler if File.file?(host_file("package.json"))
|
|
19
|
+
:none
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def css_setup
|
|
23
|
+
return :fallback if options[:skip_tailwind]
|
|
24
|
+
package = JSON.parse(host_read("package.json").presence || "{}")
|
|
25
|
+
version = package.fetch("dependencies", {}).merge(package.fetch("devDependencies", {}))["tailwindcss"].to_s
|
|
26
|
+
gemfile = host_read("Gemfile")
|
|
27
|
+
old_gem = gemfile.match?(/^\s*gem\s+["']tailwindcss-rails["']\s*,\s*["'][~>\s]*3(?:\.|["'])/)
|
|
28
|
+
old_gem ||= host_read("Gemfile.lock").match?(/^ tailwindcss-rails \(3\./)
|
|
29
|
+
old_gem ||= host_read("app/assets/stylesheets/application.tailwind.css").match?(/@tailwind\s+(?:base|components|utilities)\b/)
|
|
30
|
+
return :legacy if version.match?(/\A[~^>=\s]*3(?:\.|\z)/) || old_gem
|
|
31
|
+
return :rails if File.file?(host_file("app/assets/tailwind/application.css"))
|
|
32
|
+
if version.match?(/\A[~^>=\s]*4(?:\.|\z)/) && File.file?(host_file("app/assets/stylesheets/application.tailwind.css"))
|
|
33
|
+
return :bundler
|
|
34
|
+
end
|
|
35
|
+
:none
|
|
36
|
+
rescue JSON::ParserError => error
|
|
37
|
+
raise Thor::Error, "Cannot read package.json: #{error.message}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def copy_options
|
|
41
|
+
{ force: options[:force], skip: options[:skip] || (!options[:force] && !$stdin.tty?) }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def copy_reader_views(css: css_setup, javascript: javascript_setup)
|
|
45
|
+
directory "views/open_blog", "app/views/open_blog", **copy_options
|
|
46
|
+
content = File.read(find_in_source_paths("views/layouts/open_blog.html.erb"))
|
|
47
|
+
stylesheet = case css
|
|
48
|
+
when :rails then 'stylesheet_link_tag "tailwind", "data-turbo-track": "reload"'
|
|
49
|
+
when :bundler then 'stylesheet_link_tag "application", "data-turbo-track": "reload"'
|
|
50
|
+
else "open_blog_stylesheets"
|
|
51
|
+
end
|
|
52
|
+
content = content.sub("open_blog_stylesheets", stylesheet)
|
|
53
|
+
if javascript == :bundler
|
|
54
|
+
content = content.sub("javascript_importmap_tags", 'javascript_include_tag "application", "data-turbo-track": "reload", defer: true')
|
|
55
|
+
end
|
|
56
|
+
create_file "app/views/layouts/open_blog.html.erb", content, **copy_options
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def append_once(path, content)
|
|
60
|
+
append_to_file(path, content) unless host_read(path).include?(content.strip)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|