panda-core 0.7.2 → 0.7.4
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/app/components/panda/core/admin/user_display_component.rb +4 -3
- data/app/models/panda/core/user.rb +15 -4
- data/app/services/panda/core/attach_avatar_service.rb +51 -6
- data/lib/panda/core/configuration.rb +18 -1
- data/lib/panda/core/engine/admin_controller_config.rb +22 -0
- data/lib/panda/core/engine/autoload_config.rb +25 -0
- data/lib/panda/core/engine/generator_config.rb +20 -0
- data/lib/panda/core/engine/importmap_config.rb +31 -0
- data/lib/panda/core/engine/inflections_config.rb +23 -0
- data/lib/panda/core/engine/middleware_config.rb +33 -0
- data/lib/panda/core/engine/omniauth_config.rb +52 -0
- data/lib/panda/core/engine/phlex_config.rb +26 -0
- data/lib/panda/core/engine/test_config.rb +20 -0
- data/lib/panda/core/engine.rb +20 -115
- data/lib/panda/core/shared/generator_config.rb +22 -0
- data/lib/panda/core/shared/inflections_config.rb +24 -0
- data/lib/panda/core/version.rb +1 -1
- data/lib/panda/core.rb +2 -0
- metadata +26 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a2cf8c2a2e3d0ee0034e6d066e6b0091a91ac26b45175137bd20e55b428d0b27
|
|
4
|
+
data.tar.gz: dfce6c83389d46e65195b10ac115d6fde5ae27eb7aeaab791e102b12aa428917
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 733a874eeff0435314e2fde0ec445a35b13c3f5623bb4bb711d6bc6a5709bdc9bf07699cfa7c241868f3157c0421c3993b91a1f43c7704378aae0f7e07c5f74e
|
|
7
|
+
data.tar.gz: 3fed89c4393a1b33ab120308a40657189719c1a9b956afb822823eefda663989a259f769535fb00a587dd9e3136d70e6c0fcb4b8ceb0dbac4cc4b8511b001ff1
|
|
@@ -31,14 +31,15 @@ module Panda
|
|
|
31
31
|
|
|
32
32
|
def render_avatar
|
|
33
33
|
has_image = resolved_user.respond_to?(:avatar_url) &&
|
|
34
|
-
resolved_user.avatar_url.present?
|
|
34
|
+
resolved_user.avatar_url(size: :small).present?
|
|
35
35
|
|
|
36
36
|
if has_image
|
|
37
37
|
div do
|
|
38
38
|
img(
|
|
39
39
|
class: "inline-block w-10 h-10 rounded-full object-cover",
|
|
40
|
-
src: resolved_user.avatar_url,
|
|
41
|
-
alt: ""
|
|
40
|
+
src: resolved_user.avatar_url(size: :small),
|
|
41
|
+
alt: "",
|
|
42
|
+
loading: "lazy"
|
|
42
43
|
)
|
|
43
44
|
end
|
|
44
45
|
else
|
|
@@ -5,8 +5,13 @@ module Panda
|
|
|
5
5
|
class User < ApplicationRecord
|
|
6
6
|
self.table_name = "panda_core_users"
|
|
7
7
|
|
|
8
|
-
# Active Storage attachment for avatar
|
|
9
|
-
has_one_attached :avatar
|
|
8
|
+
# Active Storage attachment for avatar with variants
|
|
9
|
+
has_one_attached :avatar do |attachable|
|
|
10
|
+
attachable.variant :thumb, resize_to_limit: [50, 50], preprocessed: true
|
|
11
|
+
attachable.variant :small, resize_to_limit: [100, 100], preprocessed: true
|
|
12
|
+
attachable.variant :medium, resize_to_limit: [200, 200], preprocessed: true
|
|
13
|
+
attachable.variant :large, resize_to_limit: [400, 400], preprocessed: true
|
|
14
|
+
end
|
|
10
15
|
|
|
11
16
|
validates :email, presence: true, uniqueness: {case_sensitive: false}
|
|
12
17
|
|
|
@@ -90,9 +95,15 @@ module Panda
|
|
|
90
95
|
|
|
91
96
|
# Returns the URL for the user's avatar
|
|
92
97
|
# Prefers Active Storage attachment over OAuth provider URL
|
|
93
|
-
|
|
98
|
+
# @param size [Symbol] The variant size (:thumb, :small, :medium, :large, or nil for original)
|
|
99
|
+
# @return [String, nil] The avatar URL or nil if no avatar available
|
|
100
|
+
def avatar_url(size: nil)
|
|
94
101
|
if avatar.attached?
|
|
95
|
-
|
|
102
|
+
if size && [:thumb, :small, :medium, :large].include?(size)
|
|
103
|
+
Rails.application.routes.url_helpers.rails_blob_path(avatar.variant(size), only_path: true)
|
|
104
|
+
else
|
|
105
|
+
Rails.application.routes.url_helpers.rails_blob_path(avatar, only_path: true)
|
|
106
|
+
end
|
|
96
107
|
elsif self[:image_url].present?
|
|
97
108
|
# Fallback to OAuth provider URL if no avatar is attached yet
|
|
98
109
|
self[:image_url]
|
|
@@ -5,6 +5,9 @@ require "open-uri"
|
|
|
5
5
|
module Panda
|
|
6
6
|
module Core
|
|
7
7
|
class AttachAvatarService < Services::BaseService
|
|
8
|
+
MAX_FILE_SIZE = 5.megabytes
|
|
9
|
+
MAX_DIMENSION = 800 # Max width/height for optimization
|
|
10
|
+
|
|
8
11
|
def initialize(user:, avatar_url:)
|
|
9
12
|
@user = user
|
|
10
13
|
@avatar_url = avatar_url
|
|
@@ -33,25 +36,67 @@ module Panda
|
|
|
33
36
|
URI.open(@avatar_url, read_timeout: 10, open_timeout: 10, redirect: true) do |downloaded_file|
|
|
34
37
|
# standard:enable Security/Open
|
|
35
38
|
# Validate file size (max 5MB)
|
|
36
|
-
if downloaded_file.size >
|
|
39
|
+
if downloaded_file.size > MAX_FILE_SIZE
|
|
37
40
|
raise "Avatar file too large (#{downloaded_file.size} bytes)"
|
|
38
41
|
end
|
|
39
42
|
|
|
40
43
|
# Determine content type and filename
|
|
41
44
|
content_type = downloaded_file.content_type || "image/jpeg"
|
|
42
|
-
extension = determine_extension(content_type)
|
|
43
|
-
filename = "avatar_#{@user.id}_#{Time.current.to_i}#{extension}"
|
|
44
45
|
|
|
45
|
-
#
|
|
46
|
+
# Optimize and attach the avatar
|
|
47
|
+
optimized_file = optimize_image(downloaded_file, content_type)
|
|
48
|
+
|
|
49
|
+
filename = "avatar_#{@user.id}_#{Time.current.to_i}.webp"
|
|
50
|
+
|
|
51
|
+
# Attach the optimized avatar
|
|
46
52
|
@user.avatar.attach(
|
|
47
|
-
io:
|
|
53
|
+
io: optimized_file,
|
|
48
54
|
filename: filename,
|
|
49
|
-
content_type:
|
|
55
|
+
content_type: "image/webp"
|
|
50
56
|
)
|
|
51
57
|
end
|
|
52
58
|
# standard:enable Security/Open
|
|
53
59
|
end
|
|
54
60
|
|
|
61
|
+
def optimize_image(file, content_type)
|
|
62
|
+
processor = Panda::Core.config.avatar_image_processor
|
|
63
|
+
loader = load_image_processor(processor)
|
|
64
|
+
return file unless loader
|
|
65
|
+
|
|
66
|
+
begin
|
|
67
|
+
processed = loader
|
|
68
|
+
.source(file)
|
|
69
|
+
.resize_to_limit(MAX_DIMENSION, MAX_DIMENSION)
|
|
70
|
+
.convert("webp")
|
|
71
|
+
.saver(quality: 85, strip: true) # Strip metadata, 85% quality
|
|
72
|
+
.call
|
|
73
|
+
|
|
74
|
+
# Return File object
|
|
75
|
+
File.open(processed.path)
|
|
76
|
+
rescue => e
|
|
77
|
+
Rails.logger.warn("Image optimization failed (#{processor}), using original: #{e.message}")
|
|
78
|
+
# Fallback to original file if optimization fails
|
|
79
|
+
file
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def load_image_processor(processor)
|
|
84
|
+
case processor
|
|
85
|
+
when :vips
|
|
86
|
+
require "image_processing/vips"
|
|
87
|
+
ImageProcessing::Vips
|
|
88
|
+
when :mini_magick
|
|
89
|
+
require "image_processing/mini_magick"
|
|
90
|
+
ImageProcessing::MiniMagick
|
|
91
|
+
else
|
|
92
|
+
Rails.logger.warn("Unknown image processor: #{processor}, avatar optimization disabled")
|
|
93
|
+
nil
|
|
94
|
+
end
|
|
95
|
+
rescue LoadError => e
|
|
96
|
+
Rails.logger.warn("Image processor #{processor} not available: #{e.message}")
|
|
97
|
+
nil
|
|
98
|
+
end
|
|
99
|
+
|
|
55
100
|
def determine_extension(content_type)
|
|
56
101
|
case content_type
|
|
57
102
|
when /jpeg|jpg/
|
|
@@ -24,7 +24,12 @@ module Panda
|
|
|
24
24
|
:login_page_title,
|
|
25
25
|
:admin_title,
|
|
26
26
|
:initial_admin_breadcrumb,
|
|
27
|
-
:dashboard_redirect_path
|
|
27
|
+
:dashboard_redirect_path,
|
|
28
|
+
:avatar_variants,
|
|
29
|
+
:avatar_max_file_size,
|
|
30
|
+
:avatar_max_dimension,
|
|
31
|
+
:avatar_optimization_quality,
|
|
32
|
+
:avatar_image_processor
|
|
28
33
|
|
|
29
34
|
def initialize
|
|
30
35
|
@user_class = "Panda::Core::User"
|
|
@@ -80,6 +85,18 @@ module Panda
|
|
|
80
85
|
@admin_title = "Panda Admin"
|
|
81
86
|
@initial_admin_breadcrumb = nil # Proc that returns [label, path]
|
|
82
87
|
@dashboard_redirect_path = nil # Path to redirect to after login (defaults to admin_root_path)
|
|
88
|
+
|
|
89
|
+
# Avatar configuration
|
|
90
|
+
@avatar_variants = {
|
|
91
|
+
thumb: {resize_to_limit: [50, 50]},
|
|
92
|
+
small: {resize_to_limit: [100, 100]},
|
|
93
|
+
medium: {resize_to_limit: [200, 200]},
|
|
94
|
+
large: {resize_to_limit: [400, 400]}
|
|
95
|
+
}
|
|
96
|
+
@avatar_max_file_size = 5.megabytes
|
|
97
|
+
@avatar_max_dimension = 800
|
|
98
|
+
@avatar_optimization_quality = 85
|
|
99
|
+
@avatar_image_processor = :vips # or :mini_magick
|
|
83
100
|
end
|
|
84
101
|
end
|
|
85
102
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Panda
|
|
4
|
+
module Core
|
|
5
|
+
class Engine < ::Rails::Engine
|
|
6
|
+
# AdminController alias configuration
|
|
7
|
+
module AdminControllerConfig
|
|
8
|
+
extend ActiveSupport::Concern
|
|
9
|
+
|
|
10
|
+
included do
|
|
11
|
+
# Create AdminController alias after controllers are loaded
|
|
12
|
+
# This allows other gems to inherit from Panda::Core::AdminController
|
|
13
|
+
initializer "panda_core.admin_controller_alias", after: :load_config_initializers do
|
|
14
|
+
ActiveSupport.on_load(:action_controller_base) do
|
|
15
|
+
Panda::Core.const_set(:AdminController, Panda::Core::Admin::BaseController) unless Panda::Core.const_defined?(:AdminController)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Panda
|
|
4
|
+
module Core
|
|
5
|
+
class Engine < ::Rails::Engine
|
|
6
|
+
# Autoload paths configuration
|
|
7
|
+
module AutoloadConfig
|
|
8
|
+
extend ActiveSupport::Concern
|
|
9
|
+
|
|
10
|
+
included do
|
|
11
|
+
config.eager_load_namespaces << Panda::Core::Engine
|
|
12
|
+
|
|
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")]
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Panda
|
|
4
|
+
module Core
|
|
5
|
+
class Engine < ::Rails::Engine
|
|
6
|
+
# Generator configuration
|
|
7
|
+
module GeneratorConfig
|
|
8
|
+
extend ActiveSupport::Concern
|
|
9
|
+
|
|
10
|
+
included do
|
|
11
|
+
config.generators do |g|
|
|
12
|
+
g.test_framework :rspec
|
|
13
|
+
g.fixture_replacement :factory_bot
|
|
14
|
+
g.factory_bot dir: "spec/factories"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Panda
|
|
4
|
+
module Core
|
|
5
|
+
class Engine < ::Rails::Engine
|
|
6
|
+
# Importmap configuration
|
|
7
|
+
module ImportmapConfig
|
|
8
|
+
extend ActiveSupport::Concern
|
|
9
|
+
|
|
10
|
+
included do
|
|
11
|
+
# Add importmap paths from the engine
|
|
12
|
+
initializer "panda_core.importmap", before: "importmap" do |app|
|
|
13
|
+
if app.config.respond_to?(:importmap)
|
|
14
|
+
# Create a new array if frozen
|
|
15
|
+
app.config.importmap.paths = app.config.importmap.paths.dup if app.config.importmap.paths.frozen?
|
|
16
|
+
|
|
17
|
+
# Add our paths
|
|
18
|
+
app.config.importmap.paths << root.join("config/importmap.rb")
|
|
19
|
+
|
|
20
|
+
# Handle cache sweepers similarly
|
|
21
|
+
if app.config.importmap.cache_sweepers.frozen?
|
|
22
|
+
app.config.importmap.cache_sweepers = app.config.importmap.cache_sweepers.dup
|
|
23
|
+
end
|
|
24
|
+
app.config.importmap.cache_sweepers << root.join("app/javascript")
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Panda
|
|
4
|
+
module Core
|
|
5
|
+
class Engine < ::Rails::Engine
|
|
6
|
+
# Inflections configuration
|
|
7
|
+
module InflectionsConfig
|
|
8
|
+
extend ActiveSupport::Concern
|
|
9
|
+
|
|
10
|
+
included do
|
|
11
|
+
# Load inflections early to ensure proper constant resolution
|
|
12
|
+
initializer "panda_core.inflections", before: :load_config_initializers do
|
|
13
|
+
ActiveSupport::Inflector.inflections(:en) do |inflect|
|
|
14
|
+
inflect.acronym "CMS"
|
|
15
|
+
inflect.acronym "SEO"
|
|
16
|
+
inflect.acronym "AI"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Panda
|
|
4
|
+
module Core
|
|
5
|
+
class Engine < ::Rails::Engine
|
|
6
|
+
# Middleware configuration for static assets
|
|
7
|
+
module MiddlewareConfig
|
|
8
|
+
extend ActiveSupport::Concern
|
|
9
|
+
|
|
10
|
+
included do
|
|
11
|
+
# Make files in public available to the main app (e.g. /panda-core-assets/panda-logo.png)
|
|
12
|
+
config.middleware.use Rack::Static,
|
|
13
|
+
urls: ["/panda-core-assets"],
|
|
14
|
+
root: Panda::Core::Engine.root.join("public"),
|
|
15
|
+
header_rules: [
|
|
16
|
+
# Disable caching in development for instant CSS updates
|
|
17
|
+
[:all, {"Cache-Control" => Rails.env.development? ? "no-cache, no-store, must-revalidate" : "public, max-age=31536000"}]
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
# Make JavaScript files available for importmap
|
|
21
|
+
# Serve from app/javascript with proper MIME types
|
|
22
|
+
config.middleware.use Rack::Static,
|
|
23
|
+
urls: ["/panda", "/panda/core"],
|
|
24
|
+
root: Panda::Core::Engine.root.join("app/javascript"),
|
|
25
|
+
header_rules: [
|
|
26
|
+
[:all, {"Cache-Control" => Rails.env.development? ? "no-cache, no-store, must-revalidate" : "public, max-age=31536000",
|
|
27
|
+
"Content-Type" => "text/javascript; charset=utf-8"}]
|
|
28
|
+
]
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Panda
|
|
4
|
+
module Core
|
|
5
|
+
class Engine < ::Rails::Engine
|
|
6
|
+
# OmniAuth configuration
|
|
7
|
+
module OmniauthConfig
|
|
8
|
+
extend ActiveSupport::Concern
|
|
9
|
+
|
|
10
|
+
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
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Panda
|
|
4
|
+
module Core
|
|
5
|
+
class Engine < ::Rails::Engine
|
|
6
|
+
# Phlex configuration
|
|
7
|
+
module PhlexConfig
|
|
8
|
+
extend ActiveSupport::Concern
|
|
9
|
+
|
|
10
|
+
included do
|
|
11
|
+
# Load Phlex base component after Rails application is initialized
|
|
12
|
+
# This ensures Rails.application.routes is available
|
|
13
|
+
initializer "panda_core.phlex_base", after: :load_config_initializers do
|
|
14
|
+
require "phlex"
|
|
15
|
+
require "phlex-rails"
|
|
16
|
+
require "literal"
|
|
17
|
+
require "tailwind_merge"
|
|
18
|
+
|
|
19
|
+
# Load the base component
|
|
20
|
+
require root.join("app/components/panda/core/base")
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Panda
|
|
4
|
+
module Core
|
|
5
|
+
class Engine < ::Rails::Engine
|
|
6
|
+
# Test environment configuration
|
|
7
|
+
module TestConfig
|
|
8
|
+
extend ActiveSupport::Concern
|
|
9
|
+
|
|
10
|
+
included do
|
|
11
|
+
# For testing: Don't expose engine migrations since we use "copy to host app" strategy
|
|
12
|
+
# In test environment, migrations should be copied to the host app
|
|
13
|
+
if Rails.env.test?
|
|
14
|
+
config.paths["db/migrate"] = []
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
data/lib/panda/core/engine.rb
CHANGED
|
@@ -19,131 +19,36 @@ ensure
|
|
|
19
19
|
$stderr = original_stderr
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
+
# Load engine configuration modules
|
|
23
|
+
require_relative "engine/test_config"
|
|
24
|
+
require_relative "engine/autoload_config"
|
|
25
|
+
require_relative "engine/middleware_config"
|
|
26
|
+
require_relative "engine/importmap_config"
|
|
27
|
+
require_relative "engine/omniauth_config"
|
|
28
|
+
require_relative "engine/phlex_config"
|
|
29
|
+
require_relative "engine/admin_controller_config"
|
|
30
|
+
|
|
22
31
|
module Panda
|
|
23
32
|
module Core
|
|
24
33
|
class Engine < ::Rails::Engine
|
|
25
34
|
isolate_namespace Panda::Core
|
|
26
35
|
|
|
27
|
-
#
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
config.paths["db/migrate"] = []
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
config.eager_load_namespaces << Panda::Core::Engine
|
|
36
|
+
# Include shared configuration modules
|
|
37
|
+
include Shared::InflectionsConfig
|
|
38
|
+
include Shared::GeneratorConfig
|
|
34
39
|
|
|
35
|
-
#
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
# Make files in public available to the main app (e.g. /panda-core-assets/panda-logo.png)
|
|
45
|
-
config.middleware.use Rack::Static,
|
|
46
|
-
urls: ["/panda-core-assets"],
|
|
47
|
-
root: Panda::Core::Engine.root.join("public"),
|
|
48
|
-
header_rules: [
|
|
49
|
-
# Disable caching in development for instant CSS updates
|
|
50
|
-
[:all, {"Cache-Control" => Rails.env.development? ? "no-cache, no-store, must-revalidate" : "public, max-age=31536000"}]
|
|
51
|
-
]
|
|
52
|
-
|
|
53
|
-
# Make JavaScript files available for importmap
|
|
54
|
-
# Serve from app/javascript with proper MIME types
|
|
55
|
-
config.middleware.use Rack::Static,
|
|
56
|
-
urls: ["/panda", "/panda/core"],
|
|
57
|
-
root: Panda::Core::Engine.root.join("app/javascript"),
|
|
58
|
-
header_rules: [
|
|
59
|
-
[:all, {"Cache-Control" => Rails.env.development? ? "no-cache, no-store, must-revalidate" : "public, max-age=31536000",
|
|
60
|
-
"Content-Type" => "text/javascript; charset=utf-8"}]
|
|
61
|
-
]
|
|
62
|
-
|
|
63
|
-
config.generators do |g|
|
|
64
|
-
g.test_framework :rspec
|
|
65
|
-
g.fixture_replacement :factory_bot
|
|
66
|
-
g.factory_bot dir: "spec/factories"
|
|
67
|
-
end
|
|
40
|
+
# Include engine-specific configuration modules
|
|
41
|
+
include TestConfig
|
|
42
|
+
include AutoloadConfig
|
|
43
|
+
include MiddlewareConfig
|
|
44
|
+
include ImportmapConfig
|
|
45
|
+
include OmniauthConfig
|
|
46
|
+
include PhlexConfig
|
|
47
|
+
include AdminControllerConfig
|
|
68
48
|
|
|
69
49
|
initializer "panda_core.config" do |app|
|
|
70
50
|
# Configuration is already initialized with defaults in Configuration class
|
|
71
51
|
end
|
|
72
|
-
|
|
73
|
-
# Add importmap paths from the engine
|
|
74
|
-
initializer "panda_core.importmap", before: "importmap" do |app|
|
|
75
|
-
if app.config.respond_to?(:importmap)
|
|
76
|
-
# Create a new array if frozen
|
|
77
|
-
app.config.importmap.paths = app.config.importmap.paths.dup if app.config.importmap.paths.frozen?
|
|
78
|
-
|
|
79
|
-
# Add our paths
|
|
80
|
-
app.config.importmap.paths << root.join("config/importmap.rb")
|
|
81
|
-
|
|
82
|
-
# Handle cache sweepers similarly
|
|
83
|
-
if app.config.importmap.cache_sweepers.frozen?
|
|
84
|
-
app.config.importmap.cache_sweepers = app.config.importmap.cache_sweepers.dup
|
|
85
|
-
end
|
|
86
|
-
app.config.importmap.cache_sweepers << root.join("app/javascript")
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
initializer "panda_core.omniauth" do |app|
|
|
91
|
-
# Load OAuth provider gems
|
|
92
|
-
require_relative "oauth_providers"
|
|
93
|
-
Panda::Core::OAuthProviders.setup
|
|
94
|
-
|
|
95
|
-
# Mount OmniAuth at configurable admin path
|
|
96
|
-
app.middleware.use OmniAuth::Builder do
|
|
97
|
-
# Configure OmniAuth to use the configured admin path
|
|
98
|
-
configure do |config|
|
|
99
|
-
config.path_prefix = "#{Panda::Core.config.admin_path}/auth"
|
|
100
|
-
# POST-only for CSRF protection (CVE-2015-9284)
|
|
101
|
-
# All login forms use POST via form_tag method: "post"
|
|
102
|
-
config.allowed_request_methods = [:post]
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
Panda::Core.config.authentication_providers.each do |provider_name, settings|
|
|
106
|
-
# Build provider options, allowing custom path name override
|
|
107
|
-
provider_options = settings[:options] || {}
|
|
108
|
-
|
|
109
|
-
# If path_name is specified, use it to override the default strategy name in URLs
|
|
110
|
-
if settings[:path_name].present?
|
|
111
|
-
provider_options = provider_options.merge(name: settings[:path_name])
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
case provider_name.to_s
|
|
115
|
-
when "microsoft_graph"
|
|
116
|
-
provider :microsoft_graph, settings[:client_id], settings[:client_secret], provider_options
|
|
117
|
-
when "google_oauth2"
|
|
118
|
-
provider :google_oauth2, settings[:client_id], settings[:client_secret], provider_options
|
|
119
|
-
when "github"
|
|
120
|
-
provider :github, settings[:client_id], settings[:client_secret], provider_options
|
|
121
|
-
when "developer"
|
|
122
|
-
provider :developer if Rails.env.development?
|
|
123
|
-
end
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
# Load Phlex base component after Rails application is initialized
|
|
129
|
-
# This ensures Rails.application.routes is available
|
|
130
|
-
initializer "panda_core.phlex_base", after: :load_config_initializers do
|
|
131
|
-
require "phlex"
|
|
132
|
-
require "phlex-rails"
|
|
133
|
-
require "literal"
|
|
134
|
-
require "tailwind_merge"
|
|
135
|
-
|
|
136
|
-
# Load the base component
|
|
137
|
-
require root.join("app/components/panda/core/base")
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
# Create AdminController alias after controllers are loaded
|
|
141
|
-
# This allows other gems to inherit from Panda::Core::AdminController
|
|
142
|
-
initializer "panda_core.admin_controller_alias", after: :load_config_initializers do
|
|
143
|
-
ActiveSupport.on_load(:action_controller_base) do
|
|
144
|
-
Panda::Core.const_set(:AdminController, Panda::Core::Admin::BaseController) unless Panda::Core.const_defined?(:AdminController)
|
|
145
|
-
end
|
|
146
|
-
end
|
|
147
52
|
end
|
|
148
53
|
end
|
|
149
54
|
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Panda
|
|
4
|
+
module Core
|
|
5
|
+
module Shared
|
|
6
|
+
# Shared generator configuration for all panda gems
|
|
7
|
+
# This ensures consistent generator behavior across the ecosystem
|
|
8
|
+
module GeneratorConfig
|
|
9
|
+
extend ActiveSupport::Concern
|
|
10
|
+
|
|
11
|
+
included do
|
|
12
|
+
config.generators do |g|
|
|
13
|
+
g.orm :active_record, primary_key_type: :uuid
|
|
14
|
+
g.test_framework :rspec, fixture: true
|
|
15
|
+
g.fixture_replacement nil
|
|
16
|
+
g.view_specs false
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Panda
|
|
4
|
+
module Core
|
|
5
|
+
module Shared
|
|
6
|
+
# Shared inflections configuration for all panda gems
|
|
7
|
+
# Ensures consistent constant naming across the ecosystem
|
|
8
|
+
module InflectionsConfig
|
|
9
|
+
extend ActiveSupport::Concern
|
|
10
|
+
|
|
11
|
+
included do
|
|
12
|
+
# Load inflections early to ensure proper constant resolution
|
|
13
|
+
initializer "panda.inflections", before: :load_config_initializers do
|
|
14
|
+
ActiveSupport::Inflector.inflections(:en) do |inflect|
|
|
15
|
+
inflect.acronym "CMS"
|
|
16
|
+
inflect.acronym "SEO"
|
|
17
|
+
inflect.acronym "AI"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
data/lib/panda/core/version.rb
CHANGED
data/lib/panda/core.rb
CHANGED
|
@@ -15,4 +15,6 @@ require_relative "core/configuration"
|
|
|
15
15
|
require_relative "core/asset_loader"
|
|
16
16
|
require_relative "core/debug"
|
|
17
17
|
require_relative "core/services/base_service"
|
|
18
|
+
require_relative "core/shared/inflections_config"
|
|
19
|
+
require_relative "core/shared/generator_config"
|
|
18
20
|
require_relative "core/engine" if defined?(Rails)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: panda-core
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.7.
|
|
4
|
+
version: 0.7.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Otaina Limited
|
|
@@ -10,6 +10,20 @@ bindir: bin
|
|
|
10
10
|
cert_chain: []
|
|
11
11
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: image_processing
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.2'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.2'
|
|
13
27
|
- !ruby/object:Gem::Dependency
|
|
14
28
|
name: importmap-rails
|
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -461,6 +475,15 @@ files:
|
|
|
461
475
|
- lib/panda/core/configuration.rb
|
|
462
476
|
- lib/panda/core/debug.rb
|
|
463
477
|
- lib/panda/core/engine.rb
|
|
478
|
+
- lib/panda/core/engine/admin_controller_config.rb
|
|
479
|
+
- lib/panda/core/engine/autoload_config.rb
|
|
480
|
+
- lib/panda/core/engine/generator_config.rb
|
|
481
|
+
- lib/panda/core/engine/importmap_config.rb
|
|
482
|
+
- lib/panda/core/engine/inflections_config.rb
|
|
483
|
+
- lib/panda/core/engine/middleware_config.rb
|
|
484
|
+
- lib/panda/core/engine/omniauth_config.rb
|
|
485
|
+
- lib/panda/core/engine/phlex_config.rb
|
|
486
|
+
- lib/panda/core/engine/test_config.rb
|
|
464
487
|
- lib/panda/core/media.rb
|
|
465
488
|
- lib/panda/core/notifications.rb
|
|
466
489
|
- lib/panda/core/oauth_providers.rb
|
|
@@ -468,6 +491,8 @@ files:
|
|
|
468
491
|
- lib/panda/core/rake_tasks.rb
|
|
469
492
|
- lib/panda/core/seo.rb
|
|
470
493
|
- lib/panda/core/services/base_service.rb
|
|
494
|
+
- lib/panda/core/shared/generator_config.rb
|
|
495
|
+
- lib/panda/core/shared/inflections_config.rb
|
|
471
496
|
- lib/panda/core/sluggable.rb
|
|
472
497
|
- lib/panda/core/subscribers/authentication_subscriber.rb
|
|
473
498
|
- lib/panda/core/testing/rails_helper.rb
|