spree_dashboard 5.6.0 → 6.0.0.beta1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b5b17eac49a94a1eb174e727b7e9b1326ef8ee9bf7cd333d743f74daa236f756
4
- data.tar.gz: fa6829074bc825dd8fc4d0be95ad87c8de0ce3996169c20e61d8e3e6904216b1
3
+ metadata.gz: 28547ad04b0d9b667f01c7b88bb38bcb764a0c59130a6c7e5c37400306ff173c
4
+ data.tar.gz: ffc9d7d8ac62ce7f67348e3aee068842733b3f9c072d6ec3f834f0627960bb66
5
5
  SHA512:
6
- metadata.gz: e0049da7bbfdefb533f56378474a7868b5cd639282a136510b9ae6d4796da48c269a9c0e182b304c86143ab5e0d40ea4d2abd66ea5bcf8225ffd78745db07638
7
- data.tar.gz: 640fd44cbb5c8d7a55a31b47d227c88adf305c864651b8e6996da7722034c32df54446cb107513cc682ecde3660686aac89000aa24da3a76d3c28721f4d48db3
6
+ metadata.gz: 4f98563d15ff2b1849f5b263e5b4606da7993d77498dd490f48a2ba3a14c22ca5d70ff6187ac0ae7698f2ae1189e197ed0896202009f6dbae3c15d37889567ce
7
+ data.tar.gz: 8a66bfb48de8cec16458e267b558327c143923c359f7fd6efa16ce305fb7ef8c790c93211ce554559afd3a61672498715639054c959485b532b4058660d66412
data/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026-present, Vendo Sp. z o.o., Vendo Connect Inc.
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice,
9
+ this list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -1,77 +1,16 @@
1
1
  module Spree
2
2
  module Dashboard
3
- # Serves a built React Dashboard (`vite build` output) at /dashboard the
4
- # single-node topology: the SPA and the Admin API share one origin, so no
5
- # CORS entries and Lax cookies. The dist directory comes from
6
- # `Spree::Dashboard.dist_path` (the official Docker image sets it via
7
- # SPREE_DASHBOARD_DIST_PATH); when unset, /dashboard 404s.
3
+ # Serves the built React Dashboard at /dashboard. Everything about how a
4
+ # bundle is served lives in SpaController; this names which bundle.
8
5
  #
9
- # SPA semantics: real files are served with long-lived caching (Vite's
10
- # `assets/` are content-hashed, so they're immutable); every other path
11
- # falls back to index.html with no-cache so deploys take effect on the
12
- # next navigation. No authentication — the bundle is public client code;
13
- # the SPA authenticates its API calls itself.
14
- class AppController < ActionController::Base
15
- # GET-only file serving mutates nothing, but enable forgery protection
16
- # anyway so the controller stays safe if an action is ever added.
17
- protect_from_forgery with: :exception
18
- # `verify_same_origin_request` blocks JavaScript responses to plain GET
19
- # requests — protection against JSONP-style data leaks from dynamically
20
- # generated JS. The Vite bundle is static public code that the SPA's own
21
- # <script> tags must load, which is exactly that request shape.
22
- skip_after_action :verify_same_origin_request
23
-
24
- def show
25
- root = dist_root
26
- return head :not_found unless root
27
-
28
- if (file = resolve_file(root, params[:path].to_s))
29
- response.headers['Cache-Control'] = cache_control_for(params[:path].to_s)
30
- send_file file, disposition: 'inline'
31
- elsif (index = root.join('index.html')).file?
32
- response.headers['Cache-Control'] = 'no-cache'
33
- send_file index, type: 'text/html', disposition: 'inline'
34
- else
35
- head :not_found
36
- end
37
- end
38
-
6
+ # The dist directory comes from `Spree::Dashboard.dist_path` (the official
7
+ # Docker image sets it via SPREE_DASHBOARD_DIST_PATH); when unset,
8
+ # /dashboard 404s.
9
+ class AppController < SpaController
39
10
  private
40
11
 
