spree_square 0.1.1 → 0.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7e77dabce99dbae4bf55167dc863b5f8a01f49cdeeff5bdd1cf05b34ef55773f
4
- data.tar.gz: '0709cf13b8ba11aecacae6314937acde7d98994a4f62982dbbcc5d37c120785b'
3
+ metadata.gz: 199628697d1e2ef689462c4e4895ec7a90a271e5663cb3de8777533b15cdee50
4
+ data.tar.gz: 4d0137620c684345e4c4fa32bc8b75381d45e4992f773174a60ea0009ab4d55b
5
5
  SHA512:
6
- metadata.gz: a3290d9b771ac252b623f0180a1a8e16f5b7e679172cf9c2690c664b6129cde31e3eec13dd8f413f8f1bf85334755e00a04fd3b71eb89e895e1a0eb1cb5afe2e
7
- data.tar.gz: 777e7a21465fd95d3e5e32eaeacc4a270f17be68a1e752ec7f5414794e338b0ae96ccae17eec73b73e0ee60105e4763da42672a7026cded812959fe8656bb171
6
+ metadata.gz: 96d1f3c46e20844a731be1d12a1616b2cd9f06d1be0160122615f491b5fc40ce6a0e865cb14eeb373c93b1c1e3f5e8fa460d0933c978f93fcc380d53576aabc7
7
+ data.tar.gz: 072bdfa3d7dee63493118d655ffaaa3df3e55cf9dc4dcddf39b6cb006057ef52218a2851f6444a2ea461de5bcaab3ef8c82edc36e8a589501ffa20defbe592df
data/CHANGELOG.md CHANGED
@@ -2,6 +2,27 @@
2
2
 
3
3
  All notable changes to this project are documented here.
4
4
 
5
+ ## 0.1.2
6
+
7
+ - Two new demo menu items (a build-your-own bowl with stacked modifier lists, a combo meal with
8
+ entree/drink/side) with real Square modifier lists — the first end-to-end proof the modifier
9
+ system renders against real Square catalog data, not just specs.
10
+ - Fixed a real bug: removing a modifier-bearing line item from the cart raised a foreign-key
11
+ violation (`spree_square_line_item_modifiers` had no `dependent: :destroy`).
12
+ - Fixed a real bug: the Square Orders / Square Webhooks admin pages 500'd
13
+ (`undefined method 'new_admin_square_order_mapping_url'`) the moment either table was empty —
14
+ `Spree.admin.tables.register` defaults to expecting a `:new` route that doesn't exist on these
15
+ read-only, index-only resources. Added `new_resource: false` to both.
16
+ - Fixed a real bug: installing on MySQL couldn't even run migrations — MySQL rejects a literal
17
+ `DEFAULT` value on `JSON`/`TEXT`/`BLOB` columns outright, which canceled every migration after
18
+ `CreateSpreeSquareWebhookEvents`/`CreateSpreeSquareCredentials` in migration order. Moved both
19
+ defaults (`WebhookEvent#payload`, `Credential#scopes`) to the model layer instead, verified
20
+ against a real MySQL 8.0 database.
21
+ - Fixed a Brakeman warning: `WebhooksController` never actually had `protect_from_forgery`
22
+ configured (it doesn't inherit the host app's `ApplicationController`). Added it explicitly
23
+ with `:null_session` — correct for a signature-verified webhook endpoint with no session to
24
+ protect; `:exception` would break every real webhook delivery.
25
+
5
26
  ## 0.1.1
6
27
 
7
28
  - OAuth now requests `PAYMENTS_READ` in addition to the existing scopes. Without it, reading
@@ -4,7 +4,15 @@ module SpreeSquare
4
4
  # work synchronously: verify, record, ack, hand off to a job. Square treats
5
5
  # a slow or non-2xx response as a delivery failure and retries.
6
6
  class WebhooksController < ActionController::Base
