panda-core 0.10.7 → 0.12.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +35 -0
  3. data/app/assets/tailwind/application.css +5 -0
  4. data/app/models/concerns/panda/core/has_uuid.rb +24 -0
  5. data/app/models/panda/core/user.rb +22 -19
  6. data/config/brakeman.ignore +51 -0
  7. data/db/migrate/20250809000001_create_panda_core_users.rb +14 -9
  8. data/db/migrate/20251203100000_rename_is_admin_to_admin_in_panda_core_users.rb +18 -0
  9. data/lib/panda/core/configuration.rb +4 -0
  10. data/lib/panda/core/engine/autoload_config.rb +9 -10
  11. data/lib/panda/core/engine/omniauth_config.rb +82 -36
  12. data/lib/panda/core/engine/route_config.rb +37 -0
  13. data/lib/panda/core/engine.rb +46 -41
  14. data/lib/panda/core/middleware.rb +146 -0
  15. data/lib/panda/core/shared/inflections_config.rb +1 -0
  16. data/lib/panda/core/testing/rails_helper.rb +17 -8
  17. data/lib/panda/core/testing/support/authentication_helpers.rb +6 -6
  18. data/lib/panda/core/testing/support/authentication_test_helpers.rb +2 -12
  19. data/lib/panda/core/testing/support/system/browser_console_logger.rb +1 -2
  20. data/lib/panda/core/testing/support/system/chrome_path.rb +38 -0
  21. data/lib/panda/core/testing/support/system/cuprite_helpers.rb +79 -0
  22. data/lib/panda/core/testing/support/system/cuprite_setup.rb +61 -66
  23. data/lib/panda/core/testing/support/system/system_test_helpers.rb +11 -11
  24. data/lib/panda/core/version.rb +1 -1
  25. data/lib/tasks/panda/core/users.rake +3 -3
  26. data/lib/tasks/panda/shared.rake +31 -5
  27. data/public/panda-core-assets/favicons/browserconfig.xml +1 -1
  28. data/public/panda-core-assets/favicons/site.webmanifest +1 -1
  29. data/public/panda-core-assets/panda-core-0.11.0.css +2 -0
  30. data/public/panda-core-assets/panda-core.css +1 -1
  31. metadata +9 -9
  32. data/db/migrate/20250810120000_add_current_theme_to_panda_core_users.rb +0 -7
  33. data/db/migrate/20250811120000_add_oauth_avatar_url_to_panda_core_users.rb +0 -7
  34. data/lib/panda/core/engine/middleware_config.rb +0 -17
  35. data/lib/panda/core/testing/support/system/better_system_tests.rb +0 -180
  36. data/lib/panda/core/testing/support/system/capybara_config.rb +0 -64
  37. data/lib/panda/core/testing/support/system/ci_capybara_config.rb +0 -77
  38. data/public/panda-core-assets/panda-core-0.10.6.css +0 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 257bc260935111857f38b3210d637923ae7708fa0d2d0c63f6b3fc06537c3da9
4
- data.tar.gz: abb6a750127038e8ef17c70c7a30e41e045f1774593fae9d41196c21aaecc0b9
3
+ metadata.gz: 1865844f240222fd1894bae360505e3954858ac363b49d4bd0aa59bb8699fdcc
4
+ data.tar.gz: a85741affaa234bd9899c670da3b7a4eab5c7aa65bf09ba89081b49606ecbf66
5
5
  SHA512:
