panda-core 0.12.5 → 1.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 +4 -4
- data/LICENSE +1 -1
- data/README.md +53 -0
- data/app/assets/builds/panda_core_admin.css +2 -0
- data/app/assets/tailwind/application.css +236 -36
- data/app/builders/panda/core/form_builder.rb +120 -18
- data/app/components/panda/core/UI/badge.html.erb +10 -0
- data/app/components/panda/core/UI/badge.rb +14 -30
- data/app/components/panda/core/UI/button.html.erb +5 -0
- data/app/components/panda/core/UI/button.rb +11 -15
- data/app/components/panda/core/UI/card.html.erb +3 -0
- data/app/components/panda/core/UI/card.rb +7 -6
- data/app/components/panda/core/admin/attribute_diff_component.html.erb +21 -0
- data/app/components/panda/core/admin/attribute_diff_component.rb +29 -0
- data/app/components/panda/core/admin/breadcrumb_component.html.erb +36 -0
- data/app/components/panda/core/admin/breadcrumb_component.rb +12 -64
- data/app/components/panda/core/admin/breadcrumbs_component.html.erb +16 -0
- data/app/components/panda/core/admin/breadcrumbs_component.rb +26 -0
- data/app/components/panda/core/admin/button_component.html.erb +9 -0
- data/app/components/panda/core/admin/button_component.rb +36 -31
- data/app/components/panda/core/admin/callout_component.html.erb +17 -0
- data/app/components/panda/core/admin/callout_component.rb +57 -0
- data/app/components/panda/core/admin/code_block_component.html.erb +9 -0
- data/app/components/panda/core/admin/code_block_component.rb +23 -0
- data/app/components/panda/core/admin/container_component.html.erb +27 -0
- data/app/components/panda/core/admin/container_component.rb +11 -77
- data/app/components/panda/core/admin/contributors_summary_component.html.erb +15 -0
- data/app/components/panda/core/admin/contributors_summary_component.rb +23 -0
- data/app/components/panda/core/admin/dashboard_default_content_component.html.erb +70 -0
- data/app/components/panda/core/admin/dashboard_default_content_component.rb +32 -0
- data/app/components/panda/core/admin/delete_button_component.html.erb +4 -0
- data/app/components/panda/core/admin/delete_button_component.rb +41 -0
- data/app/components/panda/core/admin/dropdown_button_component.html.erb +28 -0
- data/app/components/panda/core/admin/dropdown_button_component.rb +100 -0
- data/app/components/panda/core/admin/dropdown_component.html.erb +32 -0
- data/app/components/panda/core/admin/dropdown_component.rb +65 -0
- data/app/components/panda/core/admin/empty_state_component.html.erb +16 -0
- data/app/components/panda/core/admin/empty_state_component.rb +25 -0
- data/app/components/panda/core/admin/file_gallery_component.html.erb +58 -0
- data/app/components/panda/core/admin/file_gallery_component.rb +26 -98
- data/app/components/panda/core/admin/filter_pills_component.html.erb +6 -0
- data/app/components/panda/core/admin/filter_pills_component.rb +29 -0
- data/app/components/panda/core/admin/flash_message_component.html.erb +11 -0
- data/app/components/panda/core/admin/flash_message_component.rb +33 -60
- data/app/components/panda/core/admin/form_error_component.html.erb +7 -0
- data/app/components/panda/core/admin/form_error_component.rb +8 -16
- data/app/components/panda/core/admin/form_footer_component.html.erb +16 -0
- data/app/components/panda/core/admin/form_footer_component.rb +65 -0
- data/app/components/panda/core/admin/form_input_component.html.erb +1 -0
- data/app/components/panda/core/admin/form_input_component.rb +21 -13
- data/app/components/panda/core/admin/form_section_component.html.erb +16 -0
- data/app/components/panda/core/admin/form_section_component.rb +64 -0
- data/app/components/panda/core/admin/form_select_component.html.erb +5 -0
- data/app/components/panda/core/admin/form_select_component.rb +25 -26
- data/app/components/panda/core/admin/heading_component.html.erb +26 -0
- data/app/components/panda/core/admin/heading_component.rb +23 -60
- data/app/components/panda/core/admin/login_form_component.html.erb +38 -0
- data/app/components/panda/core/admin/login_form_component.rb +28 -0
- data/app/components/panda/core/admin/main_layout_component.html.erb +88 -0
- data/app/components/panda/core/admin/main_layout_component.rb +19 -0
- data/app/components/panda/core/admin/my_profile/connected_account_component.html.erb +41 -0
- data/app/components/panda/core/admin/my_profile/connected_account_component.rb +27 -0
- data/app/components/panda/core/admin/navigation/item_component.html.erb +34 -0
- data/app/components/panda/core/admin/navigation/item_component.rb +69 -0
- data/app/components/panda/core/admin/navigation/sub_item_component.html.erb +9 -0
- data/app/components/panda/core/admin/navigation/sub_item_component.rb +44 -0
- data/app/components/panda/core/admin/navigation/user_menu_component.html.erb +30 -0
- data/app/components/panda/core/admin/navigation/user_menu_component.rb +36 -0
- data/app/components/panda/core/admin/page_header_component.html.erb +28 -0
- data/app/components/panda/core/admin/page_header_component.rb +19 -35
- data/app/components/panda/core/admin/pagination_component.html.erb +33 -0
- data/app/components/panda/core/admin/pagination_component.rb +56 -0
- data/app/components/panda/core/admin/panel_component.html.erb +19 -0
- data/app/components/panda/core/admin/panel_component.rb +4 -29
- data/app/components/panda/core/admin/prose_panel_component.html.erb +6 -0
- data/app/components/panda/core/admin/prose_panel_component.rb +43 -0
- data/app/components/panda/core/admin/search_bar_component.html.erb +19 -0
- data/app/components/panda/core/admin/search_bar_component.rb +31 -0
- data/app/components/panda/core/admin/search_filter_bar_component.html.erb +22 -0
- data/app/components/panda/core/admin/search_filter_bar_component.rb +27 -0
- data/app/components/panda/core/admin/secret_field_component.html.erb +24 -0
- data/app/components/panda/core/admin/secret_field_component.rb +31 -0
- data/app/components/panda/core/admin/sidebar_component.html.erb +160 -0
- data/app/components/panda/core/admin/sidebar_component.rb +55 -0
- data/app/components/panda/core/admin/slideover_component.html.erb +67 -0
- data/app/components/panda/core/admin/slideover_component.rb +31 -70
- data/app/components/panda/core/admin/statistics_component.html.erb +4 -0
- data/app/components/panda/core/admin/statistics_component.rb +6 -8
- data/app/components/panda/core/admin/tab_bar_component.html.erb +48 -0
- data/app/components/panda/core/admin/tab_bar_component.rb +32 -67
- data/app/components/panda/core/admin/table_component.html.erb +82 -0
- data/app/components/panda/core/admin/table_component.rb +54 -87
- data/app/components/panda/core/admin/tag_badge_component.html.erb +7 -0
- data/app/components/panda/core/admin/tag_badge_component.rb +21 -0
- data/app/components/panda/core/admin/tag_component.html.erb +1 -0
- data/app/components/panda/core/admin/tag_component.rb +51 -20
- data/app/components/panda/core/admin/tag_input_component.html.erb +37 -0
- data/app/components/panda/core/admin/tag_input_component.rb +22 -0
- data/app/components/panda/core/admin/tags_panel_component.html.erb +8 -0
- data/app/components/panda/core/admin/tags_panel_component.rb +33 -0
- data/app/components/panda/core/admin/user_activity_component.html.erb +15 -0
- data/app/components/panda/core/admin/user_activity_component.rb +9 -22
- data/app/components/panda/core/admin/user_display_component.html.erb +8 -0
- data/app/components/panda/core/admin/user_display_component.rb +19 -22
- data/app/components/panda/core/base.rb +35 -42
- data/app/components/panda/core/shared/footer_component.html.erb +2 -0
- data/app/components/panda/core/shared/footer_component.rb +11 -0
- data/app/components/panda/core/shared/header_component.html.erb +32 -0
- data/app/components/panda/core/shared/header_component.rb +19 -0
- data/app/constraints/panda/core/admin_constraint.rb +2 -2
- data/app/controllers/concerns/panda/core/admin_authorization.rb +58 -0
- data/app/controllers/concerns/panda/core/authorizable.rb +107 -0
- data/app/controllers/panda/core/admin/base_controller.rb +62 -3
- data/app/controllers/panda/core/admin/editor/search_controller.rb +17 -0
- data/app/controllers/panda/core/admin/feature_flags_controller.rb +35 -0
- data/app/controllers/panda/core/admin/file_categories_controller.rb +84 -0
- data/app/controllers/panda/core/admin/files_controller.rb +230 -0
- data/app/controllers/panda/core/admin/import_sessions_controller.rb +120 -0
- data/app/controllers/panda/core/admin/my_profile/logins_controller.rb +24 -0
- data/app/controllers/panda/core/admin/my_profile_controller.rb +31 -0
- data/app/controllers/panda/core/admin/search_controller.rb +15 -0
- data/app/controllers/panda/core/admin/sessions_controller.rb +84 -10
- data/app/controllers/panda/core/admin/tags_controller.rb +77 -0
- data/app/controllers/panda/core/admin/test_sessions_controller.rb +5 -6
- data/app/controllers/panda/core/admin/users_controller.rb +184 -0
- data/app/controllers/panda/core/application_controller.rb +13 -1
- data/app/helpers/panda/core/active_storage_helper.rb +24 -0
- data/app/helpers/panda/core/feature_flag_helper.rb +11 -0
- data/app/helpers/panda/core/form_helper.rb +15 -0
- data/app/javascript/panda/core/controllers/avatar_upload_controller.js +73 -0
- data/app/javascript/panda/core/controllers/clipboard_controller.js +119 -0
- data/app/javascript/panda/core/controllers/collapsible_item_controller.js +45 -0
- data/app/javascript/panda/core/controllers/custom_select_controller.js +256 -0
- data/app/javascript/panda/core/controllers/datepicker_controller.js +121 -0
- data/app/javascript/panda/core/controllers/daterange_controller.js +193 -0
- data/app/javascript/panda/core/controllers/file_gallery_controller.js +134 -0
- data/app/javascript/panda/core/controllers/file_upload_controller.js +165 -0
- data/app/javascript/panda/core/controllers/global_search_controller.js +184 -0
- data/app/javascript/panda/core/controllers/index.js +36 -0
- data/app/javascript/panda/core/controllers/mobile_sidebar_controller.js +58 -0
- data/app/javascript/panda/core/controllers/navigation_toggle_controller.js +49 -24
- data/app/javascript/panda/core/controllers/row_link_controller.js +27 -0
- data/app/javascript/panda/core/controllers/tag_input_controller.js +183 -0
- data/app/javascript/panda/core/tailwindcss-stimulus-components.js +2 -3
- data/app/javascript/panda/core/vendor/@fortawesome--fontawesome-free@7.2.0.js +8129 -0
- data/app/javascript/panda/core/vendor/@hotwired--turbo.js +7197 -100
- data/app/javascript/panda/core/vendor/@rails--actioncable--src.js +511 -2
- data/app/javascript/panda/core/vendor/@tailwindplus--elements@1.0.22.js +65 -0
- data/app/javascript/panda/core/vendor/cropperjs@2.1.0.js +3 -0
- data/app/javascript/panda/core/vendor/vanilla-calendar-pro.js +2 -0
- data/app/models/concerns/panda/core/has_metadata.rb +164 -0
- data/app/models/concerns/panda/core/importable.rb +79 -0
- data/app/models/concerns/panda/core/searchable.rb +66 -0
- data/app/models/concerns/panda/core/taggable.rb +37 -0
- data/app/models/panda/core/feature_flag.rb +56 -0
- data/app/models/panda/core/file_categorization.rb +14 -0
- data/app/models/panda/core/file_category.rb +45 -0
- data/app/models/panda/core/import_session.rb +79 -0
- data/app/models/panda/core/presence.rb +24 -0
- data/app/models/panda/core/tag.rb +33 -0
- data/app/models/panda/core/tagging.rb +14 -0
- data/app/models/panda/core/user.rb +94 -16
- data/app/models/panda/core/user_activity.rb +42 -0
- data/app/models/panda/core/user_session.rb +35 -0
- data/app/services/panda/core/attach_avatar_service.rb +15 -3
- data/app/services/panda/core/csv_import_service.rb +65 -0
- data/app/services/panda/core/file_categorizer.rb +52 -0
- data/app/services/panda/core/file_parser.rb +76 -0
- data/app/services/panda/core/invite_user_service.rb +44 -0
- data/app/services/panda/core/presence_service.rb +49 -0
- data/app/views/layouts/panda/core/admin.html.erb +6 -76
- data/app/views/layouts/panda/core/admin_simple.html.erb +2 -2
- data/app/views/panda/core/admin/dashboard/_default_content.html.erb +1 -73
- data/app/views/panda/core/admin/dashboard/show.html.erb +12 -15
- data/app/views/panda/core/admin/feature_flags/index.html.erb +56 -0
- data/app/views/panda/core/admin/file_categories/_form.html.erb +20 -0
- data/app/views/panda/core/admin/file_categories/edit.html.erb +5 -0
- data/app/views/panda/core/admin/file_categories/index.html.erb +55 -0
- data/app/views/panda/core/admin/file_categories/new.html.erb +5 -0
- data/app/views/panda/core/admin/files/_file_details.html.erb +156 -0
- data/app/views/panda/core/admin/files/index.html.erb +61 -0
- data/app/views/panda/core/admin/import_sessions/column_map.html.erb +43 -0
- data/app/views/panda/core/admin/import_sessions/index.html.erb +48 -0
- data/app/views/panda/core/admin/import_sessions/new.html.erb +37 -0
- data/app/views/panda/core/admin/import_sessions/preview.html.erb +51 -0
- data/app/views/panda/core/admin/import_sessions/show.html.erb +87 -0
- data/app/views/panda/core/admin/my_profile/edit.html.erb +51 -39
- data/app/views/panda/core/admin/my_profile/logins/show.html.erb +72 -0
- data/app/views/panda/core/admin/sessions/new.html.erb +1 -37
- data/app/views/panda/core/admin/shared/_breadcrumbs.html.erb +1 -18
- data/app/views/panda/core/admin/shared/_sidebar.html.erb +1 -99
- data/app/views/panda/core/admin/shared/_slideover.html.erb +3 -3
- data/app/views/panda/core/admin/tags/_form.html.erb +21 -0
- data/app/views/panda/core/admin/tags/edit.html.erb +4 -0
- data/app/views/panda/core/admin/tags/index.html.erb +41 -0
- data/app/views/panda/core/admin/tags/new.html.erb +4 -0
- data/app/views/panda/core/admin/users/activity.html.erb +90 -0
- data/app/views/panda/core/admin/users/edit.html.erb +48 -0
- data/app/views/panda/core/admin/users/index.html.erb +161 -0
- data/app/views/panda/core/admin/users/sessions.html.erb +103 -0
- data/app/views/panda/core/admin/users/show.html.erb +224 -0
- data/app/views/panda/core/shared/_footer.html.erb +1 -2
- data/app/views/panda/core/shared/_header.html.erb +4 -22
- data/config/brakeman.ignore +68 -31
- data/config/importmap.rb +13 -11
- data/config/routes.rb +49 -2
- data/db/migrate/20260202171614_create_panda_core_file_categories.rb +30 -0
- data/db/migrate/20260203000001_add_user_management_fields_to_panda_core_users.rb +21 -0
- data/db/migrate/20260203000002_create_panda_core_user_activities.rb +24 -0
- data/db/migrate/20260203000003_create_panda_core_user_sessions.rb +25 -0
- data/db/migrate/20260207100000_create_panda_core_presences.rb +21 -0
- data/db/migrate/20260213000001_fix_active_storage_attachments_record_id_type.rb +44 -0
- data/db/migrate/20260217000001_create_panda_core_feature_flags.rb +14 -0
- data/db/migrate/20260303000001_add_metadata_to_panda_core_users.rb +11 -0
- data/db/migrate/20260310000001_create_panda_core_tags.rb +27 -0
- data/db/migrate/20260310000002_create_panda_core_import_sessions.rb +29 -0
- data/lib/generators/panda/core/install_generator.rb +32 -0
- data/lib/generators/panda/core/templates/panda.rb +63 -0
- data/lib/panda/core/asset_loader.rb +5 -2
- data/lib/panda/core/authentication.rb +3 -3
- data/lib/panda/core/configuration.rb +195 -1
- data/lib/panda/core/engine/admin_controller_config.rb +3 -6
- data/lib/panda/core/engine/autoload_config.rb +21 -9
- data/lib/panda/core/engine/omniauth_config.rb +108 -38
- data/lib/panda/core/engine/pending_migrations_check.rb +46 -0
- data/lib/panda/core/engine/sqlite_schema_compatibility.rb +36 -0
- data/lib/panda/core/engine/{phlex_config.rb → view_component_config.rb} +5 -7
- data/lib/panda/core/engine.rb +90 -25
- data/lib/panda/core/middleware.rb +35 -0
- data/lib/panda/core/navigation_registry.rb +455 -0
- data/lib/panda/core/oauth_providers.rb +27 -0
- data/lib/panda/core/permission_registry.rb +89 -0
- data/lib/panda/core/search_registry.rb +69 -0
- data/lib/panda/core/seeds/file_categories.rb +30 -0
- data/lib/panda/core/shared/inflections_config.rb +1 -5
- data/lib/panda/core/testing/rails_helper.rb +87 -0
- data/lib/panda/core/testing/support/authentication_test_helpers.rb +22 -18
- data/lib/panda/core/testing/support/system/cuprite_helpers.rb +4 -4
- data/lib/panda/core/testing/view_component_test_controller.rb +19 -0
- data/lib/panda/core/version.rb +1 -1
- data/lib/panda/core/widget_registry.rb +50 -0
- data/lib/panda/core.rb +17 -8
- data/lib/tasks/panda/core/file_categories.rake +41 -0
- data/public/panda-core-assets/panda-core-0.12.5.css +2 -0
- data/public/panda-core-assets/panda-core-0.13.0.css +2 -0
- data/public/panda-core-assets/panda-core-0.14.0.css +2 -0
- data/public/panda-core-assets/panda-core-0.14.4.css +2 -0
- data/public/panda-core-assets/panda-core.css +2 -2
- data/public/panda-core-assets/vanilla-calendar.css +47 -0
- metadata +203 -26
- data/lib/panda/core/engine/inflections_config.rb +0 -23
|
@@ -14,6 +14,9 @@ module Panda
|
|
|
14
14
|
:mailer_default_url_options,
|
|
15
15
|
:session_token_cookie,
|
|
16
16
|
:authentication_providers,
|
|
17
|
+
:authentication_provider_resolver,
|
|
18
|
+
:authentication_provider_gate,
|
|
19
|
+
:authentication_validator,
|
|
17
20
|
:admin_path,
|
|
18
21
|
:admin_navigation_items,
|
|
19
22
|
:admin_dashboard_widgets,
|
|
@@ -26,13 +29,28 @@ module Panda
|
|
|
26
29
|
:login_logo_path,
|
|
27
30
|
:login_page_title,
|
|
28
31
|
:admin_title,
|
|
32
|
+
:admin_logo,
|
|
33
|
+
:admin_settings_path,
|
|
34
|
+
:admin_logo_actions,
|
|
35
|
+
:admin_sidebar_footer,
|
|
29
36
|
:initial_admin_breadcrumb,
|
|
30
37
|
:dashboard_redirect_path,
|
|
31
38
|
:avatar_variants,
|
|
32
39
|
:avatar_max_file_size,
|
|
33
40
|
:avatar_max_dimension,
|
|
34
41
|
:avatar_optimization_quality,
|
|
35
|
-
:avatar_image_processor
|
|
42
|
+
:avatar_image_processor,
|
|
43
|
+
:admin_user_menu_items,
|
|
44
|
+
:additional_head_content,
|
|
45
|
+
:compact_navigation,
|
|
46
|
+
:admin_user_edit_content,
|
|
47
|
+
:admin_user_after_update,
|
|
48
|
+
:admin_user_show_content,
|
|
49
|
+
:admin_user_index_columns,
|
|
50
|
+
:restrict_user_creation,
|
|
51
|
+
:after_user_invited,
|
|
52
|
+
:invite_form_content,
|
|
53
|
+
:post_authentication_redirect
|
|
36
54
|
|
|
37
55
|
def initialize
|
|
38
56
|
@user_class = "Panda::Core::User"
|
|
@@ -46,6 +64,9 @@ module Panda
|
|
|
46
64
|
@mailer_default_url_options = {host: "localhost:3000"}
|
|
47
65
|
@session_token_cookie = :panda_session
|
|
48
66
|
@authentication_providers = {}
|
|
67
|
+
@authentication_provider_resolver = nil
|
|
68
|
+
@authentication_provider_gate = nil
|
|
69
|
+
@authentication_validator = nil
|
|
49
70
|
@admin_path = "/admin"
|
|
50
71
|
@default_theme = "default"
|
|
51
72
|
|
|
@@ -74,6 +95,14 @@ module Panda
|
|
|
74
95
|
icon: "fa-solid fa-user"
|
|
75
96
|
}
|
|
76
97
|
|
|
98
|
+
items << {
|
|
99
|
+
label: "Settings",
|
|
100
|
+
icon: "fa-solid fa-gear",
|
|
101
|
+
children: [
|
|
102
|
+
{label: "Feature Flags", path: "#{@admin_path}/feature_flags"}
|
|
103
|
+
]
|
|
104
|
+
}
|
|
105
|
+
|
|
77
106
|
items
|
|
78
107
|
}
|
|
79
108
|
@admin_dashboard_widgets = ->(user) { [] }
|
|
@@ -83,12 +112,24 @@ module Panda
|
|
|
83
112
|
|
|
84
113
|
# Profile and UI customization
|
|
85
114
|
@additional_user_params = []
|
|
115
|
+
# Themes offered in the My Profile dropdown as [label, value] pairs.
|
|
116
|
+
# Assigning this REPLACES the default list — host apps registering
|
|
117
|
+
# their own theme should include any built-ins they still want, e.g.
|
|
118
|
+
# config.available_themes = [["Default", "default"], ["Acme", "acme"]]
|
|
119
|
+
# The value is stamped as <html data-theme="..."> on admin pages;
|
|
120
|
+
# supply the matching html[data-theme='...'] variable block via
|
|
121
|
+
# config.additional_head_content (see app/assets/tailwind/application.css).
|
|
86
122
|
@available_themes = [["Default", "default"], ["Sky", "sky"]]
|
|
87
123
|
@login_logo_path = nil
|
|
88
124
|
@login_page_title = "Panda Admin"
|
|
89
125
|
@admin_title = "Panda Admin"
|
|
126
|
+
@admin_logo = nil # Proc/lambda returning HTML for sidebar logo area (e.g. SVG + title)
|
|
127
|
+
@admin_settings_path = nil # Path for the settings gear icon in the logo area (nil hides it)
|
|
128
|
+
@admin_logo_actions = [] # Array of {path:, icon:, title:} hashes rendered as icon links next to the settings gear
|
|
129
|
+
@admin_sidebar_footer = nil # Proc/lambda returning HTML for the bottom of the sidebar (replaces version text)
|
|
90
130
|
@initial_admin_breadcrumb = nil # Proc that returns [label, path]
|
|
91
131
|
@dashboard_redirect_path = nil # Path to redirect to after login (defaults to admin_root_path)
|
|
132
|
+
@additional_head_content = nil # Proc/lambda returning HTML to inject into <head> (e.g. theme stylesheets, fonts)
|
|
92
133
|
|
|
93
134
|
# Avatar configuration
|
|
94
135
|
@avatar_variants = {
|
|
@@ -101,6 +142,159 @@ module Panda
|
|
|
101
142
|
@avatar_max_dimension = 800
|
|
102
143
|
@avatar_optimization_quality = 85
|
|
103
144
|
@avatar_image_processor = :vips # or :mini_magick
|
|
145
|
+
|
|
146
|
+
# Navigation density — false (spacious, default) or true (compact, for apps with many nav items)
|
|
147
|
+
@compact_navigation = false
|
|
148
|
+
|
|
149
|
+
# User management hooks — Procs that host apps set to inject content into user views/callbacks
|
|
150
|
+
@admin_user_edit_content = nil # Proc(user, form_builder, view_context) → HTML
|
|
151
|
+
@admin_user_after_update = nil # Proc(user, params, current_user)
|
|
152
|
+
@admin_user_show_content = nil # Proc(user, view_context) → HTML
|
|
153
|
+
@admin_user_index_columns = nil # Proc(user, view_context) → HTML
|
|
154
|
+
|
|
155
|
+
# Authentication restriction — when truthy, find_or_create_from_auth_hash
|
|
156
|
+
# will NOT create new User records for unknown emails on OAuth login.
|
|
157
|
+
# Accepts a boolean or a callable (receives auth_hash, returns truthy to restrict).
|
|
158
|
+
@restrict_user_creation = false
|
|
159
|
+
|
|
160
|
+
# Invitation hooks — host app can extend the invite flow
|
|
161
|
+
@after_user_invited = nil # Proc(user, params, current_user) — called after successful invite
|
|
162
|
+
@invite_form_content = nil # Proc(form_builder, view_context) → HTML — extra fields in invite form
|
|
163
|
+
|
|
164
|
+
# Post-authentication redirect — Proc(user, request) → URL string or nil.
|
|
165
|
+
# Called after successful OAuth login. Return a URL to redirect there,
|
|
166
|
+
# or nil to fall through to the default redirect logic.
|
|
167
|
+
@post_authentication_redirect = nil
|
|
168
|
+
|
|
169
|
+
# Legacy extensible user menu items (prefer NavigationRegistry with position: :bottom)
|
|
170
|
+
@admin_user_menu_items = []
|
|
171
|
+
|
|
172
|
+
# Register default user menu as a bottom navigation section (idempotent)
|
|
173
|
+
register_default_user_menu
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Returns the authentication providers for the given request.
|
|
177
|
+
# If an +authentication_provider_resolver+ proc is configured it is
|
|
178
|
+
# called with the request to allow per-tenant provider filtering.
|
|
179
|
+
# Otherwise the static +authentication_providers+ hash is returned.
|
|
180
|
+
def resolved_authentication_providers(request = nil)
|
|
181
|
+
if authentication_provider_resolver && request
|
|
182
|
+
authentication_provider_resolver.call(request)
|
|
183
|
+
else
|
|
184
|
+
authentication_providers
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Register a new admin navigation section via {NavigationRegistry}.
|
|
189
|
+
#
|
|
190
|
+
# Sections with the same label as an existing base section are skipped.
|
|
191
|
+
# Use +after:+ or +before:+ to control positioning relative to other sections.
|
|
192
|
+
#
|
|
193
|
+
# @param label [String] Section label displayed in the sidebar
|
|
194
|
+
# @param icon [String] FontAwesome icon class (e.g. "fa-solid fa-users")
|
|
195
|
+
# @param after [String, nil] Insert after the section with this label
|
|
196
|
+
# @param before [String, nil] Insert before the section with this label
|
|
197
|
+
# @param visible [Proc, nil] Lambda receiving user, hides section when false
|
|
198
|
+
# @param permission [Symbol, nil] Permission key — hides section unless user is authorized
|
|
199
|
+
# @param position [Symbol] :top (default) or :bottom
|
|
200
|
+
# @yield [NavigationRegistry::SectionContext] Optional block for adding items
|
|
201
|
+
#
|
|
202
|
+
# @example Add a section with items
|
|
203
|
+
# config.insert_admin_menu_section "Members",
|
|
204
|
+
# icon: "fa-solid fa-users",
|
|
205
|
+
# after: "Website" do |section|
|
|
206
|
+
# section.item "Onboarding", path: "members/onboarding"
|
|
207
|
+
# end
|
|
208
|
+
#
|
|
209
|
+
# @see NavigationRegistry.section
|
|
210
|
+
def insert_admin_menu_section(label, icon: nil, after: nil, before: nil, visible: nil, permission: nil, position: :top, &block)
|
|
211
|
+
Panda::Core::NavigationRegistry.section(label, icon: icon, after: after, before: before, visible: visible, permission: permission, position: position, &block)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Register an item to be appended to an existing admin navigation section.
|
|
215
|
+
#
|
|
216
|
+
# If the target section doesn't exist at build time, the item is silently
|
|
217
|
+
# skipped. Use +path:+ for admin-relative paths or +url:+ for absolute URLs.
|
|
218
|
+
#
|
|
219
|
+
# @param label [String] Item label displayed in the sidebar
|
|
220
|
+
# @param section [String] Label of the target section to add to
|
|
221
|
+
# @param path [String, nil] Relative path (auto-prefixed with admin_path)
|
|
222
|
+
# @param url [String, nil] Absolute URL (used as-is, no prefixing)
|
|
223
|
+
# @param target [String, nil] HTML target attribute (e.g. "_blank")
|
|
224
|
+
# @param visible [Proc, nil] Lambda receiving user, hides item when false
|
|
225
|
+
# @param before [Symbol, String, nil] :all or label — position within section
|
|
226
|
+
# @param after [Symbol, String, nil] :all or label — position within section
|
|
227
|
+
# @param method [Symbol, nil] HTTP method (e.g. :delete)
|
|
228
|
+
# @param button_options [Hash] Extra options for button_to
|
|
229
|
+
# @param path_helper [Symbol, nil] Route helper resolved at build time
|
|
230
|
+
#
|
|
231
|
+
# @example Add to an existing section
|
|
232
|
+
# config.insert_admin_menu_item "Feature Flags",
|
|
233
|
+
# section: "Settings",
|
|
234
|
+
# path: "feature_flags"
|
|
235
|
+
#
|
|
236
|
+
# @example Permission-gated item
|
|
237
|
+
# config.insert_admin_menu_item "Suggestions",
|
|
238
|
+
# section: "Tools",
|
|
239
|
+
# path: "cms/content_suggestions",
|
|
240
|
+
# permission: :review_content
|
|
241
|
+
#
|
|
242
|
+
# @see NavigationRegistry.item
|
|
243
|
+
# rubocop:disable Metrics/ParameterLists
|
|
244
|
+
def insert_admin_menu_item(label, section:, path: nil, url: nil, target: nil,
|
|
245
|
+
visible: nil, permission: nil, before: nil, after: nil, method: nil, button_options: {}, path_helper: nil)
|
|
246
|
+
Panda::Core::NavigationRegistry.item(
|
|
247
|
+
label, section: section, path: path, url: url, target: target,
|
|
248
|
+
visible: visible, permission: permission, before: before, after: after,
|
|
249
|
+
method: method, button_options: button_options, path_helper: path_helper
|
|
250
|
+
)
|
|
251
|
+
end
|
|
252
|
+
# rubocop:enable Metrics/ParameterLists
|
|
253
|
+
|
|
254
|
+
# Convenience: add an item to the user menu (bottom section).
|
|
255
|
+
# @param label [String] Item label
|
|
256
|
+
# @param path [String, nil] Path (auto-prefixed with admin_path)
|
|
257
|
+
# @param url [String, nil] Full URL (used as-is)
|
|
258
|
+
# @param visible [Proc, nil] Lambda receiving user, hides item when false
|
|
259
|
+
# @param position [Symbol] :before_logout (default) or :top
|
|
260
|
+
def insert_admin_user_menu_item(label, path: nil, url: nil, visible: nil, position: :before_logout)
|
|
261
|
+
before_opt = (position == :before_logout) ? "Logout" : nil
|
|
262
|
+
before_opt = :all if position == :top
|
|
263
|
+
Panda::Core::NavigationRegistry.item(
|
|
264
|
+
label, section: "My Account", path: path, url: url,
|
|
265
|
+
visible: visible, before: before_opt
|
|
266
|
+
)
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# Register a post-build filter that conditionally hides items by label.
|
|
270
|
+
# @param label [String] Label to match
|
|
271
|
+
# @param visible [Proc] Lambda receiving user — item hidden when false
|
|
272
|
+
def filter_admin_menu(label, visible:)
|
|
273
|
+
Panda::Core::NavigationRegistry.filter(label, visible: visible)
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Register a dashboard widget via WidgetRegistry.
|
|
277
|
+
# @param label [String] Widget label
|
|
278
|
+
# @param component [Proc] Lambda receiving user, returns a component instance
|
|
279
|
+
# @param visible [Proc, nil] Lambda receiving user, hides widget when false
|
|
280
|
+
# @param position [Integer] Sort order (lower first)
|
|
281
|
+
def register_admin_dashboard_widget(label, component:, visible: nil, position: 0)
|
|
282
|
+
Panda::Core::WidgetRegistry.register(label, component: component, visible: visible, position: position)
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
private
|
|
286
|
+
|
|
287
|
+
# Register the default user menu items as a bottom navigation section.
|
|
288
|
+
# Idempotent: skips if "My Account" is already registered.
|
|
289
|
+
def register_default_user_menu
|
|
290
|
+
return if Panda::Core::NavigationRegistry.sections.any? { |s| s[:label] == "My Account" }
|
|
291
|
+
|
|
292
|
+
Panda::Core::NavigationRegistry.section("My Account", position: :bottom) do |s|
|
|
293
|
+
s.item "My Profile", path: "my_profile/edit"
|
|
294
|
+
s.item "Login & Security", path: "my_profile/logins"
|
|
295
|
+
s.item "Logout", path_helper: :admin_logout_path, method: :delete,
|
|
296
|
+
button_options: {id: "logout-link", data: {turbo: false}}
|
|
297
|
+
end
|
|
104
298
|
end
|
|
105
299
|
end
|
|
106
300
|
|
|
@@ -11,12 +11,9 @@ module Panda
|
|
|
11
11
|
# Create AdminController alias after controllers are loaded
|
|
12
12
|
# This allows other gems to inherit from Panda::Core::AdminController
|
|
13
13
|
config.to_prepare do
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
unless Panda::Core.const_defined?(:AdminController)
|
|
18
|
-
Panda::Core.const_set(:AdminController, Panda::Core::Admin::BaseController)
|
|
19
|
-
end
|
|
14
|
+
unless Panda::Core.const_defined?(:AdminController)
|
|
15
|
+
admin_base = "Panda::Core::Admin::BaseController".safe_constantize
|
|
16
|
+
Panda::Core.const_set(:AdminController, admin_base) if admin_base
|
|
20
17
|
end
|
|
21
18
|
end
|
|
22
19
|
end
|
|
@@ -6,17 +6,29 @@ module Panda
|
|
|
6
6
|
module AutoloadConfig
|
|
7
7
|
extend ActiveSupport::Concern
|
|
8
8
|
|
|
9
|
+
# Custom autoload paths for panda-core
|
|
10
|
+
# Note: Must be Strings (not Pathnames) for Rails 8.1.2+ compatibility
|
|
11
|
+
AUTOLOAD_DIRECTORIES = %w[
|
|
12
|
+
app/builders
|
|
13
|
+
app/components
|
|
14
|
+
app/services
|
|
15
|
+
app/models
|
|
16
|
+
app/helpers
|
|
17
|
+
app/constraints
|
|
18
|
+
].freeze
|
|
19
|
+
|
|
9
20
|
included do
|
|
10
|
-
#
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
config.autoload_paths << root.join("app/constraints")
|
|
21
|
+
# Use initializer with before: to ensure paths are added before
|
|
22
|
+
# Rails freezes the autoload_paths array in Rails 8.1.2+
|
|
23
|
+
initializer "panda_core.set_autoload_paths", before: :set_autoload_paths do |app|
|
|
24
|
+
AUTOLOAD_DIRECTORIES.each do |dir|
|
|
25
|
+
path = root.join(dir).to_s
|
|
26
|
+
next unless File.directory?(path)
|
|
17
27
|
|
|
18
|
-
|
|
19
|
-
|
|
28
|
+
app.config.autoload_paths << path unless app.config.autoload_paths.include?(path)
|
|
29
|
+
app.config.eager_load_paths << path unless app.config.eager_load_paths.include?(path)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
20
32
|
end
|
|
21
33
|
end
|
|
22
34
|
end
|
|
@@ -26,27 +26,42 @@ module Panda
|
|
|
26
26
|
"developer" => :developer
|
|
27
27
|
}.freeze
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
29
|
+
class_methods do
|
|
30
|
+
# Load YAML provider overrides during engine definition (before middleware setup)
|
|
31
|
+
def load_yaml_provider_overrides_early!
|
|
32
|
+
path = Panda::Core::Engine.root.join("config/providers.yml")
|
|
33
|
+
return unless File.exist?(path)
|
|
34
|
+
|
|
35
|
+
yaml = YAML.load_file(path) || {}
|
|
36
|
+
(yaml["providers"] || {}).each do |name, settings|
|
|
37
|
+
Panda::Core.config.authentication_providers[name.to_s] ||= {}
|
|
38
|
+
Panda::Core.config.authentication_providers[name.to_s].deep_merge!(settings)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
37
41
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
# Configure OmniAuth globals
|
|
43
|
+
def configure_omniauth_globals
|
|
44
|
+
OmniAuth.configure do |c|
|
|
45
|
+
c.allowed_request_methods = [:post]
|
|
46
|
+
c.path_prefix = "#{Panda::Core.config.admin_path}/auth"
|
|
47
|
+
|
|
48
|
+
# OmniAuth's built-in AuthenticityTokenProtection uses
|
|
49
|
+
# Rack::Protection's :csrf session key, but Rails stores its
|
|
50
|
+
# CSRF token under :_csrf_token. This session key mismatch
|
|
51
|
+
# causes "Forbidden" errors when submitting the admin login form.
|
|
52
|
+
#
|
|
53
|
+
# Disabling OmniAuth's request_validation_phase is safe because:
|
|
54
|
+
# - Production OAuth providers (Google, GitHub, Microsoft) are
|
|
55
|
+
# protected by the OAuth state parameter in the callback phase
|
|
56
|
+
# - The developer provider only runs in development/test
|
|
57
|
+
# - The login form still requires POST (allowed_request_methods)
|
|
58
|
+
c.request_validation_phase = nil
|
|
43
59
|
end
|
|
44
60
|
end
|
|
45
61
|
end
|
|
46
62
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
# 1. YAML overrides
|
|
63
|
+
# Instance method for loading YAML provider overrides (for testing)
|
|
64
|
+
# @deprecated Use class method load_yaml_provider_overrides_early! instead
|
|
50
65
|
def load_yaml_provider_overrides!
|
|
51
66
|
path = Panda::Core::Engine.root.join("config/providers.yml")
|
|
52
67
|
return unless File.exist?(path)
|
|
@@ -58,31 +73,15 @@ module Panda
|
|
|
58
73
|
end
|
|
59
74
|
end
|
|
60
75
|
|
|
61
|
-
#
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
c.path_prefix = "#{Panda::Core.config.admin_path}/auth"
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
# 3. Middleware insertion
|
|
70
|
-
def mount_omniauth_middleware(app)
|
|
71
|
-
ctx = self # Capture the Engine/Concern context
|
|
72
|
-
|
|
73
|
-
Panda::Core::Middleware.use(app, OmniAuth::Builder) do
|
|
74
|
-
Panda::Core.config.authentication_providers.each do |name, settings|
|
|
75
|
-
ctx.send(:configure_provider, self, name, settings)
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
# 4. Provider builder
|
|
76
|
+
# Configure a single provider on the OmniAuth builder
|
|
77
|
+
# @param builder [OmniAuth::Builder] the OmniAuth builder instance
|
|
78
|
+
# @param name [String] the provider name (may be an alias)
|
|
79
|
+
# @param settings [Hash] provider configuration
|
|
81
80
|
def configure_provider(builder, name, settings)
|
|
82
81
|
symbol = PROVIDER_REGISTRY[name.to_s]
|
|
83
82
|
|
|
84
83
|
unless symbol
|
|
85
|
-
Rails.logger.warn("[panda-core] Unknown OmniAuth provider: #{name.inspect}")
|
|
84
|
+
Rails.logger.warn("[panda-core] Unknown OmniAuth provider: #{name.inspect}") if defined?(Rails.logger)
|
|
86
85
|
return
|
|
87
86
|
end
|
|
88
87
|
|
|
@@ -91,7 +90,7 @@ module Panda
|
|
|
91
90
|
# Skip providers without credentials (except developer which doesn't need them)
|
|
92
91
|
has_credentials = settings[:client_id].present? && settings[:client_secret].present?
|
|
93
92
|
if symbol != :developer && !has_credentials
|
|
94
|
-
Rails.logger.info("[panda-core] Skipping OmniAuth provider #{name.inspect}: missing client_id or client_secret")
|
|
93
|
+
Rails.logger.info("[panda-core] Skipping OmniAuth provider #{name.inspect}: missing client_id or client_secret") if defined?(Rails.logger)
|
|
95
94
|
return
|
|
96
95
|
end
|
|
97
96
|
|
|
@@ -104,6 +103,77 @@ module Panda
|
|
|
104
103
|
builder.provider symbol, options
|
|
105
104
|
end
|
|
106
105
|
end
|
|
106
|
+
|
|
107
|
+
included do
|
|
108
|
+
# Only run Rails Engine-specific code when included into an actual Engine
|
|
109
|
+
# This prevents errors when the module is included into test dummy classes
|
|
110
|
+
next unless self < ::Rails::Engine
|
|
111
|
+
|
|
112
|
+
# Load YAML overrides early during engine definition so they're available
|
|
113
|
+
# when the OmniAuth middleware block is evaluated
|
|
114
|
+
load_yaml_provider_overrides_early!
|
|
115
|
+
|
|
116
|
+
if respond_to?(:initializer)
|
|
117
|
+
# Set up OAuth providers
|
|
118
|
+
config.before_initialize do |app|
|
|
119
|
+
require_relative "../oauth_providers"
|
|
120
|
+
Panda::Core::OAuthProviders.setup
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Configure OmniAuth globals AFTER all initializers have run
|
|
124
|
+
# This ensures Panda::Core.config.admin_path has been set by the app
|
|
125
|
+
initializer "panda_core.omniauth_globals" do |app|
|
|
126
|
+
app.config.after_initialize do
|
|
127
|
+
Panda::Core::Engine.configure_omniauth_globals
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Add OmniAuth middleware during engine configuration
|
|
133
|
+
# Rails 8.1.2+ freezes the middleware stack before initializers run
|
|
134
|
+
config.app_middleware.use OmniAuth::Builder do
|
|
135
|
+
# Capture the builder instance for provider configuration
|
|
136
|
+
builder = self
|
|
137
|
+
Panda::Core.config.authentication_providers.each do |name, settings|
|
|
138
|
+
symbol = PROVIDER_REGISTRY[name.to_s]
|
|
139
|
+
|
|
140
|
+
unless symbol
|
|
141
|
+
Rails.logger.warn("[panda-core] Unknown OmniAuth provider: #{name.inspect}") if defined?(Rails.logger)
|
|
142
|
+
next
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
next if symbol == :developer && !Rails.env.development?
|
|
146
|
+
|
|
147
|
+
# Skip providers without credentials (except developer which doesn't need them)
|
|
148
|
+
has_credentials = settings[:client_id].present? && settings[:client_secret].present?
|
|
149
|
+
if symbol != :developer && !has_credentials
|
|
150
|
+
Rails.logger.info("[panda-core] Skipping OmniAuth provider #{name.inspect}: missing client_id or client_secret") if defined?(Rails.logger)
|
|
151
|
+
next
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
options = (settings[:options] || {}).dup
|
|
155
|
+
options[:name] = settings[:path_name] if settings[:path_name].present?
|
|
156
|
+
|
|
157
|
+
# Inject a setup lambda that gates providers per-request (e.g. per-tenant)
|
|
158
|
+
# and allows dynamic redirect_uri override for multi-subdomain OAuth flows.
|
|
159
|
+
gate = Panda::Core.config.authentication_provider_gate
|
|
160
|
+
if gate && symbol != :developer
|
|
161
|
+
provider_name = name.to_s
|
|
162
|
+
options[:setup] = ->(env) {
|
|
163
|
+
unless gate.call(provider_name, env)
|
|
164
|
+
raise OmniAuth::Error, "Provider not enabled for this workspace"
|
|
165
|
+
end
|
|
166
|
+
}
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
if settings[:client_id] && settings[:client_secret]
|
|
170
|
+
builder.provider symbol, settings[:client_id], settings[:client_secret], options
|
|
171
|
+
else
|
|
172
|
+
builder.provider symbol, options
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
107
177
|
end
|
|
108
178
|
end
|
|
109
179
|
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Panda
|
|
4
|
+
module Core
|
|
5
|
+
class Engine < ::Rails::Engine
|
|
6
|
+
module PendingMigrationsCheck
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
included do
|
|
10
|
+
initializer "panda_core.pending_migrations_check", after: :load_config_initializers do
|
|
11
|
+
config.after_initialize do
|
|
12
|
+
next unless Rails.env.local?
|
|
13
|
+
|
|
14
|
+
check_for_pending_panda_migrations
|
|
15
|
+
rescue ActiveRecord::NoDatabaseError, ActiveRecord::ConnectionNotEstablished, NoMethodError
|
|
16
|
+
# Skip when DB isn't available (asset precompilation, etc.)
|
|
17
|
+
# or when migration API differs across Rails versions
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def check_for_pending_panda_migrations
|
|
25
|
+
paths = ActiveRecord::Migrator.migrations_paths
|
|
26
|
+
context = ActiveRecord::MigrationContext.new(paths)
|
|
27
|
+
return unless context.needs_migration?
|
|
28
|
+
|
|
29
|
+
pending = context.open.pending_migrations
|
|
30
|
+
panda_pending = pending.select { |m| m.filename.match?(/panda/i) }
|
|
31
|
+
return if panda_pending.empty?
|
|
32
|
+
|
|
33
|
+
install_tasks = Panda::Core::ModuleRegistry.modules.keys
|
|
34
|
+
.unshift("panda-core").uniq
|
|
35
|
+
.map { |gem_name| "#{gem_name.tr("-", "_")}:install:migrations" }
|
|
36
|
+
.join(" ")
|
|
37
|
+
|
|
38
|
+
Rails.logger.warn(
|
|
39
|
+
"\n⚠️ #{panda_pending.size} pending Panda migration(s) detected.\n" \
|
|
40
|
+
" Run: bin/rails #{install_tasks} db:migrate\n"
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Panda
|
|
4
|
+
module Core
|
|
5
|
+
module SqliteSchemaCompatibility
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
initializer "panda_core.sqlite_schema_compatibility" do
|
|
10
|
+
next unless ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).any? { |cfg| cfg.adapter == "sqlite3" }
|
|
11
|
+
|
|
12
|
+
require "active_record/connection_adapters/sqlite3_adapter"
|
|
13
|
+
|
|
14
|
+
sqlite_adapter = ActiveRecord::ConnectionAdapters::SQLite3Adapter
|
|
15
|
+
unless sqlite_adapter.singleton_class.method_defined?(:native_database_types_with_panda_sqlite_compatibility)
|
|
16
|
+
sqlite_adapter.singleton_class.prepend(Module.new do
|
|
17
|
+
def native_database_types_with_panda_sqlite_compatibility
|
|
18
|
+
super.merge(
|
|
19
|
+
uuid: {name: "varchar"},
|
|
20
|
+
jsonb: {name: "json"}
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
alias_method :native_database_types, :native_database_types_with_panda_sqlite_compatibility
|
|
24
|
+
end)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
table_definition = ActiveRecord::ConnectionAdapters::SQLite3::TableDefinition
|
|
28
|
+
# Rails' SQLite TableDefinition intentionally omits these helpers, but our
|
|
29
|
+
# cross-database migrations/schema use them. We define them once at boot.
|
|
30
|
+
table_definition.send(:define_column_methods, :uuid) unless table_definition.method_defined?(:uuid)
|
|
31
|
+
table_definition.send(:define_column_methods, :jsonb) unless table_definition.method_defined?(:jsonb)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
module Panda
|
|
4
4
|
module Core
|
|
5
5
|
class Engine < ::Rails::Engine
|
|
6
|
-
#
|
|
7
|
-
module
|
|
6
|
+
# ViewComponent configuration
|
|
7
|
+
module ViewComponentConfig
|
|
8
8
|
extend ActiveSupport::Concern
|
|
9
9
|
|
|
10
10
|
included do
|
|
@@ -26,12 +26,10 @@ module Panda
|
|
|
26
26
|
end
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
# Load
|
|
29
|
+
# Load ViewComponent base component after Rails application is initialized
|
|
30
30
|
# This ensures Rails.application.routes is available
|
|
31
|
-
initializer "panda_core.
|
|
32
|
-
require "
|
|
33
|
-
require "phlex-rails"
|
|
34
|
-
require "literal"
|
|
31
|
+
initializer "panda_core.view_component_base", after: :load_config_initializers do
|
|
32
|
+
require "view_component"
|
|
35
33
|
require "tailwind_merge"
|
|
36
34
|
|
|
37
35
|
# Load the base component
|