7
- skip_before_action :verify_authenticity_token, raise: false
7
+ # Explicit, not just `skip_before_action :verify_authenticity_token`
8
+ # this controller doesn't inherit the host app's ApplicationController
9
+ # (which is where `protect_from_forgery` normally gets declared), so
10
+ # Brakeman's ForgerySetting check correctly flags it as never actually
11
+ # configured either way. `:null_session` degrades a forged/missing
12
+ # token to an empty session instead of raising — appropriate here since
13
+ # this endpoint is Square-signature-verified, not session-authenticated,
14
+ # so there's no session to protect in the first place.
15
+ protect_from_forgery with: :null_session
8
16
 
9
17
  def create
10
18
  raw_body = request.raw_post
@@ -5,6 +5,17 @@ module Spree
5
5
  # SpreeSquare-prefixed one) means nothing ever triggers loading this file
6
6
  # at all, silently, since nothing references the name Zeitwerk expects.
7
7
  module LineItemDecorator
8
+ def self.prepended(base)
9
+ # No `dependent: :destroy` here originally meant removing a line item
10
+ # that had modifier selections hit a foreign-key violation instead of
11
+ # actually removing it (spree_square_line_item_modifiers.line_item_id
12
+ # has no ON DELETE behavior beyond Postgres's RESTRICT default) — only
13
+ # surfaced once a real modifier-bearing item was added to a cart and
14
+ # then removed, which nothing exercised before.
15
+ base.has_many :square_line_item_modifiers, class_name: 'SpreeSquare::LineItemModifier',
16
+ foreign_key: 'line_item_id', dependent: :destroy
17
+ end
18
+
8
19
  # Transient carrier for selected modifier ids from add-to-cart through to
9
20
  # SpreeSquare::Cart::AddItem, which reads it right after `super` to build
10
21
  # the persistent LineItemModifier snapshot rows. Never persisted itself —
@@ -17,6 +17,13 @@ module SpreeSquare
17
17
 
18
18
  belongs_to :store, class_name: 'Spree::Store'
19
19
 
20
+ # Ruby-level, not a DB-level `default: []` on the migration — MySQL
21
+ # rejects a literal DEFAULT on a JSON column outright (see that
22
+ # migration's own comment). The :square_credential factory relies on
23
+ # this (never sets `scopes` explicitly), so it has to actually default
24
+ # to an empty array, not just avoid raising.
25
+ attribute :scopes, default: -> { [] }
26
+
20
27
  encrypts :access_token, :refresh_token
21
28
 
22
29
  validates :store, presence: true, uniqueness: true
@@ -5,6 +5,11 @@ module SpreeSquare
5
5
  class WebhookEvent < Spree.base_class
6
6
  self.table_name = 'spree_square_webhook_events'
7
7
 
8
+ # Ruby-level, not a DB-level `default: {}` on the migration — MySQL
9
+ # rejects a literal DEFAULT on a JSON column outright (see that
10
+ # migration's own comment). This works identically on every adapter.
11
+ attribute :payload, default: -> { {} }
12
+
8
13
  validates :square_event_id, presence: true, uniqueness: true
9
14
  validates :event_type, presence: true
10
15
 
