bragi-cms 1.27.0__py3-none-any.whl
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.
- bragi/__init__.py +15 -0
- bragi/alembic/__init__.py +0 -0
- bragi/alembic/env.py +66 -0
- bragi/alembic/script.py.mako +29 -0
- bragi/alembic/versions/.gitkeep +0 -0
- bragi/alembic/versions/2026_05_13_2119-cb48ebc1f1f5_initial_schema.py +99 -0
- bragi/alembic/versions/2026_05_13_2138-36a0ec65ddbf_add_redirects.py +56 -0
- bragi/alembic/versions/2026_05_13_2145-52631645e3c1_add_local_credentials.py +44 -0
- bragi/alembic/versions/2026_05_13_2208-38aa6fec0409_add_sessions.py +52 -0
- bragi/alembic/versions/2026_05_14_0526-9dbc52ee17a8_add_audit_log.py +61 -0
- bragi/alembic/versions/2026_05_14_0538-9f3fd65db818_add_attachments_and_post_image_fks.py +74 -0
- bragi/alembic/versions/2026_05_14_0623-1f966d693a2d_add_analytics_events.py +71 -0
- bragi/alembic/versions/2026_05_14_0641-f679d5fc62bb_add_extra_settings_to_sites.py +43 -0
- bragi/alembic/versions/2026_05_14_0719-0e57d255f32d_add_site_aliases.py +40 -0
- bragi/alembic/versions/2026_05_14_0723-6275dd6feda6_add_pages.py +55 -0
- bragi/alembic/versions/2026_05_14_0731-46fe76487384_add_tags.py +50 -0
- bragi/alembic/versions/2026_05_14_0745-2ecc724d6dfb_add_user_site_roles.py +48 -0
- bragi/alembic/versions/2026_05_14_0752-48e608e60109_add_user_identities.py +49 -0
- bragi/alembic/versions/2026_05_14_0904-c9d608f87623_add_revisions.py +69 -0
- bragi/alembic/versions/2026_05_14_1123-44fe91537fd5_add_attachment_image_fields.py +42 -0
- bragi/alembic/versions/2026_05_14_1154-25c6f95918c4_add_attachment_renditions.py +58 -0
- bragi/alembic/versions/2026_05_14_1238-17c7f26e8fde_add_page_source_id.py +36 -0
- bragi/alembic/versions/2026_05_14_2049-1eff692ffe2b_add_search_fts.py +71 -0
- bragi/alembic/versions/2026_05_14_2307-2a429b18c1d8_add_site_theme.py +39 -0
- bragi/alembic/versions/2026_05_15_1547-7fd0ed6fe2df_add_site_owner.py +109 -0
- bragi/alembic/versions/2026_05_17_1018-3341c91c55aa_add_site_home_page_id.py +61 -0
- bragi/alembic/versions/2026_05_17_1157-88dea48173c6_add_page_kind.py +138 -0
- bragi/alembic/versions/2026_05_17_1700-22e5570ca7f5_add_og_image_columns.py +68 -0
- bragi/alembic/versions/2026_05_17_1730-ad0c0c05ef40_add_user_bio.py +40 -0
- bragi/alembic/versions/2026_05_17_1900-3b4c8d1e0f00_add_personal_access_tokens.py +63 -0
- bragi/alembic/versions/2026_05_17_2000-4c5d9e2f1a01_add_webmentions.py +80 -0
- bragi/alembic/versions/2026_05_17_2100-5d6eaf3b1b02_add_activitypub.py +88 -0
- bragi/alembic/versions/2026_05_17_2304-e4f9edb18c87_extend_fk_ondelete_to_core_tables.py +1036 -0
- bragi/alembic/versions/2026_05_18_0900-6e7fbf4c2c03_add_fk_ondelete.py +374 -0
- bragi/alembic/versions/2026_05_18_1200-a1b2c3d4e5f6_redirect_source_path_nonempty.py +49 -0
- bragi/alembic/versions/2026_05_18_2200-2e99f2f0e525_add_internal_links_table_for_backlinks_.py +57 -0
- bragi/alembic/versions/2026_05_25_1909-8f97d76bd501_add_post_pinning.py +47 -0
- bragi/alembic/versions/2026_05_25_2255-5e26615ca3d3_add_post_revisions_pinning_columns.py +39 -0
- bragi/alembic/versions/2026_05_26_0919-e8e90e4b4afc_merge_og_image_into_featured_image.py +173 -0
- bragi/alembic/versions/2026_05_26_2022-f0a2ba28973b_attachment_renditions_v2_multi_format_.py +169 -0
- bragi/alembic/versions/2026_05_27_1905-171678f699a1_add_page_kind_resume_resume_data_column.py +30 -0
- bragi/alembic/versions/2026_05_29_1432-1a03542dfa5c_add_pages_show_in_nav_menu_order.py +46 -0
- bragi/alembic/versions/2026_06_02_1628-b6c01fcf9be2_attachments_external_source_credit_.py +57 -0
- bragi/alembic/versions/2026_06_03_2045-a523a7f5366b_attachment_renditions_claimed_at_for_.py +39 -0
- bragi/alembic/versions/__init__.py +0 -0
- bragi/api.py +790 -0
- bragi/apps/__init__.py +10 -0
- bragi/apps/admin.py +384 -0
- bragi/apps/delivery.py +181 -0
- bragi/cli.py +375 -0
- bragi/contrib/__init__.py +23 -0
- bragi/contrib/activitypub/__init__.py +27 -0
- bragi/contrib/activitypub/activities.py +160 -0
- bragi/contrib/activitypub/cli.py +63 -0
- bragi/contrib/activitypub/keys.py +89 -0
- bragi/contrib/activitypub/plugin.py +99 -0
- bragi/contrib/activitypub/sender.py +174 -0
- bragi/contrib/activitypub/signature.py +322 -0
- bragi/contrib/activitypub/views.py +440 -0
- bragi/contrib/admin_imports/__init__.py +0 -0
- bragi/contrib/admin_imports/admin.py +37 -0
- bragi/contrib/admin_imports/plugin.py +115 -0
- bragi/contrib/admin_imports/templates/admin/import_index.html +31 -0
- bragi/contrib/analytics/__init__.py +16 -0
- bragi/contrib/analytics/admin.py +85 -0
- bragi/contrib/analytics/plugin.py +129 -0
- bragi/contrib/analytics/templates/admin/analytics_dashboard.html +32 -0
- bragi/contrib/anchors/__init__.py +13 -0
- bragi/contrib/anchors/plugin.py +20 -0
- bragi/contrib/anchors/transform.py +74 -0
- bragi/contrib/api_tokens/__init__.py +18 -0
- bragi/contrib/api_tokens/admin.py +163 -0
- bragi/contrib/api_tokens/api.py +357 -0
- bragi/contrib/api_tokens/auth.py +261 -0
- bragi/contrib/api_tokens/plugin.py +59 -0
- bragi/contrib/api_tokens/templates/admin/api_tokens/created.html +16 -0
- bragi/contrib/api_tokens/templates/admin/api_tokens/list.html +72 -0
- bragi/contrib/api_tokens/tokens.py +151 -0
- bragi/contrib/attachments/__init__.py +10 -0
- bragi/contrib/attachments/admin.py +716 -0
- bragi/contrib/attachments/cli.py +436 -0
- bragi/contrib/attachments/delivery.py +132 -0
- bragi/contrib/attachments/plugin.py +208 -0
- bragi/contrib/attachments/service.py +146 -0
- bragi/contrib/attachments/templates/admin/_attachment_row.html +60 -0
- bragi/contrib/attachments/templates/admin/_attachments_picker_library.html +84 -0
- bragi/contrib/attachments/templates/admin/attachment_edit.html +62 -0
- bragi/contrib/attachments/templates/admin/attachments_list.html +110 -0
- bragi/contrib/attachments/templates/admin/attachments_new.html +22 -0
- bragi/contrib/attachments/templates/admin/attachments_picker.html +142 -0
- bragi/contrib/attachments/transforms.py +182 -0
- bragi/contrib/audit/__init__.py +8 -0
- bragi/contrib/audit/admin.py +79 -0
- bragi/contrib/audit/plugin.py +28 -0
- bragi/contrib/audit/templates/admin/audit_list.html +69 -0
- bragi/contrib/auth_github/__init__.py +15 -0
- bragi/contrib/auth_github/client.py +78 -0
- bragi/contrib/auth_github/plugin.py +26 -0
- bragi/contrib/auth_github/views.py +192 -0
- bragi/contrib/auth_local/__init__.py +19 -0
- bragi/contrib/auth_local/cli.py +252 -0
- bragi/contrib/auth_local/passwords.py +58 -0
- bragi/contrib/auth_local/plugin.py +112 -0
- bragi/contrib/auth_local/templates/auth_local/change_password.html +65 -0
- bragi/contrib/auth_local/templates/auth_local/login.html +61 -0
- bragi/contrib/auth_local/views.py +209 -0
- bragi/contrib/embeds/__init__.py +15 -0
- bragi/contrib/embeds/cli.py +47 -0
- bragi/contrib/embeds/directive.py +163 -0
- bragi/contrib/embeds/plugin.py +54 -0
- bragi/contrib/embeds/providers/__init__.py +49 -0
- bragi/contrib/embeds/providers/base.py +42 -0
- bragi/contrib/embeds/providers/bluesky.py +65 -0
- bragi/contrib/embeds/providers/oembed.py +94 -0
- bragi/contrib/embeds/providers/youtube.py +178 -0
- bragi/contrib/embeds/rerender.py +143 -0
- bragi/contrib/embeds/transforms.py +52 -0
- bragi/contrib/highlight/__init__.py +13 -0
- bragi/contrib/highlight/plugin.py +43 -0
- bragi/contrib/highlight/transform.py +64 -0
- bragi/contrib/import_ghost/__init__.py +12 -0
- bragi/contrib/import_ghost/admin.py +304 -0
- bragi/contrib/import_ghost/cli.py +75 -0
- bragi/contrib/import_ghost/importer.py +530 -0
- bragi/contrib/import_ghost/loader.py +70 -0
- bragi/contrib/import_ghost/plugin.py +63 -0
- bragi/contrib/import_ghost/templates/admin/import_ghost_result.html +34 -0
- bragi/contrib/import_ghost/templates/admin/import_ghost_review.html +48 -0
- bragi/contrib/import_ghost/templates/admin/import_ghost_upload.html +20 -0
- bragi/contrib/import_hugo/__init__.py +12 -0
- bragi/contrib/import_hugo/cli.py +75 -0
- bragi/contrib/import_hugo/importer.py +291 -0
- bragi/contrib/import_hugo/parser.py +53 -0
- bragi/contrib/import_hugo/plugin.py +36 -0
- bragi/contrib/import_linkedin/__init__.py +19 -0
- bragi/contrib/import_linkedin/admin.py +198 -0
- bragi/contrib/import_linkedin/cli.py +188 -0
- bragi/contrib/import_linkedin/importer.py +396 -0
- bragi/contrib/import_linkedin/parser.py +377 -0
- bragi/contrib/import_linkedin/plugin.py +43 -0
- bragi/contrib/import_linkedin/proposals.py +548 -0
- bragi/contrib/import_linkedin/templates/admin/_linkedin_import_widget.html +20 -0
- bragi/contrib/import_linkedin/templates/admin/linkedin_review.html +52 -0
- bragi/contrib/import_wordpress/__init__.py +12 -0
- bragi/contrib/import_wordpress/_upsert.py +335 -0
- bragi/contrib/import_wordpress/cli.py +88 -0
- bragi/contrib/import_wordpress/importer.py +295 -0
- bragi/contrib/import_wordpress/loader.py +279 -0
- bragi/contrib/import_wordpress/plugin.py +34 -0
- bragi/contrib/indexnow/__init__.py +17 -0
- bragi/contrib/indexnow/cli.py +59 -0
- bragi/contrib/indexnow/client.py +61 -0
- bragi/contrib/indexnow/plugin.py +171 -0
- bragi/contrib/indexnow/views.py +37 -0
- bragi/contrib/internal_links/__init__.py +29 -0
- bragi/contrib/internal_links/admin.py +286 -0
- bragi/contrib/internal_links/cli.py +71 -0
- bragi/contrib/internal_links/delivery.py +146 -0
- bragi/contrib/internal_links/index.py +162 -0
- bragi/contrib/internal_links/markdown_ext.py +139 -0
- bragi/contrib/internal_links/plugin.py +93 -0
- bragi/contrib/internal_links/templates/admin/_picker.html +85 -0
- bragi/contrib/internal_links/templates/admin/backlinks.html +41 -0
- bragi/contrib/markdown_extras/__init__.py +7 -0
- bragi/contrib/markdown_extras/plugin.py +88 -0
- bragi/contrib/nav/__init__.py +15 -0
- bragi/contrib/nav/plugin.py +83 -0
- bragi/contrib/nav/templates/delivery/_site_nav.html +104 -0
- bragi/contrib/nav/tree.py +58 -0
- bragi/contrib/page/__init__.py +7 -0
- bragi/contrib/page/admin.py +1535 -0
- bragi/contrib/page/archive.py +175 -0
- bragi/contrib/page/delivery.py +508 -0
- bragi/contrib/page/plugin.py +356 -0
- bragi/contrib/page/resume.py +42 -0
- bragi/contrib/page/resume_jsonld.py +104 -0
- bragi/contrib/page/static/pinned-carousel.js +146 -0
- bragi/contrib/page/templates/admin/_page_list_table.html +43 -0
- bragi/contrib/page/templates/admin/_page_menu_order_cell.html +16 -0
- bragi/contrib/page/templates/admin/_page_show_in_nav_cell.html +12 -0
- bragi/contrib/page/templates/admin/_page_slug_cell.html +28 -0
- bragi/contrib/page/templates/admin/_page_status_cell.html +18 -0
- bragi/contrib/page/templates/admin/_page_title_cell.html +30 -0
- bragi/contrib/page/templates/admin/_resume_fieldset.html +534 -0
- bragi/contrib/page/templates/admin/page_edit.html +213 -0
- bragi/contrib/page/templates/admin/page_list.html +15 -0
- bragi/contrib/page/templates/admin/page_revision_detail.html +46 -0
- bragi/contrib/page/templates/admin/page_revisions.html +38 -0
- bragi/contrib/page/templates/delivery/_pinned_carousel.html +46 -0
- bragi/contrib/page/templates/delivery/archive_month.html +20 -0
- bragi/contrib/page/templates/delivery/archive_year.html +15 -0
- bragi/contrib/page/templates/delivery/archive_years.html +17 -0
- bragi/contrib/page/templates/delivery/page.html +38 -0
- bragi/contrib/page/templates/delivery/post_index.html +60 -0
- bragi/contrib/page/templates/delivery/resume.html +193 -0
- bragi/contrib/page/templates/delivery/tag_list.html +25 -0
- bragi/contrib/post/__init__.py +11 -0
- bragi/contrib/post/admin.py +1080 -0
- bragi/contrib/post/cli.py +138 -0
- bragi/contrib/post/delivery.py +24 -0
- bragi/contrib/post/plugin.py +273 -0
- bragi/contrib/post/related.py +73 -0
- bragi/contrib/post/templates/admin/_pinned_cell.html +17 -0
- bragi/contrib/post/templates/admin/_post_list_table.html +47 -0
- bragi/contrib/post/templates/admin/_slug_cell.html +31 -0
- bragi/contrib/post/templates/admin/_status_cell.html +19 -0
- bragi/contrib/post/templates/admin/_title_cell.html +33 -0
- bragi/contrib/post/templates/admin/edit.html +122 -0
- bragi/contrib/post/templates/admin/list.html +15 -0
- bragi/contrib/post/templates/admin/post_revision_detail.html +45 -0
- bragi/contrib/post/templates/admin/post_revisions.html +36 -0
- bragi/contrib/post/templates/delivery/post.html +115 -0
- bragi/contrib/redirects/__init__.py +15 -0
- bragi/contrib/redirects/admin.py +298 -0
- bragi/contrib/redirects/plugin.py +396 -0
- bragi/contrib/redirects/templates/admin/redirects_edit.html +70 -0
- bragi/contrib/redirects/templates/admin/redirects_list.html +62 -0
- bragi/contrib/search/__init__.py +13 -0
- bragi/contrib/search/backend.py +409 -0
- bragi/contrib/search/cli.py +66 -0
- bragi/contrib/search/delivery.py +55 -0
- bragi/contrib/search/plugin.py +148 -0
- bragi/contrib/search/templates/delivery/_search_results.html +49 -0
- bragi/contrib/search/templates/delivery/search.html +25 -0
- bragi/contrib/search/text.py +31 -0
- bragi/contrib/seo/__init__.py +15 -0
- bragi/contrib/seo/feed.py +70 -0
- bragi/contrib/seo/plugin.py +42 -0
- bragi/contrib/seo/robots.py +32 -0
- bragi/contrib/seo/security_txt.py +33 -0
- bragi/contrib/seo/sitemap.py +106 -0
- bragi/contrib/sessions/__init__.py +17 -0
- bragi/contrib/sessions/admin.py +161 -0
- bragi/contrib/sessions/plugin.py +45 -0
- bragi/contrib/sessions/templates/admin/sessions_all_list.html +48 -0
- bragi/contrib/sessions/templates/admin/sessions_self_list.html +53 -0
- bragi/contrib/sites/__init__.py +11 -0
- bragi/contrib/sites/admin.py +951 -0
- bragi/contrib/sites/cli.py +240 -0
- bragi/contrib/sites/plugin.py +59 -0
- bragi/contrib/sites/templates/admin/site_dashboard.html +44 -0
- bragi/contrib/sites/templates/admin/sites_edit.html +226 -0
- bragi/contrib/sites/templates/admin/sites_list.html +66 -0
- bragi/contrib/team/__init__.py +18 -0
- bragi/contrib/team/admin.py +194 -0
- bragi/contrib/team/plugin.py +35 -0
- bragi/contrib/team/templates/admin/team_list.html +76 -0
- bragi/contrib/theme_default/__init__.py +14 -0
- bragi/contrib/theme_default/plugin.py +68 -0
- bragi/contrib/theme_default/static/resume.css +165 -0
- bragi/contrib/theme_default/templates/delivery/base.html +185 -0
- bragi/contrib/theme_minimal/__init__.py +10 -0
- bragi/contrib/theme_minimal/plugin.py +36 -0
- bragi/contrib/theme_minimal/static/resume.css +165 -0
- bragi/contrib/theme_minimal/templates/delivery/base.html +187 -0
- bragi/contrib/theme_serif/__init__.py +8 -0
- bragi/contrib/theme_serif/plugin.py +27 -0
- bragi/contrib/theme_serif/static/resume.css +165 -0
- bragi/contrib/theme_serif/templates/delivery/base.html +193 -0
- bragi/contrib/theme_terminal/__init__.py +9 -0
- bragi/contrib/theme_terminal/plugin.py +27 -0
- bragi/contrib/theme_terminal/static/resume.css +165 -0
- bragi/contrib/theme_terminal/templates/delivery/base.html +203 -0
- bragi/contrib/themes/__init__.py +22 -0
- bragi/contrib/themes/cli.py +32 -0
- bragi/contrib/themes/delivery.py +31 -0
- bragi/contrib/themes/plugin.py +31 -0
- bragi/contrib/unsplash/__init__.py +0 -0
- bragi/contrib/unsplash/admin.py +209 -0
- bragi/contrib/unsplash/client.py +116 -0
- bragi/contrib/unsplash/plugin.py +75 -0
- bragi/contrib/unsplash/render.py +226 -0
- bragi/contrib/unsplash/templates/admin/_unsplash_results.html +44 -0
- bragi/contrib/unsplash/templates/admin/_unsplash_tab.html +127 -0
- bragi/contrib/webmentions/__init__.py +24 -0
- bragi/contrib/webmentions/admin.py +91 -0
- bragi/contrib/webmentions/cli.py +36 -0
- bragi/contrib/webmentions/parse.py +200 -0
- bragi/contrib/webmentions/plugin.py +233 -0
- bragi/contrib/webmentions/receiver.py +267 -0
- bragi/contrib/webmentions/sender.py +151 -0
- bragi/contrib/webmentions/templates/admin/webmentions/list.html +56 -0
- bragi/core/__init__.py +12 -0
- bragi/core/audit.py +113 -0
- bragi/core/breadcrumbs.py +46 -0
- bragi/core/cache.py +144 -0
- bragi/core/db.py +69 -0
- bragi/core/export.py +421 -0
- bragi/core/feed.py +95 -0
- bragi/core/healthz.py +52 -0
- bragi/core/htmx.py +16 -0
- bragi/core/http.py +273 -0
- bragi/core/image_processor.py +247 -0
- bragi/core/middleware/__init__.py +12 -0
- bragi/core/middleware/csrf.py +118 -0
- bragi/core/middleware/redirects.py +94 -0
- bragi/core/middleware/sessions.py +265 -0
- bragi/core/middleware/site_resolver.py +58 -0
- bragi/core/models/__init__.py +85 -0
- bragi/core/models/_base.py +17 -0
- bragi/core/models/_mixins.py +40 -0
- bragi/core/models/activitypub.py +102 -0
- bragi/core/models/analytics_event.py +39 -0
- bragi/core/models/attachment.py +79 -0
- bragi/core/models/attachment_rendition.py +77 -0
- bragi/core/models/audit_log.py +45 -0
- bragi/core/models/internal_link.py +75 -0
- bragi/core/models/local_credential.py +31 -0
- bragi/core/models/page.py +153 -0
- bragi/core/models/page_revision.py +44 -0
- bragi/core/models/personal_access_token.py +74 -0
- bragi/core/models/post.py +104 -0
- bragi/core/models/post_revision.py +64 -0
- bragi/core/models/redirect.py +82 -0
- bragi/core/models/session.py +41 -0
- bragi/core/models/site.py +77 -0
- bragi/core/models/site_alias.py +30 -0
- bragi/core/models/tag.py +40 -0
- bragi/core/models/user.py +32 -0
- bragi/core/models/user_identity.py +35 -0
- bragi/core/models/user_site_role.py +36 -0
- bragi/core/models/webmention.py +108 -0
- bragi/core/permissions.py +202 -0
- bragi/core/registry.py +215 -0
- bragi/core/render/__init__.py +12 -0
- bragi/core/render/excerpts.py +69 -0
- bragi/core/render/markdown.py +146 -0
- bragi/core/render/reading_time.py +36 -0
- bragi/core/render/toc.py +101 -0
- bragi/core/render/transforms.py +58 -0
- bragi/core/renditions.py +178 -0
- bragi/core/safe_urls.py +158 -0
- bragi/core/security.py +55 -0
- bragi/core/seo.py +72 -0
- bragi/core/storage.py +249 -0
- bragi/core/text.py +110 -0
- bragi/core/themes.py +219 -0
- bragi/core/time.py +59 -0
- bragi/core/url.py +228 -0
- bragi/core/useragent.py +71 -0
- bragi/hookspecs.py +488 -0
- bragi/plugins.py +25 -0
- bragi/settings.py +222 -0
- bragi/static/admin/admin-chrome.css +254 -0
- bragi/static/admin/inline-edit.js +12 -0
- bragi/templates/admin/_admin_nav.html +157 -0
- bragi/templates/admin/_image_picker_field.html +167 -0
- bragi/templates/admin/_repeating_field.html +46 -0
- bragi/templates/admin/_tiptap_editor.html +760 -0
- bragi/templates/admin/base.html +29 -0
- bragi/templates/admin/index.html +33 -0
- bragi/templates/delivery/_welcome_fallback.html +17 -0
- bragi_cms-1.27.0.dist-info/METADATA +717 -0
- bragi_cms-1.27.0.dist-info/RECORD +357 -0
- bragi_cms-1.27.0.dist-info/WHEEL +4 -0
- bragi_cms-1.27.0.dist-info/entry_points.txt +41 -0
- bragi_cms-1.27.0.dist-info/licenses/LICENSE +21 -0
bragi/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""bragi: a multisite CMS with htmx, plugins, and SEO baked in."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
# Distribution name is `bragi-cms` (the `bragi` name on PyPI is held
|
|
9
|
+
# by an unrelated project). Import path and this package name stay
|
|
10
|
+
# `bragi`; the distribution name change only affects pip install.
|
|
11
|
+
__version__ = version("bragi-cms")
|
|
12
|
+
except PackageNotFoundError: # pragma: no cover - not installed
|
|
13
|
+
__version__ = "0.0.0+unknown"
|
|
14
|
+
|
|
15
|
+
__all__ = ["__version__"]
|
|
File without changes
|
bragi/alembic/env.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Alembic environment for bragi.
|
|
2
|
+
|
|
3
|
+
Reads the DB URL from BRAGI_DATABASE_URL when set, otherwise falls
|
|
4
|
+
back to the value in alembic.ini. Target metadata is
|
|
5
|
+
`bragi.core.models.Base.metadata` so autogenerate sees every model.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
from logging.config import fileConfig
|
|
12
|
+
|
|
13
|
+
from alembic import context
|
|
14
|
+
from sqlalchemy import engine_from_config, pool
|
|
15
|
+
|
|
16
|
+
# Alembic Config object; gives access to values in alembic.ini.
|
|
17
|
+
config = context.config
|
|
18
|
+
|
|
19
|
+
# Override DB URL from environment when present.
|
|
20
|
+
if db_url := os.environ.get("BRAGI_DATABASE_URL"):
|
|
21
|
+
config.set_main_option("sqlalchemy.url", db_url)
|
|
22
|
+
|
|
23
|
+
# Configure loggers from alembic.ini.
|
|
24
|
+
if config.config_file_name is not None:
|
|
25
|
+
fileConfig(config.config_file_name)
|
|
26
|
+
|
|
27
|
+
# Target metadata for autogenerate. Imported lazily so the alembic
|
|
28
|
+
# CLI still works during early scaffolding before models exist.
|
|
29
|
+
try:
|
|
30
|
+
from bragi.core.models import Base
|
|
31
|
+
|
|
32
|
+
target_metadata = Base.metadata
|
|
33
|
+
except ImportError:
|
|
34
|
+
target_metadata = None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def run_migrations_offline() -> None:
|
|
38
|
+
"""Run migrations in 'offline' mode, emitting SQL to a script."""
|
|
39
|
+
url = config.get_main_option("sqlalchemy.url")
|
|
40
|
+
context.configure(
|
|
41
|
+
url=url,
|
|
42
|
+
target_metadata=target_metadata,
|
|
43
|
+
literal_binds=True,
|
|
44
|
+
dialect_opts={"paramstyle": "named"},
|
|
45
|
+
)
|
|
46
|
+
with context.begin_transaction():
|
|
47
|
+
context.run_migrations()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def run_migrations_online() -> None:
|
|
51
|
+
"""Run migrations in 'online' mode against a live DB."""
|
|
52
|
+
connectable = engine_from_config(
|
|
53
|
+
config.get_section(config.config_ini_section, {}),
|
|
54
|
+
prefix="sqlalchemy.",
|
|
55
|
+
poolclass=pool.NullPool,
|
|
56
|
+
)
|
|
57
|
+
with connectable.connect() as connection:
|
|
58
|
+
context.configure(connection=connection, target_metadata=target_metadata)
|
|
59
|
+
with context.begin_transaction():
|
|
60
|
+
context.run_migrations()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
if context.is_offline_mode():
|
|
64
|
+
run_migrations_offline()
|
|
65
|
+
else:
|
|
66
|
+
run_migrations_online()
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""${message}
|
|
2
|
+
|
|
3
|
+
Revision ID: ${up_revision}
|
|
4
|
+
Revises: ${down_revision | comma,n}
|
|
5
|
+
Create Date: ${create_date}
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from collections.abc import Sequence
|
|
12
|
+
|
|
13
|
+
import sqlalchemy as sa
|
|
14
|
+
from alembic import op
|
|
15
|
+
${imports if imports else ""}
|
|
16
|
+
|
|
17
|
+
# revision identifiers, used by Alembic.
|
|
18
|
+
revision: str = ${repr(up_revision)}
|
|
19
|
+
down_revision: str | None = ${repr(down_revision)}
|
|
20
|
+
branch_labels: str | Sequence[str] | None = ${repr(branch_labels)}
|
|
21
|
+
depends_on: str | Sequence[str] | None = ${repr(depends_on)}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def upgrade() -> None:
|
|
25
|
+
${upgrades if upgrades else "pass"}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def downgrade() -> None:
|
|
29
|
+
${downgrades if downgrades else "pass"}
|
|
File without changes
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""initial_schema
|
|
2
|
+
|
|
3
|
+
Revision ID: cb48ebc1f1f5
|
|
4
|
+
Revises:
|
|
5
|
+
Create Date: 2026-05-13 21:19:28.794005+00:00
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from collections.abc import Sequence
|
|
10
|
+
|
|
11
|
+
import sqlalchemy as sa
|
|
12
|
+
from alembic import op
|
|
13
|
+
|
|
14
|
+
# revision identifiers, used by Alembic.
|
|
15
|
+
revision: str = "cb48ebc1f1f5"
|
|
16
|
+
down_revision: str | None = None
|
|
17
|
+
branch_labels: str | Sequence[str] | None = None
|
|
18
|
+
depends_on: str | Sequence[str] | None = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def upgrade() -> None:
|
|
22
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
23
|
+
op.create_table(
|
|
24
|
+
"sites",
|
|
25
|
+
sa.Column("slug", sa.String(length=64), nullable=False),
|
|
26
|
+
sa.Column("hostname", sa.String(length=255), nullable=False),
|
|
27
|
+
sa.Column("title", sa.String(length=255), nullable=False),
|
|
28
|
+
sa.Column("locale", sa.String(length=16), nullable=False),
|
|
29
|
+
sa.Column("timezone", sa.String(length=64), nullable=False),
|
|
30
|
+
sa.Column("canonical_url", sa.String(length=255), nullable=False),
|
|
31
|
+
sa.Column("active", sa.Boolean(), nullable=False),
|
|
32
|
+
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
|
33
|
+
sa.Column("created_at", sa.DateTime(), nullable=False),
|
|
34
|
+
sa.Column("updated_at", sa.DateTime(), nullable=False),
|
|
35
|
+
sa.PrimaryKeyConstraint("id"),
|
|
36
|
+
sa.UniqueConstraint("hostname"),
|
|
37
|
+
sa.UniqueConstraint("slug"),
|
|
38
|
+
)
|
|
39
|
+
op.create_table(
|
|
40
|
+
"users",
|
|
41
|
+
sa.Column("email", sa.String(length=255), nullable=False),
|
|
42
|
+
sa.Column("display_name", sa.String(length=255), nullable=False),
|
|
43
|
+
sa.Column("is_superuser", sa.Boolean(), nullable=False),
|
|
44
|
+
sa.Column("is_active", sa.Boolean(), nullable=False),
|
|
45
|
+
sa.Column("last_login_at", sa.DateTime(), nullable=True),
|
|
46
|
+
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
|
47
|
+
sa.Column("created_at", sa.DateTime(), nullable=False),
|
|
48
|
+
sa.Column("updated_at", sa.DateTime(), nullable=False),
|
|
49
|
+
sa.PrimaryKeyConstraint("id"),
|
|
50
|
+
sa.UniqueConstraint("email"),
|
|
51
|
+
)
|
|
52
|
+
op.create_table(
|
|
53
|
+
"posts",
|
|
54
|
+
sa.Column("site_id", sa.Integer(), nullable=False),
|
|
55
|
+
sa.Column("slug", sa.String(length=255), nullable=False),
|
|
56
|
+
sa.Column("title", sa.String(length=255), nullable=False),
|
|
57
|
+
sa.Column("subtitle", sa.String(length=255), nullable=True),
|
|
58
|
+
sa.Column("body_markdown", sa.Text(), nullable=False),
|
|
59
|
+
sa.Column("body_html", sa.Text(), nullable=False),
|
|
60
|
+
sa.Column("body_excerpt", sa.Text(), nullable=False),
|
|
61
|
+
sa.Column("author_id", sa.Integer(), nullable=False),
|
|
62
|
+
sa.Column("status", sa.String(length=16), nullable=False),
|
|
63
|
+
sa.Column("published_at", sa.DateTime(), nullable=True),
|
|
64
|
+
sa.Column("scheduled_for", sa.DateTime(), nullable=True),
|
|
65
|
+
sa.Column("meta_title", sa.String(length=255), nullable=True),
|
|
66
|
+
sa.Column("meta_description", sa.Text(), nullable=True),
|
|
67
|
+
sa.Column("canonical_url", sa.String(length=255), nullable=True),
|
|
68
|
+
sa.Column("noindex", sa.Boolean(), nullable=False),
|
|
69
|
+
sa.Column("source_id", sa.String(length=255), nullable=True),
|
|
70
|
+
sa.Column("source_meta", sa.JSON(), nullable=True),
|
|
71
|
+
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
|
72
|
+
sa.Column("created_at", sa.DateTime(), nullable=False),
|
|
73
|
+
sa.Column("updated_at", sa.DateTime(), nullable=False),
|
|
74
|
+
sa.ForeignKeyConstraint(
|
|
75
|
+
["author_id"],
|
|
76
|
+
["users.id"],
|
|
77
|
+
),
|
|
78
|
+
sa.ForeignKeyConstraint(
|
|
79
|
+
["site_id"],
|
|
80
|
+
["sites.id"],
|
|
81
|
+
),
|
|
82
|
+
sa.PrimaryKeyConstraint("id"),
|
|
83
|
+
sa.UniqueConstraint("site_id", "slug", name="uq_posts_site_slug"),
|
|
84
|
+
)
|
|
85
|
+
op.create_index(op.f("ix_posts_published_at"), "posts", ["published_at"], unique=False)
|
|
86
|
+
op.create_index(op.f("ix_posts_scheduled_for"), "posts", ["scheduled_for"], unique=False)
|
|
87
|
+
op.create_index(op.f("ix_posts_source_id"), "posts", ["source_id"], unique=False)
|
|
88
|
+
# ### end Alembic commands ###
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def downgrade() -> None:
|
|
92
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
93
|
+
op.drop_index(op.f("ix_posts_source_id"), table_name="posts")
|
|
94
|
+
op.drop_index(op.f("ix_posts_scheduled_for"), table_name="posts")
|
|
95
|
+
op.drop_index(op.f("ix_posts_published_at"), table_name="posts")
|
|
96
|
+
op.drop_table("posts")
|
|
97
|
+
op.drop_table("users")
|
|
98
|
+
op.drop_table("sites")
|
|
99
|
+
# ### end Alembic commands ###
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""add_redirects
|
|
2
|
+
|
|
3
|
+
Revision ID: 36a0ec65ddbf
|
|
4
|
+
Revises: cb48ebc1f1f5
|
|
5
|
+
Create Date: 2026-05-13 21:38:41.962916+00:00
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from collections.abc import Sequence
|
|
12
|
+
|
|
13
|
+
import sqlalchemy as sa
|
|
14
|
+
from alembic import op
|
|
15
|
+
|
|
16
|
+
# revision identifiers, used by Alembic.
|
|
17
|
+
revision: str = "36a0ec65ddbf"
|
|
18
|
+
down_revision: str | None = "cb48ebc1f1f5"
|
|
19
|
+
branch_labels: str | Sequence[str] | None = None
|
|
20
|
+
depends_on: str | Sequence[str] | None = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def upgrade() -> None:
|
|
24
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
25
|
+
op.create_table(
|
|
26
|
+
"redirects",
|
|
27
|
+
sa.Column("site_id", sa.Integer(), nullable=False),
|
|
28
|
+
sa.Column("source_path", sa.String(length=1024), nullable=False),
|
|
29
|
+
sa.Column("target", sa.String(length=1024), nullable=False),
|
|
30
|
+
sa.Column("status_code", sa.Integer(), nullable=False),
|
|
31
|
+
sa.Column("match_type", sa.String(length=16), nullable=False),
|
|
32
|
+
sa.Column("active", sa.Boolean(), nullable=False),
|
|
33
|
+
sa.Column("hit_count", sa.Integer(), nullable=False),
|
|
34
|
+
sa.Column("last_hit_at", sa.DateTime(), nullable=True),
|
|
35
|
+
sa.Column("source", sa.String(length=64), nullable=False),
|
|
36
|
+
sa.Column("note", sa.Text(), nullable=True),
|
|
37
|
+
sa.Column("broken", sa.Boolean(), nullable=False),
|
|
38
|
+
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
|
39
|
+
sa.Column("created_at", sa.DateTime(), nullable=False),
|
|
40
|
+
sa.Column("updated_at", sa.DateTime(), nullable=False),
|
|
41
|
+
sa.ForeignKeyConstraint(
|
|
42
|
+
["site_id"],
|
|
43
|
+
["sites.id"],
|
|
44
|
+
),
|
|
45
|
+
sa.PrimaryKeyConstraint("id"),
|
|
46
|
+
sa.UniqueConstraint(
|
|
47
|
+
"site_id", "source_path", "match_type", name="uq_redirects_site_source_match"
|
|
48
|
+
),
|
|
49
|
+
)
|
|
50
|
+
# ### end Alembic commands ###
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def downgrade() -> None:
|
|
54
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
55
|
+
op.drop_table("redirects")
|
|
56
|
+
# ### end Alembic commands ###
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""add_local_credentials
|
|
2
|
+
|
|
3
|
+
Revision ID: 52631645e3c1
|
|
4
|
+
Revises: 36a0ec65ddbf
|
|
5
|
+
Create Date: 2026-05-13 21:45:47.264899+00:00
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from collections.abc import Sequence
|
|
12
|
+
|
|
13
|
+
import sqlalchemy as sa
|
|
14
|
+
from alembic import op
|
|
15
|
+
|
|
16
|
+
# revision identifiers, used by Alembic.
|
|
17
|
+
revision: str = "52631645e3c1"
|
|
18
|
+
down_revision: str | None = "36a0ec65ddbf"
|
|
19
|
+
branch_labels: str | Sequence[str] | None = None
|
|
20
|
+
depends_on: str | Sequence[str] | None = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def upgrade() -> None:
|
|
24
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
25
|
+
op.create_table(
|
|
26
|
+
"local_credentials",
|
|
27
|
+
sa.Column("user_id", sa.Integer(), nullable=False),
|
|
28
|
+
sa.Column("password_hash", sa.String(length=255), nullable=False),
|
|
29
|
+
sa.Column("must_change", sa.Boolean(), nullable=False),
|
|
30
|
+
sa.Column("created_at", sa.DateTime(), nullable=False),
|
|
31
|
+
sa.Column("updated_at", sa.DateTime(), nullable=False),
|
|
32
|
+
sa.ForeignKeyConstraint(
|
|
33
|
+
["user_id"],
|
|
34
|
+
["users.id"],
|
|
35
|
+
),
|
|
36
|
+
sa.PrimaryKeyConstraint("user_id"),
|
|
37
|
+
)
|
|
38
|
+
# ### end Alembic commands ###
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def downgrade() -> None:
|
|
42
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
43
|
+
op.drop_table("local_credentials")
|
|
44
|
+
# ### end Alembic commands ###
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""add_sessions
|
|
2
|
+
|
|
3
|
+
Revision ID: 38aa6fec0409
|
|
4
|
+
Revises: 52631645e3c1
|
|
5
|
+
Create Date: 2026-05-13 22:08:11.125671+00:00
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from collections.abc import Sequence
|
|
12
|
+
|
|
13
|
+
import sqlalchemy as sa
|
|
14
|
+
from alembic import op
|
|
15
|
+
|
|
16
|
+
# revision identifiers, used by Alembic.
|
|
17
|
+
revision: str = "38aa6fec0409"
|
|
18
|
+
down_revision: str | None = "52631645e3c1"
|
|
19
|
+
branch_labels: str | Sequence[str] | None = None
|
|
20
|
+
depends_on: str | Sequence[str] | None = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def upgrade() -> None:
|
|
24
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
25
|
+
op.create_table(
|
|
26
|
+
"sessions",
|
|
27
|
+
sa.Column("id", sa.String(length=64), nullable=False),
|
|
28
|
+
sa.Column("user_id", sa.Integer(), nullable=True),
|
|
29
|
+
sa.Column("expires_at", sa.DateTime(), nullable=False),
|
|
30
|
+
sa.Column("last_seen_at", sa.DateTime(), nullable=False),
|
|
31
|
+
sa.Column("ip", sa.String(length=64), nullable=True),
|
|
32
|
+
sa.Column("user_agent", sa.String(length=512), nullable=True),
|
|
33
|
+
sa.Column("data", sa.JSON(), nullable=False),
|
|
34
|
+
sa.Column("created_at", sa.DateTime(), nullable=False),
|
|
35
|
+
sa.Column("updated_at", sa.DateTime(), nullable=False),
|
|
36
|
+
sa.ForeignKeyConstraint(
|
|
37
|
+
["user_id"],
|
|
38
|
+
["users.id"],
|
|
39
|
+
),
|
|
40
|
+
sa.PrimaryKeyConstraint("id"),
|
|
41
|
+
)
|
|
42
|
+
op.create_index(op.f("ix_sessions_expires_at"), "sessions", ["expires_at"], unique=False)
|
|
43
|
+
op.create_index(op.f("ix_sessions_user_id"), "sessions", ["user_id"], unique=False)
|
|
44
|
+
# ### end Alembic commands ###
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def downgrade() -> None:
|
|
48
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
49
|
+
op.drop_index(op.f("ix_sessions_user_id"), table_name="sessions")
|
|
50
|
+
op.drop_index(op.f("ix_sessions_expires_at"), table_name="sessions")
|
|
51
|
+
op.drop_table("sessions")
|
|
52
|
+
# ### end Alembic commands ###
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""add_audit_log
|
|
2
|
+
|
|
3
|
+
Revision ID: 9dbc52ee17a8
|
|
4
|
+
Revises: 38aa6fec0409
|
|
5
|
+
Create Date: 2026-05-14 05:26:36.147437+00:00
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from collections.abc import Sequence
|
|
12
|
+
|
|
13
|
+
import sqlalchemy as sa
|
|
14
|
+
from alembic import op
|
|
15
|
+
|
|
16
|
+
# revision identifiers, used by Alembic.
|
|
17
|
+
revision: str = "9dbc52ee17a8"
|
|
18
|
+
down_revision: str | None = "38aa6fec0409"
|
|
19
|
+
branch_labels: str | Sequence[str] | None = None
|
|
20
|
+
depends_on: str | Sequence[str] | None = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def upgrade() -> None:
|
|
24
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
25
|
+
op.create_table(
|
|
26
|
+
"audit_log",
|
|
27
|
+
sa.Column("actor_user_id", sa.Integer(), nullable=True),
|
|
28
|
+
sa.Column("action", sa.String(length=64), nullable=False),
|
|
29
|
+
sa.Column("target_type", sa.String(length=32), nullable=True),
|
|
30
|
+
sa.Column("target_id", sa.Integer(), nullable=True),
|
|
31
|
+
sa.Column("site_id", sa.Integer(), nullable=True),
|
|
32
|
+
sa.Column("occurred_at", sa.DateTime(), nullable=False),
|
|
33
|
+
sa.Column("ip", sa.String(length=64), nullable=True),
|
|
34
|
+
sa.Column("user_agent", sa.String(length=512), nullable=True),
|
|
35
|
+
sa.Column("extra", sa.JSON(), nullable=False),
|
|
36
|
+
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
|
37
|
+
sa.ForeignKeyConstraint(
|
|
38
|
+
["actor_user_id"],
|
|
39
|
+
["users.id"],
|
|
40
|
+
),
|
|
41
|
+
sa.ForeignKeyConstraint(
|
|
42
|
+
["site_id"],
|
|
43
|
+
["sites.id"],
|
|
44
|
+
),
|
|
45
|
+
sa.PrimaryKeyConstraint("id"),
|
|
46
|
+
)
|
|
47
|
+
op.create_index(op.f("ix_audit_log_action"), "audit_log", ["action"], unique=False)
|
|
48
|
+
op.create_index(
|
|
49
|
+
op.f("ix_audit_log_actor_user_id"), "audit_log", ["actor_user_id"], unique=False
|
|
50
|
+
)
|
|
51
|
+
op.create_index(op.f("ix_audit_log_occurred_at"), "audit_log", ["occurred_at"], unique=False)
|
|
52
|
+
# ### end Alembic commands ###
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def downgrade() -> None:
|
|
56
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
57
|
+
op.drop_index(op.f("ix_audit_log_occurred_at"), table_name="audit_log")
|
|
58
|
+
op.drop_index(op.f("ix_audit_log_actor_user_id"), table_name="audit_log")
|
|
59
|
+
op.drop_index(op.f("ix_audit_log_action"), table_name="audit_log")
|
|
60
|
+
op.drop_table("audit_log")
|
|
61
|
+
# ### end Alembic commands ###
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""add_attachments_and_post_image_fks
|
|
2
|
+
|
|
3
|
+
Revision ID: 9f3fd65db818
|
|
4
|
+
Revises: 9dbc52ee17a8
|
|
5
|
+
Create Date: 2026-05-14 05:38:23.956762+00:00
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from collections.abc import Sequence
|
|
12
|
+
|
|
13
|
+
import sqlalchemy as sa
|
|
14
|
+
from alembic import op
|
|
15
|
+
|
|
16
|
+
# revision identifiers, used by Alembic.
|
|
17
|
+
revision: str = "9f3fd65db818"
|
|
18
|
+
down_revision: str | None = "9dbc52ee17a8"
|
|
19
|
+
branch_labels: str | Sequence[str] | None = None
|
|
20
|
+
depends_on: str | Sequence[str] | None = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def upgrade() -> None:
|
|
24
|
+
op.create_table(
|
|
25
|
+
"attachments",
|
|
26
|
+
sa.Column("site_id", sa.Integer(), nullable=False),
|
|
27
|
+
sa.Column("filename", sa.String(length=255), nullable=False),
|
|
28
|
+
sa.Column("content_type", sa.String(length=127), nullable=False),
|
|
29
|
+
sa.Column("size_bytes", sa.Integer(), nullable=False),
|
|
30
|
+
sa.Column("storage_key", sa.String(length=128), nullable=False),
|
|
31
|
+
sa.Column("uploaded_by", sa.Integer(), nullable=True),
|
|
32
|
+
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
|
33
|
+
sa.Column("created_at", sa.DateTime(), nullable=False),
|
|
34
|
+
sa.Column("updated_at", sa.DateTime(), nullable=False),
|
|
35
|
+
sa.ForeignKeyConstraint(
|
|
36
|
+
["site_id"],
|
|
37
|
+
["sites.id"],
|
|
38
|
+
),
|
|
39
|
+
sa.ForeignKeyConstraint(
|
|
40
|
+
["uploaded_by"],
|
|
41
|
+
["users.id"],
|
|
42
|
+
),
|
|
43
|
+
sa.PrimaryKeyConstraint("id"),
|
|
44
|
+
sa.UniqueConstraint("site_id", "storage_key", name="uq_attachments_site_key"),
|
|
45
|
+
)
|
|
46
|
+
op.create_index(op.f("ix_attachments_site_id"), "attachments", ["site_id"], unique=False)
|
|
47
|
+
|
|
48
|
+
# SQLite can't ALTER TABLE ... ADD CONSTRAINT; batch mode does
|
|
49
|
+
# the copy-and-move dance. Pass-through on PostgreSQL.
|
|
50
|
+
with op.batch_alter_table("posts") as batch_op:
|
|
51
|
+
batch_op.add_column(sa.Column("featured_image_id", sa.Integer(), nullable=True))
|
|
52
|
+
batch_op.add_column(sa.Column("og_image_id", sa.Integer(), nullable=True))
|
|
53
|
+
batch_op.create_foreign_key(
|
|
54
|
+
"fk_posts_featured_image_id_attachments",
|
|
55
|
+
"attachments",
|
|
56
|
+
["featured_image_id"],
|
|
57
|
+
["id"],
|
|
58
|
+
)
|
|
59
|
+
batch_op.create_foreign_key(
|
|
60
|
+
"fk_posts_og_image_id_attachments",
|
|
61
|
+
"attachments",
|
|
62
|
+
["og_image_id"],
|
|
63
|
+
["id"],
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def downgrade() -> None:
|
|
68
|
+
with op.batch_alter_table("posts") as batch_op:
|
|
69
|
+
batch_op.drop_constraint("fk_posts_og_image_id_attachments", type_="foreignkey")
|
|
70
|
+
batch_op.drop_constraint("fk_posts_featured_image_id_attachments", type_="foreignkey")
|
|
71
|
+
batch_op.drop_column("og_image_id")
|
|
72
|
+
batch_op.drop_column("featured_image_id")
|
|
73
|
+
op.drop_index(op.f("ix_attachments_site_id"), table_name="attachments")
|
|
74
|
+
op.drop_table("attachments")
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""add_analytics_events
|
|
2
|
+
|
|
3
|
+
Revision ID: 1f966d693a2d
|
|
4
|
+
Revises: 9f3fd65db818
|
|
5
|
+
Create Date: 2026-05-14 06:23:04.536210+00:00
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from collections.abc import Sequence
|
|
12
|
+
|
|
13
|
+
import sqlalchemy as sa
|
|
14
|
+
from alembic import op
|
|
15
|
+
|
|
16
|
+
# revision identifiers, used by Alembic.
|
|
17
|
+
revision: str = "1f966d693a2d"
|
|
18
|
+
down_revision: str | None = "9f3fd65db818"
|
|
19
|
+
branch_labels: str | Sequence[str] | None = None
|
|
20
|
+
depends_on: str | Sequence[str] | None = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def upgrade() -> None:
|
|
24
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
25
|
+
op.create_table(
|
|
26
|
+
"analytics_events",
|
|
27
|
+
sa.Column("site_id", sa.Integer(), nullable=False),
|
|
28
|
+
sa.Column("event_type", sa.String(length=32), nullable=False),
|
|
29
|
+
sa.Column("path", sa.String(length=1024), nullable=True),
|
|
30
|
+
sa.Column("referrer", sa.String(length=1024), nullable=True),
|
|
31
|
+
sa.Column("user_agent_class", sa.String(length=32), nullable=True),
|
|
32
|
+
sa.Column("user_id", sa.Integer(), nullable=True),
|
|
33
|
+
sa.Column("occurred_at", sa.DateTime(), nullable=False),
|
|
34
|
+
sa.Column("extra", sa.JSON(), nullable=False),
|
|
35
|
+
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
|
36
|
+
sa.ForeignKeyConstraint(
|
|
37
|
+
["site_id"],
|
|
38
|
+
["sites.id"],
|
|
39
|
+
),
|
|
40
|
+
sa.ForeignKeyConstraint(
|
|
41
|
+
["user_id"],
|
|
42
|
+
["users.id"],
|
|
43
|
+
),
|
|
44
|
+
sa.PrimaryKeyConstraint("id"),
|
|
45
|
+
)
|
|
46
|
+
op.create_index(
|
|
47
|
+
op.f("ix_analytics_events_event_type"), "analytics_events", ["event_type"], unique=False
|
|
48
|
+
)
|
|
49
|
+
op.create_index(
|
|
50
|
+
op.f("ix_analytics_events_occurred_at"), "analytics_events", ["occurred_at"], unique=False
|
|
51
|
+
)
|
|
52
|
+
op.create_index(
|
|
53
|
+
op.f("ix_analytics_events_site_id"), "analytics_events", ["site_id"], unique=False
|
|
54
|
+
)
|
|
55
|
+
op.create_index(
|
|
56
|
+
op.f("ix_analytics_events_user_agent_class"),
|
|
57
|
+
"analytics_events",
|
|
58
|
+
["user_agent_class"],
|
|
59
|
+
unique=False,
|
|
60
|
+
)
|
|
61
|
+
# ### end Alembic commands ###
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def downgrade() -> None:
|
|
65
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
66
|
+
op.drop_index(op.f("ix_analytics_events_user_agent_class"), table_name="analytics_events")
|
|
67
|
+
op.drop_index(op.f("ix_analytics_events_site_id"), table_name="analytics_events")
|
|
68
|
+
op.drop_index(op.f("ix_analytics_events_occurred_at"), table_name="analytics_events")
|
|
69
|
+
op.drop_index(op.f("ix_analytics_events_event_type"), table_name="analytics_events")
|
|
70
|
+
op.drop_table("analytics_events")
|
|
71
|
+
# ### end Alembic commands ###
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""add extra_settings to sites
|
|
2
|
+
|
|
3
|
+
Revision ID: f679d5fc62bb
|
|
4
|
+
Revises: 1f966d693a2d
|
|
5
|
+
Create Date: 2026-05-14 06:41:17.203719+00:00
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from collections.abc import Sequence
|
|
12
|
+
|
|
13
|
+
import sqlalchemy as sa
|
|
14
|
+
from alembic import op
|
|
15
|
+
|
|
16
|
+
# revision identifiers, used by Alembic.
|
|
17
|
+
revision: str = "f679d5fc62bb"
|
|
18
|
+
down_revision: str | None = "1f966d693a2d"
|
|
19
|
+
branch_labels: str | Sequence[str] | None = None
|
|
20
|
+
depends_on: str | Sequence[str] | None = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def upgrade() -> None:
|
|
24
|
+
# server_default makes the migration safe against a populated
|
|
25
|
+
# `sites` table: existing rows get an empty JSON object on add.
|
|
26
|
+
# We don't keep the server_default on the live column because
|
|
27
|
+
# the Python-side `default=dict` is the source of truth at the
|
|
28
|
+
# ORM layer.
|
|
29
|
+
op.add_column(
|
|
30
|
+
"sites",
|
|
31
|
+
sa.Column(
|
|
32
|
+
"extra_settings",
|
|
33
|
+
sa.JSON(),
|
|
34
|
+
nullable=False,
|
|
35
|
+
server_default="{}",
|
|
36
|
+
),
|
|
37
|
+
)
|
|
38
|
+
with op.batch_alter_table("sites") as batch:
|
|
39
|
+
batch.alter_column("extra_settings", server_default=None)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def downgrade() -> None:
|
|
43
|
+
op.drop_column("sites", "extra_settings")
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""add_site_aliases
|
|
2
|
+
|
|
3
|
+
Revision ID: 0e57d255f32d
|
|
4
|
+
Revises: f679d5fc62bb
|
|
5
|
+
Create Date: 2026-05-14 07:19:37.827827+00:00
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from collections.abc import Sequence
|
|
12
|
+
|
|
13
|
+
import sqlalchemy as sa
|
|
14
|
+
from alembic import op
|
|
15
|
+
|
|
16
|
+
# revision identifiers, used by Alembic.
|
|
17
|
+
revision: str = "0e57d255f32d"
|
|
18
|
+
down_revision: str | None = "f679d5fc62bb"
|
|
19
|
+
branch_labels: str | Sequence[str] | None = None
|
|
20
|
+
depends_on: str | Sequence[str] | None = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def upgrade() -> None:
|
|
24
|
+
op.create_table(
|
|
25
|
+
"site_aliases",
|
|
26
|
+
sa.Column("site_id", sa.Integer(), nullable=False),
|
|
27
|
+
sa.Column("hostname", sa.String(length=255), nullable=False),
|
|
28
|
+
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
|
29
|
+
sa.Column("created_at", sa.DateTime(), nullable=False),
|
|
30
|
+
sa.Column("updated_at", sa.DateTime(), nullable=False),
|
|
31
|
+
sa.ForeignKeyConstraint(["site_id"], ["sites.id"]),
|
|
32
|
+
sa.PrimaryKeyConstraint("id"),
|
|
33
|
+
sa.UniqueConstraint("hostname"),
|
|
34
|
+
)
|
|
35
|
+
op.create_index(op.f("ix_site_aliases_site_id"), "site_aliases", ["site_id"], unique=False)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def downgrade() -> None:
|
|
39
|
+
op.drop_index(op.f("ix_site_aliases_site_id"), table_name="site_aliases")
|
|
40
|
+
op.drop_table("site_aliases")
|