41
- def dist_root
42
- path = Spree::Dashboard.dist_path
43
- return if path.blank?
44
-
45
- root = Pathname.new(path).expand_path
46
- root if root.directory?
47
- end
48
-
49
- # Resolve a request path to a real file inside the dist directory.
50
- # Defense in depth against traversal: reject null bytes, absolute
51
- # paths, and any `.`/`..` segment before touching the filesystem, then
52
- # verify the expanded path still lives under the dist root (which also
53
- # covers symlinks pointing outside it).
54
- def resolve_file(root, relative_path)
55
- return if relative_path.blank? || relative_path.include?("\0")
56
-
57
- relative = Pathname.new(relative_path)
58
- return if relative.absolute?
59
-
60
- segments = relative.each_filename.to_a
61
- return if segments.empty? || segments.any? { |segment| segment == '.' || segment == '..' }
62
-
63
- candidate = root.join(*segments).expand_path
64
- return unless candidate.to_s.start_with?("#{root}#{File::SEPARATOR}")
65
-
66
- candidate if candidate.file?
67
- end
68
-
69
- def cache_control_for(relative_path)
70
- if relative_path.start_with?('assets/')
71
- 'public, max-age=31536000, immutable'
72
- else
73
- 'public, max-age=3600'
74
- end
12
+ def dist_path
13
+ Spree::Dashboard.dist_path
75
14
  end
76
15
  end
77
16
  end
@@ -0,0 +1,86 @@
1
+ module Spree
2
+ module Dashboard
3
+ # Serves a built Vite SPA from a directory on disk — the single-node
4
+ # topology, where the SPA and the API it calls share one origin, so no
5
+ # CORS entries and Lax cookies.
6
+ #
7
+ # Abstract: a subclass names the directory by implementing `dist_path`.
8
+ # Two panels ride on this — the operator's dashboard at /dashboard and the
9
+ # marketplace seller panel at /sellers — and they differ in nothing but
10
+ # that directory and their mount point.
11
+ #
12
+ # SPA semantics: real files are served with long-lived caching (Vite's
13
+ # `assets/` are content-hashed, so they're immutable); every other path
14
+ # falls back to index.html with no-cache so deploys take effect on the
15
+ # next navigation. No authentication — the bundle is public client code;
16
+ # the SPA authenticates its API calls itself.
17
+ class SpaController < ActionController::Base
18
+ # GET-only file serving mutates nothing, but enable forgery protection
19
+ # anyway so the controller stays safe if an action is ever added.
20
+ protect_from_forgery with: :exception
21
+ # `verify_same_origin_request` blocks JavaScript responses to plain GET
22
+ # requests — protection against JSONP-style data leaks from dynamically
23
+ # generated JS. The Vite bundle is static public code that the SPA's own
24
+ # <script> tags must load, which is exactly that request shape.
25
+ skip_after_action :verify_same_origin_request
26
+
27
+ def show
28
+ root = dist_root
29
+ return head :not_found unless root
30
+
31
+ if (file = resolve_file(root, params[:path].to_s))
32
+ response.headers['Cache-Control'] = cache_control_for(params[:path].to_s)
33
+ send_file file, disposition: 'inline'
34
+ elsif (index = root.join('index.html')).file?
35
+ response.headers['Cache-Control'] = 'no-cache'
36
+ send_file index, type: 'text/html', disposition: 'inline'
37
+ else
38
+ head :not_found
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ # @return [String, nil] directory holding the built bundle
45
+ def dist_path
46
+ raise NotImplementedError, "#{self.class} must implement #dist_path"
47
+ end
48
+
49
+ def dist_root
50
+ path = dist_path
51
+ return if path.blank?
52
+
53
+ root = Pathname.new(path).expand_path
54
+ root if root.directory?
55
+ end
56
+
57
+ # Resolve a request path to a real file inside the dist directory.
58
+ # Defense in depth against traversal: reject null bytes, absolute
59
+ # paths, and any `.`/`..` segment before touching the filesystem, then
60
+ # verify the expanded path still lives under the dist root (which also
61
+ # covers symlinks pointing outside it).
62
+ def resolve_file(root, relative_path)
63
+ return if relative_path.blank? || relative_path.include?("\0")
64
+
65
+ relative = Pathname.new(relative_path)
66
+ return if relative.absolute?
67
+
68
+ segments = relative.each_filename.to_a
69
+ return if segments.empty? || segments.any? { |segment| segment == '.' || segment == '..' }
70
+
71
+ candidate = root.join(*segments).expand_path
72
+ return unless candidate.to_s.start_with?("#{root}#{File::SEPARATOR}")
73
+
74
+ candidate if candidate.file?
75
+ end
76
+
77
+ def cache_control_for(relative_path)
78
+ if relative_path.start_with?('assets/')
79
+ 'public, max-age=31536000, immutable'
80
+ else
81
+ 'public, max-age=3600'
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,18 @@
1
+ module Spree
2
+ module SellerPanel
3
+ # Serves the built marketplace seller panel at /sellers.
4
+ #
5
+ # Same origin as the dashboard and the API on purpose: the two panels'
6
+ # refresh cookies are already isolated by name and path, so sharing a host
7
+ # saves every deployment a second hostname and certificate. A marketplace
8
+ # that wants sellers on their own domain deploys the bundle there and
9
+ # points SPREE_SELLER_PANEL_URL at it.
10
+ class AppController < Spree::Dashboard::SpaController
11
+ private
12
+
13
+ def dist_path
14
+ Spree::Dashboard.seller_panel_dist_path
15
+ end
16
+ end
17
+ end
18
+ end
data/config/routes.rb CHANGED
@@ -5,4 +5,10 @@ Spree::Core::Engine.add_routes do
5
5
  # being parsed as a response format.