6
- metadata.gz: 5a7e4a43e151b6092209e5853a6a84b047296db826973cf8d4cd130fe570af28b8beaca21c5071b280dcb818384fe4ea7520a0a9a6d2c5a4e98fe6a8853f658a
7
- data.tar.gz: 19e87a071db0442ce9dbd40b9815de8811f9c87d2873e77603d4041b9fbbdd19165f1ee0e605ba36065eefd66dcfb1cc846c096eb0d77f49b554f071420f6827
6
+ metadata.gz: a873452e2589d9fc7e4254f7ef81b4bb8cbccc2a57fd19d17f95aed22609d1226711579fe905b5caf269fec5506a203fa93ab16b10073ec372bf033e0ccd70be
7
+ data.tar.gz: c1be83e01a08ff7a6ddce1b2afc321ec780dd5f2b00837220c411838f39e0dd9584419737239c8640a25e15b4d86ea190109fc22c26291ea21d6551317082773
data/README.md CHANGED
@@ -5,6 +5,26 @@ Core functionality shared between Panda Software gems:
5
5
  - [Panda CMS](https://github.com/tastybamboo/panda-cms)
6
6
  - [Panda Editor](https://github.com/tastybamboo/panda-editor)
7
7
 
8
+ ## Requirements
9
+
10
+ ### Database Support
11
+
12
+ Panda Core supports both PostgreSQL and SQLite3 databases:
13
+
14
+ **PostgreSQL** (recommended for production):
15
+
16
+ - PostgreSQL 12, 15, 17, 18
17
+ - Tested against all versions in CI
18
+ - Uses native UUID generation (`gen_random_uuid()`)
19
+
20
+ **SQLite3** (development/testing):
21
+
22
+ - SQLite 3.x
23
+ - Uses application-level UUID generation
24
+ - Ideal for local development and testing
25
+
26
+ Both databases are tested extensively in CI across multiple Ruby and Rails versions to ensure cross-database compatibility.
27
+
8
28
  ## Installation
9
29
 
10
30
  Add this line to your application's Gemfile:
@@ -251,6 +271,21 @@ bundle exec rspec spec/generators
251
271
  bundle exec rspec spec/system
252
272
  ```
253
273
 
274
+ ### CI/Docker parity
275
+
276
+ The GitHub Actions workflow now builds a reusable CI image (Chrome + PostgreSQL + Ruby). You can reuse it locally:
277
+
278
+ ```
279
+ bin/ci build # build the CI image locally
280
+ bin/ci test # run the suite inside the container
281
+ ```
282
+
283
+ To dry-run the workflow with `act` (uses the locally built image tagged `:local`):
284
+
285
+ ```
286
+ act -j test-stable
287
+ ```
288
+
254
289
  ## Contributing
255
290
 
256
291
  1. Fork it
@@ -153,6 +153,11 @@
153
153
  html[data-theme='sky'] .bg-gradient-admin {
154
154
  background: linear-gradient(to bottom right, rgb(20, 32, 74), rgb(42, 102, 159));
155
155
  }
156
+
157
+ /* Remove rounded bottom-left corner on admin sidebar */
158
+ .bg-gradient-admin {
159
+ border-bottom-left-radius: 0;
160
+ }
156
161
  }
157
162
 
158
163
  /* Form input styles */
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Panda
4
+ module Core
5
+ # Concern for models using UUIDs as primary keys
6
+ # Ensures UUID generation works across all database adapters
7
+ module HasUUID
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ # Generate UUID before creation if not already set
12
+ # PostgreSQL uses gen_random_uuid() natively
13
+ # SQLite and other databases use SecureRandom.uuid
14
+ before_create :generate_uuid_if_blank
15
+ end
16
+
17
+ private
18
+
19
+ def generate_uuid_if_blank
20
+ self.id ||= SecureRandom.uuid if id.blank?
21
+ end
22
+ end
23
+ end
24
+ end
@@ -3,6 +3,8 @@
3
3
  module Panda
4
4
  module Core
5
5
  class User < ApplicationRecord
6
+ include HasUUID
7
+
6
8
  self.table_name = "panda_core_users"
7
9
 
8
10
  # Active Storage attachment for avatar with variants
@@ -17,16 +19,15 @@ module Panda
17
19
 
18
20
  before_save :downcase_email
19
21
 
22
+ # Determine which column stores admin flag (supports legacy `admin` and new `is_admin`)
23
+ def self.admin_column
24
+ # Prefer canonical `admin` if available, otherwise fall back to legacy `is_admin`
25
+ @admin_column ||= column_names.include?("admin") ? "admin" : "is_admin"
26
+ end
27
+
20
28
  # Scopes
21
- # Support both 'admin' (newer) and 'is_admin' (older) column names
22
29
  scope :admins, -> {
23
- if column_names.include?("admin")
24
- where(admin: true)
25
- elsif column_names.include?("is_admin")
26
- where(is_admin: true)
27
- else
28
- none
29
- end
30
+ where(admin_column => true)
30
31
  }
31
32
 
32
33
  def self.find_or_create_from_auth_hash(auth_hash)
@@ -46,7 +47,7 @@ module Panda
46
47
  email: auth_hash.info.email.downcase,
47
48
  name: auth_hash.info.name || "Unknown User",
48
49
  image_url: auth_hash.info.image,
49
- is_admin: User.count.zero? # First user is admin
50
+ admin_column => User.count.zero? # First user is admin
50
51
  }
51
52
 
52
53
  user = create!(attributes)
@@ -60,21 +61,23 @@ module Panda
60
61
  end
61
62
 
62
63
  # Admin status check
63
- # Note: Column is named 'admin' in newer schemas, 'is_admin' in older ones
64
64
  def admin?
65
- self[:admin] || self[:is_admin] || false
65
+ ActiveRecord::Type::Boolean.new.cast(admin)
66
66
  end
67
67
 
68
- def active_for_authentication?
69
- true
68
+ # Support both legacy `admin` and new `is_admin` columns
69
+ def admin
70
+ self[self.class.admin_column]
70
71
  end
71
72
 
72
- def name
73
- if self[:name].present?
74
- self[:name]
75
- else
76
- email&.split("@")&.first || "Unknown User"
77
- end
73
+ def admin=(value)
74
+ self[self.class.admin_column] = ActiveRecord::Type::Boolean.new.cast(value)
75
+ end
76
+ alias_method :is_admin, :admin
77
+ alias_method :is_admin=, :admin=
78
+
79
+ def active_for_authentication?
80
+ true
78
81
  end
79
82
 
80
83
  # Returns the URL for the user's avatar
@@ -0,0 +1,51 @@
1
+ {
2
+ "ignored_warnings": [
3
+ {
4
+ "warning_type": "Command Injection",
5
+ "warning_code": 14,
6
+ "fingerprint": "29d5b60b583fc98e293f6ac47f153bbf5db48a03963f8c9a7711bd251ecf2d81",
7
+ "check_name": "Execute",
8
+ "message": "Possible command injection",
9
+ "file": "lib/panda/core/testing/support/system/cuprite_helpers.rb",
10
+ "line": 78,
11
+ "link": "https://brakemanscanner.org/docs/warning_types/command_injection/",
12
+ "code": "system(\"ffmpeg -y -framerate 8 -pattern_type glob -i '#{File.join(dir, \"frames\")}/*.png' -c:v libx264 -pix_fmt yuv420p '#{capybara_artifacts_dir.join(\"#{example.metadata[:full_description].parameterize}.mp4\")}'\\n\")",
13
+ "render_path": null,
14
+ "location": {
15
+ "type": "method",
16
+ "class": "Panda::Core::Testing::CupriteHelpers",
17
+ "method": "record_video!"
18
+ },
19
+ "user_input": "File.join(dir, \"frames\")",
20
+ "confidence": "Medium",
21
+ "cwe_id": [
22
+ 77
23
+ ],
24
+ "note": "This does not use user input and is based on environment variables and the current folder"
25
+ },
26
+ {
27
+ "warning_type": "Command Injection",
28
+ "warning_code": 14,
29
+ "fingerprint": "d3c339f054cbd511956e04eaf8763bc722bd2e4559a1c1fec58749abef9792cd",
30
+ "check_name": "Execute",
31
+ "message": "Possible command injection",
32
+ "file": "lib/panda/core/testing/support/system/chrome_verification.rb",
33
+ "line": 43,
34
+ "link": "https://brakemanscanner.org/docs/warning_types/command_injection/",
35
+ "code": "Process.spawn(*[BrowserPath.resolve, v.nil? ? (\"--#{k}\") : (\"--#{k}=#{v}\"), \"about:blank\"], :out => (File::NULL), :err => (File::NULL))",
36
+ "render_path": null,
37
+ "location": {
38
+ "type": "method",
39
+ "class": "Panda::Core::Testing::Support::System::ChromeVerification",
40
+ "method": "s(:self).verify!"
41
+ },
42
+ "user_input": "k",
43
+ "confidence": "Medium",
44
+ "cwe_id": [
45
+ 77
46
+ ],
47
+ "note": "Chrome verification uses a fixed set of browser flags from CHROME_FLAGS constant; no user input is accepted"
48
+ }
49
+ ],
50
+ "brakeman_version": "7.1.1"
51
+ }
@@ -2,16 +2,21 @@
2
2
 
3
3
  class CreatePandaCoreUsers < ActiveRecord::Migration[7.1]
4
4
  def change
5
- unless table_exists?(:panda_core_users)
6
- create_table :panda_core_users, id: :uuid do |t|
7
- t.string :name
8
- t.string :email, null: false
9
- t.string :image_url
10
- t.boolean :is_admin, default: false, null: false
11
- t.timestamps
12
- end
5
+ # Enable pgcrypto extension for PostgreSQL UUID generation
6
+ enable_extension "pgcrypto" if ActiveRecord::Base.connection.adapter_name == "PostgreSQL"
13
7
 
14
- add_index :panda_core_users, :email, unique: true
8
+ # Rails 7.1+ supports id: :uuid across databases
9
+ # PostgreSQL uses gen_random_uuid(), SQLite uses application-generated UUIDs
10
+ create_table :panda_core_users, id: :uuid do |t|
11
+ t.string :name
12
+ t.string :email, null: false
13
+ t.string :image_url
14
+ t.boolean :admin, default: false, null: false
15
+ t.string :current_theme
16
+ t.string :oauth_avatar_url
17
+ t.timestamps
15
18
  end
19
+
20
+ add_index :panda_core_users, :email, unique: true
16
21
  end
17
22
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RenameIsAdminToAdminInPandaCoreUsers < ActiveRecord::Migration[7.1]
4
+ def up
5
+ # If apps created an `is_admin` column during the brief rename, move it back
6
+ return unless column_exists?(:panda_core_users, :is_admin)
7
+ return if column_exists?(:panda_core_users, :admin)
8
+
9
+ rename_column :panda_core_users, :is_admin, :admin
10
+ end
11
+
12
+ def down
13
+ return unless column_exists?(:panda_core_users, :admin)
14
+ return if column_exists?(:panda_core_users, :is_admin)
15
+
16
+ rename_column :panda_core_users, :admin, :is_admin
17
+ end
18
+ end
@@ -1,3 +1,5 @@
1
+ require "active_support/core_ext/numeric/bytes"
2
+
1
3
  module Panda
2
4
  module Core
3
5
  class Configuration
@@ -7,6 +9,7 @@ module Panda
7
9
  :cache_store,
8
10
  :parent_controller,
9
11
  :parent_mailer,
12
+ :auto_mount_engine,
10
13
  :mailer_sender,
11
14
  :mailer_default_url_options,
12
15
  :session_token_cookie,
@@ -38,6 +41,7 @@ module Panda
38
41
  @cache_store = :memory_store
39
42
  @parent_controller = "ActionController::API"
40
43
  @parent_mailer = "ActionMailer::Base"
44
+ @auto_mount_engine = true
41
45
  @mailer_sender = "support@example.com"
42
46
  @mailer_default_url_options = {host: "localhost:3000"}
43
47
  @session_token_cookie = :panda_session
@@ -3,21 +3,20 @@
3
3
  module Panda
4
4
  module Core
5
5
  class Engine < ::Rails::Engine
6
- # Autoload paths configuration
7
6
  module AutoloadConfig
8
7
  extend ActiveSupport::Concern
9
8
 
10
9
  included do
11
- config.eager_load_namespaces << Panda::Core::Engine
10
+ # These must run BEFORE initialization, so this is allowed
11
+ config.autoload_paths << root.join("app/builders")
12
+ config.autoload_paths << root.join("app/components")
13
+ config.autoload_paths << root.join("app/services")
14
+ config.autoload_paths << root.join("app/models")
15
+ config.autoload_paths << root.join("app/helpers")
16
+ config.autoload_paths << root.join("app/constraints")
12
17
 
13
- # Add engine's app directories to autoload paths
14
- # Note: Only add the root directories, not nested subdirectories
15
- # Zeitwerk will automatically discover nested modules from these roots
16
- config.autoload_paths += Dir[root.join("app", "models")]
17
- config.autoload_paths += Dir[root.join("app", "controllers")]
18
- config.autoload_paths += Dir[root.join("app", "builders")]
19
- config.autoload_paths += Dir[root.join("app", "components")]
20
- config.autoload_paths += Dir[root.join("app", "services")]
18
+ # Mirror eager-load as needed
19
+ config.eager_load_paths.concat(config.autoload_paths)
21
20
  end
22
21
  end
23
22
  end
@@ -1,51 +1,97 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/concern"
4
+
3
5
  module Panda
4
6
  module Core
5
7
  class Engine < ::Rails::Engine
6
- # OmniAuth configuration
7
8
  module OmniauthConfig
8
9
  extend ActiveSupport::Concern
9
10
 
11
+ PROVIDER_REGISTRY = {
12
+ # Microsoft
13
+ "microsoft" => :microsoft_graph,
14
+ "microsoft_graph" => :microsoft_graph,
15
+
16
+ # Google
17
+ "google" => :google_oauth2,
18
+ "google_oauth2" => :google_oauth2,
19
+ "gmail" => :google_oauth2,
20
+
21
+ # GitHub
22
+ "github" => :github,
23
+ "gh" => :github,
24
+
25
+ # Developer
26
+ "developer" => :developer
27
+ }.freeze
28
+
10
29
  included do
11
- initializer "panda_core.omniauth" do |app|
12
- # Load OAuth provider gems
13
- require_relative "../oauth_providers"
14
- Panda::Core::OAuthProviders.setup
15
-
16
- # Mount OmniAuth at configurable admin path
17
- app.middleware.use OmniAuth::Builder do
18
- # Configure OmniAuth to use the configured admin path
19
- configure do |config|
20
- config.path_prefix = "#{Panda::Core.config.admin_path}/auth"
21
- # POST-only for CSRF protection (CVE-2015-9284)
22
- # All login forms use POST via form_tag method: "post"
23
- config.allowed_request_methods = [:post]
24
- end
25
-
26
- Panda::Core.config.authentication_providers.each do |provider_name, settings|
27
- # Build provider options, allowing custom path name override
28
- provider_options = settings[:options] || {}
29
-
30
- # If path_name is specified, use it to override the default strategy name in URLs
31
- if settings[:path_name].present?
32
- provider_options = provider_options.merge(name: settings[:path_name])
33
- end
34
-
35
- case provider_name.to_s
36
- when "microsoft_graph"
37
- provider :microsoft_graph, settings[:client_id], settings[:client_secret], provider_options
38
- when "google_oauth2"
39
- provider :google_oauth2, settings[:client_id], settings[:client_secret], provider_options
40
- when "github"
41
- provider :github, settings[:client_id], settings[:client_secret], provider_options
42
- when "developer"
43
- provider :developer if Rails.env.development?
44
- end
45
- end
30
+ if respond_to?(:initializer)
31
+ initializer "panda_core.omniauth" do |app|
32
+ require_relative "../oauth_providers"
33
+ Panda::Core::OAuthProviders.setup
34
+
35
+ load_yaml_provider_overrides!
36
+ configure_omniauth_globals
37
+ mount_omniauth_middleware(app)
46
38
  end
47
39
  end
48
40
  end
41
+
42
+ private
43
+
44
+ # 1. YAML overrides
45
+ def load_yaml_provider_overrides!
46
+ path = Panda::Core::Engine.root.join("config/providers.yml")
47
+ return unless File.exist?(path)
48
+
49
+ yaml = YAML.load_file(path) || {}
50
+ (yaml["providers"] || {}).each do |name, settings|
51
+ Panda::Core.config.authentication_providers[name.to_s] ||= {}
52
+ Panda::Core.config.authentication_providers[name.to_s].deep_merge!(settings)
53
+ end
54
+ end
55
+
56
+ # 2. Global settings
57
+ def configure_omniauth_globals
58
+ OmniAuth.configure do |c|
59
+ c.allowed_request_methods = [:post]
60
+ c.path_prefix = "#{Panda::Core.config.admin_path}/auth"
61
+ end
62
+ end
63
+
64
+ # 3. Middleware insertion
65
+ def mount_omniauth_middleware(app)
66
+ ctx = self # Capture the Engine/Concern context
67
+
68
+ Panda::Core::Middleware.use(app, OmniAuth::Builder) do
69
+ Panda::Core.config.authentication_providers.each do |name, settings|
70
+ ctx.send(:configure_provider, self, name, settings)
71
+ end
72
+ end
73
+ end
74
+
75
+ # 4. Provider builder
76
+ def configure_provider(builder, name, settings)
77
+ symbol = PROVIDER_REGISTRY[name.to_s]
78
+
79
+ unless symbol
80
+ Rails.logger.warn("[panda-core] Unknown OmniAuth provider: #{name.inspect}")
81
+ return
82
+ end
83
+
84
+ return if symbol == :developer && !Rails.env.development?
85
+
86
+ options = (settings[:options] || {}).dup
87
+ options[:name] = settings[:path_name] if settings[:path_name].present?
88
+
89
+ if settings[:client_id] && settings[:client_secret]
90
+ builder.provider symbol, settings[:client_id], settings[:client_secret], options
91
+ else
92
+ builder.provider symbol, options
93
+ end
94
+ end
49
95
  end
50
96
  end
51
97
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Panda
4
+ module Core
5
+ class Engine < ::Rails::Engine
6
+ # Automatically mount the engine into host applications
7
+ module RouteConfig
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ # Append Core routes to the host app after initialization so
12
+ # engine routes are available without manual mounting.
13
+ config.after_initialize do |app|
14
+ next unless Panda::Core.config.auto_mount_engine
15
+
16
+ route_set = app.routes
17
+ already_mounted =
18
+ route_set.routes.any? do |route|
19
+ route.app == Panda::Core::Engine ||
20
+ (route.app.respond_to?(:app) && route.app.app == Panda::Core::Engine)
21
+ end
22
+ already_mounted ||= route_set.named_routes.key?(:panda_core)
23
+
24
+ next if already_mounted
25
+
26
+ route_set.append do
27
+ # Re-check inside the mapper to avoid duplicate mounts during reloads
28
+ next if route_set.named_routes.key?(:panda_core)
29
+
30
+ mount Panda::Core::Engine => "/", as: "panda_core"
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -1,82 +1,86 @@
1
- require "rubygems"
2
- require "stringio"
3
- require "rails/engine"
1
+ # frozen_string_literal: true
2
+
3
+ require "rails"
4
4
  require "omniauth"
5
5
 
6
- # Silence ActiveSupport::Configurable deprecation from omniauth-rails_csrf_protection
7
- # This gem uses the deprecated module but hasn't been updated yet
8
- # Issue: https://github.com/cookpad/omniauth-rails_csrf_protection/issues/23
9
- # This can be removed once the gem is updated or Rails 8.2 is released
10
- #
11
- # We suppress the warning by temporarily redirecting stderr since
12
- # ActiveSupport::Deprecation.silence was removed in Rails 8.1
13
- original_stderr = $stderr
14
- $stderr = StringIO.new
15
- begin
16
- require "omniauth/rails_csrf_protection"
17
- ensure
18
- $stderr = original_stderr
19
- end
6
+ require "panda/core/middleware"
7
+ require "panda/core/module_registry"
20
8
 
21
- # Load shared configuration modules
9
+ # Shared engine mixins
22
10
  require_relative "shared/inflections_config"
23
11
  require_relative "shared/generator_config"
24
12
 
25
- # Load engine configuration modules
13
+ # Engine mixins
26
14
  require_relative "engine/autoload_config"
27
- require_relative "engine/middleware_config"
28
15
  require_relative "engine/importmap_config"
29
16
  require_relative "engine/omniauth_config"
30
17
  require_relative "engine/phlex_config"
31
18
  require_relative "engine/admin_controller_config"
32
-
33
- # Load module registry
34
- require_relative "module_registry"
19
+ require_relative "engine/route_config"
35
20
 
36
21
  module Panda
37
22
  module Core
38
23
  class Engine < ::Rails::Engine
39
24
  isolate_namespace Panda::Core
40
25
 
41
- # Include shared configuration modules
26
+ #
27
+ # Include shared behaviours
28
+ #
42
29
  include Shared::InflectionsConfig
43
30
  include Shared::GeneratorConfig
44
31
 
45
- # Include engine-specific configuration modules
32
+ #
33
+ # Include engine-level concerns
34
+ #
46
35
  include AutoloadConfig
47
- include MiddlewareConfig
48
36
  include ImportmapConfig
49
37
  include OmniauthConfig
50
38
  include PhlexConfig
51
39
  include AdminControllerConfig
40
+ include RouteConfig
52
41
 
53
- initializer "panda_core.config" do |app|
54
- # Configuration is already initialized with defaults in Configuration class
42
+ #
43
+ # Misc configuration point
44
+ #
45
+ initializer "panda_core.configuration" do
46
+ # Intentionally quiet — used as a stable anchor point
55
47
  end
56
48
 
57
- # Static asset middleware for serving public files and JavaScript modules
58
- # Must run before Propshaft to intercept /panda/* requests, but we can't
59
- # guarantee Propshaft is in the host application, so just insert it
60
- # high up in the middleware stack
61
- initializer "panda.core.static_assets" do |app|
62
- # Serve public assets (CSS, images, etc.)
63
- app.config.middleware.insert_before ActionDispatch::Static, Rack::Static,
49
+ #
50
+ # Static asset handling for:
51
+ # /panda-core-assets
52
+ #
53
+ initializer "panda_core.static_assets" do |app|
54
+ Panda::Core::Middleware.use(
55
+ app,
56
+ Rack::Static,
64
57
  urls: ["/panda-core-assets"],
65
58
  root: Panda::Core::Engine.root.join("public"),
66
59
  header_rules: [
67
- # Disable caching in development for instant CSS updates
68
- [:all, {"Cache-Control" => Rails.env.development? ? "no-cache, no-store, must-revalidate" : "public, max-age=31536000"}]
60
+ [
61
+ :all,
62
+ {
63
+ "Cache-Control" =>
64
+ Rails.env.development? ?
65
+ "no-cache, no-store, must-revalidate" :
66
+ "public, max-age=31536000"
67
+ }
68
+ ]
69
69
  ]
70
+ )
70
71
 
71
- # Use ModuleRegistry's custom middleware to serve JavaScript from all registered modules
72
- # This middleware checks all modules and serves from the first matching location
73
- app.config.middleware.insert_before ActionDispatch::Static, Panda::Core::ModuleRegistry::JavaScriptMiddleware
72
+ Panda::Core::Middleware.use(
73
+ app,
74
+ Panda::Core::ModuleRegistry::JavaScriptMiddleware
75
+ )
74
76
  end
75
77
  end
76
78
  end
77
79
  end
78
80
 
79
- # Register Core module with ModuleRegistry for JavaScript serving
81
+ #
82
+ # Register engine with ModuleRegistry
83
+ #
80
84
  Panda::Core::ModuleRegistry.register(
81
85
  gem_name: "panda-core",
82
86
  engine: "Panda::Core::Engine",
@@ -85,6 +89,7 @@ Panda::Core::ModuleRegistry.register(
85
89
  components: "app/components/panda/core/**/*.rb",
86
90
  helpers: "app/helpers/panda/core/**/*.rb",
87
91
  views: "app/views/panda/core/**/*.erb",
92
+ layouts: "app/views/layouts/panda/core/**/*.erb",
88
93
  javascripts: "app/assets/javascript/panda/core/**/*.js"
89
94
  }
90
95
  )