@@ -0,0 +1,28 @@
1
+ {
2
+ "ignored_warnings": [
3
+ {
4
+ "warning_type": "Cross-Site Request Forgery",
5
+ "warning_code": 86,
6
+ "fingerprint": "1bed78a0df37b0520a169a67b62ba7df8b4af42e6f12844ac923bfde7958fc14",
7
+ "check_name": "ForgerySetting",
8
+ "message": "`protect_from_forgery` should be configured with `with: :exception`",
9
+ "file": "app/controllers/spree_square/webhooks_controller.rb",
10
+ "line": 15,
11
+ "link": "https://brakemanscanner.org/docs/warning_types/cross-site_request_forgery/",
12
+ "code": "protect_from_forgery(:with => :null_session)",
13
+ "render_path": null,
14
+ "location": {
15
+ "type": "controller",
16
+ "controller": "SpreeSquare::WebhooksController"
17
+ },
18
+ "user_input": null,
19
+ "confidence": "Medium",
20
+ "cwe_id": [
21
+ 352
22
+ ],
23
+ "note": "Intentional, not a false positive to fix: this is a webhook endpoint authenticated by Square's HMAC signature (SpreeSquare::WebhookVerifier), not by a Rails session, so there's no session-based state for CSRF to protect in the first place. `:exception` (Brakeman's preferred default) would make every legitimate webhook POST raise ActionController::InvalidAuthenticityToken, since Square never sends a Rails CSRF token — that would break the endpoint entirely, not harden it. `:null_session` is the correct, standard Rails pattern for an unauthenticated-by-session API/webhook endpoint."
24
+ }
25
+ ],
26
+ "updated": "2026-08-13 23:32:22 +0530",
27
+ "brakeman_version": "8.0.5"
28
+ }
@@ -1,5 +1,13 @@
1
1
  Rails.application.config.after_initialize do
2
- Spree.admin.tables.register(:square_order_mappings, model_class: SpreeSquare::OrderMapping, search_param: :square_order_id_cont)
2
+ # new_resource: false read-only support/diagnostic tables (no
3
+ # `:new`/`:create` route exists; only: [:index] in config/routes.rb). The
4
+ # default (true) crashes with a routing error the moment the table is
5
+ # ever empty, because the "no resource found" empty-state partial builds
6
+ # a `new_object_url` link unconditionally unless told not to. Found live
7
+ # via the sibling spree_doordash gem's own admin pages, which hit the
8
+ # same bug and fixed it in spree_admin_doordash_tables.rb.
9
+ Spree.admin.tables.register(:square_order_mappings, model_class: SpreeSquare::OrderMapping,
10
+ search_param: :square_order_id_cont, new_resource: false)
3
11
 
4
12
  Spree.admin.tables.square_order_mappings.add :order_number,
5
13
  label: :order,
@@ -42,7 +50,8 @@ Rails.application.config.after_initialize do
42
50
  default: true,
43
51
  position: 50
44
52
 
45
- Spree.admin.tables.register(:square_webhook_events, model_class: SpreeSquare::WebhookEvent, search_param: :event_type_cont)
53
+ Spree.admin.tables.register(:square_webhook_events, model_class: SpreeSquare::WebhookEvent,
54
+ search_param: :event_type_cont, new_resource: false)
46
55
 
47
56
  Spree.admin.tables.square_webhook_events.add :event_type,
48
57
  label: :event_type,
@@ -12,7 +12,17 @@ class CreateSpreeSquareWebhookEvents < ActiveRecord::Migration[8.1]
12
12
  # JSON querying — application code only ever reads/writes it as a
13
13
  # plain Ruby hash, so the Postgres jsonb-vs-json performance distinction
14
14
  # doesn't apply.
15
- t.json :payload, null: false, default: {}
15
+ #
16
+ # No `default: {}` here — MySQL rejects a literal DEFAULT on
17
+ # BLOB/TEXT/GEOMETRY/JSON columns outright ("BLOB, TEXT, GEOMETRY or
18
+ # JSON column 'payload' can't have a default value"), which silently
19
+ # canceled every migration after this one in CI's MySQL job — every
20
+ # spree_square_* table after this one in migration order was just
21
+ # missing. Found via a real MySQL CI failure, not from docs. The
22
+ # default now lives on the model instead (see WebhookEvent's own
23
+ # `attribute :payload, default: -> { {} }`), which works identically
24
+ # across every adapter.
25
+ t.json :payload, null: false
16
26
  t.datetime :processed_at
17
27
  t.string :status, null: false, default: 'pending' # pending, processed, failed
18
28
  t.text :error_message
@@ -18,7 +18,14 @@ class CreateSpreeSquareCredentials < ActiveRecord::Migration[8.1]
18
18
  t.datetime :refresh_token_expires_at
19
19
  # `t.json`, not `t.jsonb`/`array: true` — the extension's own dummy
20
20
  # app (spec/dummy) runs on SQLite, which supports neither.
21
- t.json :scopes, default: [], null: false
21
+ #
22
+ # No `default: []` here — MySQL rejects a literal DEFAULT on JSON
23
+ # columns entirely, which canceled every migration after this one in
24
+ # CI's MySQL job. Default now lives on the model instead (see
25
+ # Credential's own `attribute :scopes, default: -> { [] }`) — the
26
+ # :square_credential factory relies on this default (never sets
27
+ # `scopes` explicitly), so this had to move, not just disappear.
28
+ t.json :scopes, null: false
22
29
 
23
30
  t.timestamps
24
31
  end
@@ -1,5 +1,5 @@
1
1
  module SpreeSquare
2
- VERSION = '0.1.1'.freeze
2
+ VERSION = '0.1.2'.freeze
3
3
 
4
4
  def gem_version
5
5
  Gem::Version.new(VERSION)
@@ -59,6 +59,42 @@ namespace :spree_square do
59
59
  menu.each do |category_name, items|
60
60
  items.each do |item|
61
61
  item_temp_id = "#item-#{item['image_slug']}"
62
+
63
+ # Modifier lists (Square has no true nested-modifier concept — a
64
+ # "build your own" item is just several independent modifier lists
65
+ # stacked on one item, each its own CatalogObject nested inline the
66
+ # same way ITEM_VARIATION is nested under item_data.variations
67
+ # above). modifier_list_info is what actually attaches a list to
68
+ # this item; the MODIFIER_LIST objects themselves go in the same
69
+ # top-level `objects` array as everything else in the batch.
70
+ modifier_list_info = Array(item['modifier_lists']).map.with_index do |mod_list, list_index|
71
+ list_temp_id = "#{item_temp_id}-modlist-#{list_index}"
72
+ objects << {
73
+ type: 'MODIFIER_LIST',
74
+ id: list_temp_id,
75
+ modifier_list_data: {
76
+ name: mod_list['name'],
77
+ selection_type: mod_list['selection_type'],
78
+ modifiers: mod_list['options'].map.with_index do |option, option_index|
79
+ {
80
+ type: 'MODIFIER',
81
+ id: "#{list_temp_id}-opt-#{option_index}",
82
+ modifier_data: {
83
+ name: option['name'],
84
+ price_money: { amount: option['price_cents'], currency: 'USD' }
85
+ }
86
+ }
87
+ end
88
+ }
89
+ }
90
+ {
91
+ modifier_list_id: list_temp_id,
92
+ min_selected_modifiers: mod_list['min_selected'],
93
+ max_selected_modifiers: mod_list['max_selected'],
94
+ enabled: true
95
+ }
96
+ end
97
+
62
98
  objects << {
63
99
  type: 'ITEM',
64
100
  id: item_temp_id,
@@ -82,8 +118,9 @@ namespace :spree_square do
82
118
  price_money: { amount: item['price_cents'], currency: 'USD' }
83
119
  }
84
120
  }
85
- ]
86
- }
121
+ ],
122
+ modifier_list_info: modifier_list_info
123
+ }.compact_blank
87
124
  }
88
125
  end
89
126
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spree_square
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Amit Solanki
@@ -164,6 +164,7 @@ files:
164
164
  - app/views/spree/admin/square_webhook_events/index.html.erb
165
165
  - bin/importmap
166
166
  - bin/rails
167
+ - config/brakeman.ignore
167
168
  - config/importmap.rb
168
169
  - config/initializers/spree.rb
169
170
  - config/initializers/spree_admin_square_navigation.rb