6
6
  get '/dashboard', to: 'dashboard/app#show', as: :dashboard_app
7
7
  get '/dashboard/*path', to: 'dashboard/app#show', format: false
8
+
9
+ # Hosted seller panel, same topology as the dashboard above. Its own mount
10
+ # rather than a nested route: it is a separate app with its own bundle, and
11
+ # a marketplace that runs no seller panel leaves it unconfigured.
12
+ get '/sellers', to: 'seller_panel/app#show', as: :seller_panel_app
13
+ get '/sellers/*path', to: 'seller_panel/app#show', format: false
8
14
  end
@@ -12,6 +12,17 @@ module Spree
12
12
  def dist_path
13
13
  @dist_path.presence || ENV.fetch('SPREE_DASHBOARD_DIST_PATH', nil)
14
14
  end
15
+
16
+ # Directory holding a built marketplace seller panel, served at
17
+ # /sellers. A separate bundle rather than the dashboard's: it is a
18
+ # different app — its own entry point, router and API client — so
19
+ # pointing both mounts at one directory would serve sellers the
20
+ # operator's back office. Unset, /sellers responds 404.
21
+ attr_writer :seller_panel_dist_path
22
+
23
+ def seller_panel_dist_path
24
+ @seller_panel_dist_path.presence || ENV.fetch('SPREE_SELLER_PANEL_DIST_PATH', nil)
25
+ end
15
26
  end
16
27
  end
17
28
  end
metadata CHANGED
@@ -1,40 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spree_dashboard
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.6.0
4
+ version: 6.0.0.beta1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vendo Connect Inc.
8
+ - Vendo Sp. z o.o.
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2026-07-23 00:00:00.000000000 Z
12
+ date: 2026-09-15 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: spree
15
16
  requirement: !ruby/object:Gem::Requirement
16
17
  requirements:
17
- - - ">="
18
+ - - '='
18
19
  - !ruby/object:Gem::Version
19
- version: 5.6.0
20
+ version: 6.0.0.beta1
20
21
  type: :runtime
21
22
  prerelease: false
22
23
  version_requirements: !ruby/object:Gem::Requirement
23
24
  requirements:
24
- - - ">="
25
+ - - '='
25
26
  - !ruby/object:Gem::Version
26
- version: 5.6.0
27
- description: Serves a built Spree React Dashboard at /dashboard with SPA semantics
28
- the single-node topology where the dashboard and the Admin API share one origin.
29
- Developer Preview; becomes the default admin delivery in Spree 6.
27
+ version: 6.0.0.beta1
28
+ description: Serves a built Spree React Dashboard with your Rails server, simplest
29
+ way to deploy Spree API + Dashboard
30
30
  email: hello@spreecommerce.org
31
31
  executables: []
32
32
  extensions: []
33
33
  extra_rdoc_files: []
34
34
  files:
35
+ - LICENSE
35
36
  - README.md
36
37
  - Rakefile
37
38
  - app/controllers/spree/dashboard/app_controller.rb
39
+ - app/controllers/spree/dashboard/spa_controller.rb
40
+ - app/controllers/spree/seller_panel/app_controller.rb
38
41
  - config/routes.rb
39
42
  - lib/spree/dashboard.rb
40
43
  - lib/spree/dashboard/engine.rb
@@ -44,9 +47,9 @@ licenses:
44
47
  - BSD-3-Clause
45
48
  metadata:
46
49
  bug_tracker_uri: https://github.com/spree/spree/issues
47
- changelog_uri: https://github.com/spree/spree/releases/tag/v5.6.0
50
+ changelog_uri: https://github.com/spree/spree/releases/tag/v6.0.0.beta1
48
51
  documentation_uri: https://spreecommerce.org/docs/developer/dashboard/deployment
49
- source_code_uri: https://github.com/spree/spree/tree/v5.6.0
52
+ source_code_uri: https://github.com/spree/spree/tree/v6.0.0.beta1
50
53
  post_install_message:
51
54
  rdoc_options: []
52
55
  require_paths: