Merge latest master into julesemmac/6584-map-location-confirm and resolve conflict in api/enterprises_controller.rb

This commit is contained in:
Cillian O'Ruanaidh
2021-04-22 19:43:49 +01:00
594 changed files with 17866 additions and 5598 deletions

View File

@@ -2,9 +2,9 @@ version: "2"
plugins:
rubocop:
enabled: true
channel: "rubocop-0-76"
channel: "rubocop-1-12"
config:
file: ".rubocop_styleguide.yml"
file: ".rubocop.yml"
scss-lint:
enabled: true
checks:

View File

@@ -8,8 +8,53 @@ on:
env:
DISABLE_KNAPSACK: true
TIMEZONE: UTC
COVERAGE: true
jobs:
test-controllers-and-serializers:
runs-on: ubuntu-18.04
services:
postgres:
image: postgres:10
ports: ["5432:5432"]
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
POSTGRES_DB: open_food_network_test
POSTGRES_USER: ofn
POSTGRES_PASSWORD: f00d
steps:
- uses: actions/checkout@v2
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- uses: actions/setup-node@v2
with:
node-version: '14.15.5'
- name: Install JS dependencies
run: yarn install --frozen-lockfile
- name: Set up application.yml
run: cp config/application.yml.example config/application.yml
- name: Set up database
run: |
bundle exec rake db:create RAILS_ENV=test
bundle exec rake db:schema:load RAILS_ENV=test
- name: Run controller tests
run: bundle exec rspec --profile -- spec/controllers spec/serializers
- name: Codecov
uses: codecov/codecov-action@v1.3.1
test-models:
runs-on: ubuntu-18.04
services:
@@ -49,9 +94,12 @@ jobs:
bundle exec rake db:schema:load RAILS_ENV=test
- name: Run tests
run: bundle exec rspec spec/models
run: bundle exec rspec --profile -- spec/models
test-admin-features:
- name: Codecov
uses: codecov/codecov-action@v1.3.1
test-admin-features-1:
runs-on: ubuntu-18.04
services:
postgres:
@@ -90,9 +138,12 @@ jobs:
bundle exec rake db:schema:load RAILS_ENV=test
- name: Run admin feature tests
run: bundle exec rspec --profile -- spec/features/admin/*_spec.rb
run: bundle exec rspec --profile -- spec/features/admin/[a-o0-9]*_spec.rb
test-admin-features-folders:
- name: Codecov
uses: codecov/codecov-action@v1.3.1
test-admin-features-2:
runs-on: ubuntu-18.04
services:
postgres:
@@ -131,7 +182,10 @@ jobs:
bundle exec rake db:schema:load RAILS_ENV=test
- name: Run admin feature tests
run: bundle exec rspec --profile --pattern "spec/features/admin/*/*_spec.rb"
run: bundle exec rspec --profile -- spec/features/admin/[p-z]*_spec.rb
- name: Codecov
uses: codecov/codecov-action@v1.3.1
test-consumer-features:
runs-on: ubuntu-18.04
@@ -174,48 +228,10 @@ jobs:
- name: Run consumer feature tests
run: bundle exec rspec --profile -- spec/features/consumer
test-controllers:
runs-on: ubuntu-18.04
services:
postgres:
image: postgres:10
ports: ["5432:5432"]
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
POSTGRES_DB: open_food_network_test
POSTGRES_USER: ofn
POSTGRES_PASSWORD: f00d
steps:
- uses: actions/checkout@v2
- name: Codecov
uses: codecov/codecov-action@v1.3.1
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- uses: actions/setup-node@v2
with:
node-version: '14.15.5'
- name: Install JS dependencies
run: yarn install --frozen-lockfile
- name: Set up application.yml
run: cp config/application.yml.example config/application.yml
- name: Set up database
run: |
bundle exec rake db:create RAILS_ENV=test
bundle exec rake db:schema:load RAILS_ENV=test
- name: Run tests
run: bundle exec rspec spec/controllers
test-other:
test-engines-etc:
runs-on: ubuntu-18.04
services:
postgres:
@@ -256,20 +272,54 @@ jobs:
- name: Run JS tests
run: RAILS_ENV=test bundle exec rake karma:run
- name: Run all other tests
run: |
bundle exec rspec \
spec/helpers/ \
spec/initializers/ \
spec/jobs/ \
spec/lib/ \
spec/mailers/ \
spec/queries/ \
spec/requests/ \
spec/serializers/ \
spec/services/ \
spec/validators/ \
spec/views
# Migration tests need to be run in a separate task.
# See: https://github.com/openfoodfoundation/openfoodnetwork/pull/6924#issuecomment-813056525
- name: Run migration tests
run: bundle exec rspec --pattern "spec/{migrations}/**/*_spec.rb"
- name: Run engines tests
run: bundle exec rake ofn:specs:engines:rspec
- name: Run all other tests
run: bundle exec rake ofn:specs:run:excluding_folders["models,controllers,serializers,features,lib,migrations"]
test-the-rest:
runs-on: ubuntu-18.04
services:
postgres:
image: postgres:10
ports: ["5432:5432"]
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
POSTGRES_DB: open_food_network_test
POSTGRES_USER: ofn
POSTGRES_PASSWORD: f00d
steps:
- uses: actions/checkout@v2
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- uses: actions/setup-node@v2
with:
node-version: '14.15.5'
- name: Install JS dependencies
run: yarn install --frozen-lockfile
- name: Set up application.yml
run: cp config/application.yml.example config/application.yml
- name: Set up database
run: |
bundle exec rake db:create RAILS_ENV=test
bundle exec rake db:schema:load RAILS_ENV=test
- name: Run admin feature folders, engines, lib
run: bundle exec rspec --profile --pattern "engines/*/spec/{,/*/**}/*_spec.rb,spec/features/admin/*/*_spec.rb,spec/lib/{,/*/**}/*_spec.rb"
- name: Codecov
uses: codecov/codecov-action@v1.3.1

1
.gitignore vendored
View File

@@ -47,3 +47,4 @@ coverage
/reports/
!/reports/README.md
bin/
/spec/components/stories/**/*.stories.json

View File

@@ -10,8 +10,8 @@ inherit_from:
# The automatically generated todo list to ignore all current violations.
- .rubocop_todo.yml
# Our Open Food Network style guide. It's used by Code Climate. If you want to see all violations,
# then use only that configuration (like Code Climate):
# Our Open Food Network style guide. If you want to see all violations,
# then use only that configuration:
#
# bundle exec rubocop -c .rubocop_styleguide.yml
#

View File

@@ -21,6 +21,25 @@
Layout/LineLength:
Max: 100
Exclude:
- app/controllers/spree/admin/products_controller.rb
- app/controllers/spree/admin/reports_controller.rb
- app/controllers/spree/paypal_controller.rb
- engines/order_management/spec/services/order_management/order/stripe_sca_payment_authorize_spec.rb
- engines/order_management/spec/services/order_management/order/updater_spec.rb
- engines/order_management/spec/services/order_management/reports/bulk_coop/bulk_coop_report_spec.rb
- lib/open_food_network/reports/line_items.rb
- spec/controllers/spree/admin/orders/invoices_spec.rb
- spec/controllers/spree/admin/tax_rates_controller_spec.rb
- spec/controllers/user_passwords_controller_spec.rb
- spec/features/admin/configuration/general_settings_spec.rb
- spec/features/consumer/shopping/unit_price_spec.rb
- spec/helpers/admin/orders_helper_spec.rb
- spec/lib/open_food_network/order_cycle_management_report_spec.rb
- spec/lib/stripe/authorize_response_patcher_spec.rb
- spec/services/bulk_invoice_service_spec.rb
- spec/services/content_sanitizer_spec.rb
- spec/services/process_payment_intent_spec.rb
- spec/support/features/datepicker_helper.rb
- app/controllers/admin/bulk_line_items_controller.rb
- app/controllers/admin/contents_controller.rb
- app/controllers/admin/customers_controller.rb
@@ -230,7 +249,6 @@ Layout/LineLength:
- spec/lib/open_food_network/products_and_inventory_report_spec.rb
- spec/lib/open_food_network/scope_variant_to_hub_spec.rb
- spec/lib/open_food_network/tag_rule_applicator_spec.rb
- spec/lib/open_food_network/user_balance_calculator_spec.rb
- spec/lib/open_food_network/users_and_enterprises_report_spec.rb
- spec/lib/open_food_network/xero_invoices_report_spec.rb
- spec/lib/spree/core/calculated_adjustments_spec.rb
@@ -329,6 +347,29 @@ Layout/LineLength:
Metrics/AbcSize:
Max: 15
Exclude:
- app/controllers/admin/schedules_controller.rb
- app/controllers/checkout_controller.rb
- app/controllers/spree/admin/general_settings_controller.rb
- app/controllers/spree/admin/images_controller.rb
- app/controllers/spree/admin/mail_methods_controller.rb
- app/controllers/spree/admin/shipping_methods_controller.rb
- app/controllers/spree/paypal_controller.rb
- app/helpers/spree/base_helper.rb
- app/jobs/subscription_placement_job.rb
- app/models/order_cycle.rb
- app/models/product_import/unit_converter.rb
- app/models/spree/gateway/pay_pal_express.rb
- app/serializers/api/admin/enterprise_serializer.rb
- app/services/order_factory.rb
- app/services/variants_stock_levels.rb
- engines/order_management/app/services/order_management/subscriptions/form.rb
- lib/open_food_network/enterprise_fee_calculator.rb
- lib/open_food_network/order_grouper.rb
- lib/spree/core/controller_helpers/auth.rb
- lib/spree/core/controller_helpers/common.rb
- lib/spree/core/product_duplicator.rb
- lib/stripe/authorize_response_patcher.rb
- lib/stripe/profile_storer.rb
- app/controllers/admin/bulk_line_items_controller.rb
- app/controllers/admin/customers_controller.rb
- app/controllers/admin/enterprise_fees_controller.rb
@@ -468,7 +509,7 @@ Metrics/AbcSize:
Metrics/BlockLength:
Max: 25
ExcludedMethods: [
IgnoredMethods: [
"class_eval",
"collection",
"context",
@@ -482,6 +523,8 @@ Metrics/BlockLength:
"scenario"
]
Exclude:
- spec/features/admin/order_cycles/complex_updating_specific_time_spec.rb
- spec/features/admin/tag_rules_spec.rb
- app/models/spree/order/checkout.rb
- app/models/spree/payment/processing.rb
- app/models/spree/shipment.rb
@@ -514,6 +557,27 @@ Metrics/BlockLength:
Metrics/CyclomaticComplexity:
Max: 6
Exclude:
- app/controllers/admin/resource_controller.rb
- app/controllers/spree/admin/payment_methods_controller.rb
- app/controllers/spree/admin/payments_controller.rb
- app/controllers/spree/admin/products_controller.rb
- app/controllers/spree/admin/users_controller.rb
- app/models/product_import/entry_validator.rb
- app/models/spree/order_inventory.rb
- app/models/spree/order.rb
- app/models/spree/shipment.rb
- app/models/spree/tax_rate.rb
- app/models/spree/variant.rb
- engines/order_management/app/services/order_management/reports/enterprise_fee_summary/parameters.rb
- lib/active_merchant/billing/gateways/stripe_decorator.rb
- lib/open_food_network/group_buy_report.rb
- lib/open_food_network/order_cycle_form_applicator.rb
- lib/open_food_network/order_cycle_permissions.rb
- lib/open_food_network/payments_report.rb
- lib/spree/core/controller_helpers/common.rb
- lib/stripe/authorize_response_patcher.rb
- lib/stripe/credit_card_clone_destroyer.rb
- spec/support/i18n_translations_checker.rb
- app/controllers/admin/enterprise_fees_controller.rb
- app/controllers/admin/enterprises_controller.rb
- app/controllers/spree/admin/taxons_controller.rb
@@ -552,6 +616,18 @@ Metrics/CyclomaticComplexity:
Metrics/PerceivedComplexity:
Max: 7
Exclude:
- app/controllers/spree/admin/payment_methods_controller.rb
- app/controllers/spree/admin/payments_controller.rb
- app/controllers/spree/admin/users_controller.rb
- app/models/product_import/entry_validator.rb
- app/models/spree/order_inventory.rb
- app/models/spree/shipment.rb
- app/models/spree/variant.rb
- lib/active_merchant/billing/gateways/stripe_decorator.rb
- lib/open_food_network/group_buy_report.rb
- lib/open_food_network/order_cycle_form_applicator.rb
- lib/open_food_network/order_cycle_permissions.rb
- lib/open_food_network/payments_report.rb
- app/controllers/admin/enterprises_controller.rb
- app/controllers/api/variants_controller.rb
- app/controllers/spree/admin/taxons_controller.rb
@@ -702,10 +778,18 @@ Metrics/MethodLength:
- spec/features/consumer/shopping/variant_overrides_spec.rb
- spec/models/product_importer_spec.rb
- spec/support/i18n_translations_checker.rb
- app/controllers/admin/bulk_line_items_controller.rb
- app/controllers/spree/paypal_controller.rb
- app/jobs/subscription_placement_job.rb
- app/models/spree/gateway/pay_pal_express.rb
Metrics/ClassLength:
Max: 100
Exclude:
- app/controllers/admin/customers_controller.rb
- app/controllers/spree/admin/payments_controller.rb
- app/controllers/spree/paypal_controller.rb
- engines/order_management/app/services/order_management/order/updater.rb
- app/controllers/admin/enterprises_controller.rb
- app/controllers/admin/order_cycles_controller.rb
- app/controllers/admin/resource_controller.rb
@@ -764,6 +848,7 @@ Metrics/ModuleLength:
- app/models/spree/payment/processing.rb
- engines/order_management/spec/services/order_management/subscriptions/proxy_order_syncer_spec.rb
- engines/order_management/spec/services/order_management/subscriptions/validator_spec.rb
- engines/order_management/spec/services/order_management/subscriptions/summary_spec.rb
- lib/open_food_network/column_preference_defaults.rb
- spec/controllers/admin/order_cycles_controller_spec.rb
- spec/controllers/api/orders_controller_spec.rb

View File

@@ -1,11 +1,10 @@
# Our Open Food Network style guide.
#
# These are the rules we agreed upon and we work towards. Code Climate uses
# these rules to rate our code and detect new violations. But when you run
# rubocop locally, the default configuration file `.rubocop.yml` loads
# our "todo lists" to ignore all current violations.
# These are the rules we agreed upon and we work towards.
AllCops:
TargetRailsVersion: 4.0
NewCops: disable
SuggestExtensions: false
TargetRailsVersion: 5.0
Exclude:
- 'bin/**/*'
- 'db/**/*'
@@ -183,7 +182,7 @@ Metrics/AbcSize:
Metrics/BlockLength:
Max: 25
ExcludedMethods: [
IgnoredMethods: [
"class_eval",
"collection",
"context",
@@ -217,3 +216,6 @@ Metrics/ParameterLists:
Metrics/PerceivedComplexity:
Max: 7
Naming/PredicateName:
Enabled: false

File diff suppressed because it is too large Load Diff

View File

@@ -1 +1 @@
2.4.4
2.5.8

View File

@@ -1,8 +1,6 @@
#!/bin/env ruby
SimpleCov.start 'rails' do
minimum_coverage 54
add_filter '/bin/'
add_filter '/config/'
add_filter '/jobs/application_job.rb'
@@ -15,4 +13,8 @@ SimpleCov.start 'rails' do
add_filter '/script'
add_filter '/log'
add_filter '/db'
add_filter '/lib/tasks/sample_data/'
end
require 'codecov'
SimpleCov.formatter = SimpleCov::Formatter::Codecov

7
.storybook/main.js Normal file
View File

@@ -0,0 +1,7 @@
module.exports = {
stories: ['../spec/components/stories/**/*.stories.json'],
addons: [
'@storybook/addon-docs',
'@storybook/addon-controls',
],
};

View File

@@ -0,0 +1,2 @@
<link href='https://fonts.googleapis.com/css?family=Roboto:400,300italic,400italic,300,700,700italic|Oswald:300,400,700' rel='stylesheet' type='text/css'>
<link rel="stylesheet" media="screen" href="http://localhost:3000/assets/darkswarm/all.css" />

5
.storybook/preview.js Normal file
View File

@@ -0,0 +1,5 @@
export const parameters = {
server: {
url: `http://localhost:3000/rails/stories`,
},
};

View File

@@ -6,12 +6,13 @@ This is a general guide to setting up an Open Food Network **development environ
The fastest way to make it work locally is to use Docker, you only need to setup git, see the [Docker setup guide](docker/README.md).
Otherwise, for a local setup you will need:
* Ruby 2.3.7 and bundler
* Ruby 2.4.4 and bundler (check current Ruby version in [.ruby-version](https://github.com/openfoodfoundation/openfoodnetwork/blob/master/.ruby-version) file)
* PostgreSQL database
* Chrome (for testing)
The following guides will provide OS-specific step-by-step instructions to get these requirements installed:
- [Ubuntu Setup Guide][ubuntu]
- [Debian Setup Guide][debian]
- [OSX Setup Guide][osx]
If you are likely to need to manage multiple version of ruby on your local machine, we recommend version managers such as [rbenv](https://github.com/rbenv/rbenv) or [RVM](https://rvm.io/).
@@ -20,7 +21,7 @@ For those new to Rails, the following tutorial will help get you up to speed wit
### Get it
If you're planning on contributing code to the project (which we [LOVE](CONTRIBUTING.md)), it is a good idea to begin by forking this repo using the `Fork` button in the top-right corner of this screen. You should then be able to use `git clone` to copy your fork onto your local machine.
So you have set up your local environment according to the requirements listed above. If you're planning on contributing code to the project (which we [LOVE](CONTRIBUTING.md)), it is a good idea to begin by forking this repo using the `Fork` button in the top-right corner of this screen. You should then be able to use `git clone` to copy your fork onto your local machine:
git clone https://github.com/YOUR_GITHUB_USERNAME_HERE/openfoodnetwork
@@ -116,6 +117,7 @@ If these commands succeed, you should be able to [continue the setup process](#g
[developer-wiki]: https://github.com/openfoodfoundation/openfoodnetwork/wiki
[osx]: https://github.com/openfoodfoundation/openfoodnetwork/wiki/Development-Environment-Setup:-OS-X
[ubuntu]: https://github.com/openfoodfoundation/openfoodnetwork/wiki/Development-Environment-Setup:-Ubuntu
[debian]: https://github.com/openfoodfoundation/openfoodnetwork/wiki/Development-Environment-Setup:-Debian
[wiki]: https://github.com/openfoodfoundation/openfoodnetwork/wiki
[zeus]: https://github.com/burke/zeus
[rubocop]: https://rubocop.readthedocs.io/en/latest/

23
Gemfile
View File

@@ -1,7 +1,7 @@
# frozen_string_literal: true
source 'https://rubygems.org'
ruby "2.4.4"
ruby "2.5.8"
git_source(:github) { |repo_name| "https://github.com/#{repo_name}.git" }
gem 'rails', '~> 5.0.0'
@@ -15,7 +15,7 @@ gem 'sass', '<= 4.7.1'
gem 'sass-rails', '< 6.0.0'
gem 'i18n'
gem 'i18n-js', '~> 3.8.1'
gem 'i18n-js', '~> 3.8.2'
gem 'rails-i18n'
gem 'rails_safe_tasks', '~> 1.0'
@@ -31,7 +31,7 @@ gem 'web', path: './engines/web'
gem 'activerecord-postgresql-adapter'
gem 'pg', '~> 0.21.0'
gem 'acts_as_list', '0.9.19'
gem 'acts_as_list', '1.0.4'
gem 'cancancan', '~> 1.15.0'
gem 'ffaker'
gem 'highline', '2.0.3' # Necessary for the install generator
@@ -48,7 +48,7 @@ gem 'devise'
gem 'devise-encryptable'
gem 'devise-token_authenticatable'
gem 'jwt', '~> 2.2'
gem 'oauth2', '~> 1.4.4' # Used for Stripe Connect
gem 'oauth2', '~> 1.4.7' # Used for Stripe Connect
gem 'daemons'
gem 'delayed_job_active_record'
@@ -83,7 +83,7 @@ gem 'roadie-rails', '~> 1.3.0'
gem 'combine_pdf'
gem 'wicked_pdf'
gem 'wkhtmltopdf-binary', '0.12.5' # We need to upgrade our CI before we can bump this :/
gem 'wkhtmltopdf-binary'
gem 'immigrant'
gem 'roo', '~> 2.8.3'
@@ -95,7 +95,7 @@ gem 'test-unit', '~> 3.4'
gem 'coffee-rails', '~> 4.2.2'
gem 'compass-rails'
gem 'mini_racer', '0.3.1'
gem 'mini_racer', '0.4.0'
gem 'uglifier', '>= 1.0.3'
@@ -113,6 +113,12 @@ gem 'ofn-qz', github: 'openfoodfoundation/ofn-qz', branch: 'ofn-rails-4'
gem 'good_migrations'
gem 'flipper'
gem 'flipper-active_record'
gem 'flipper-ui'
gem "view_component", require: "view_component/engine"
group :production, :staging do
gem 'ddtrace'
gem 'unicorn-worker-killer'
@@ -125,7 +131,7 @@ group :test, :development do
gem 'bullet'
gem 'capybara'
gem 'database_cleaner', require: false
gem "factory_bot_rails", '5.2.0', require: false
gem "factory_bot_rails", '6.1.0', require: false
gem 'fuubar', '~> 2.5.1'
gem 'json_spec', '~> 1.1.4'
gem 'knapsack'
@@ -141,6 +147,7 @@ group :test, :development do
end
group :test do
gem 'codecov', require: false
gem 'simplecov', require: false
gem 'test-prof'
gem 'webmock'
@@ -159,6 +166,8 @@ group :development do
gem 'spring'
gem 'spring-commands-rspec'
gem "view_component_storybook", require: "view_component/storybook/engine"
# 1.0.9 fixed openssl issues on macOS https://github.com/eventmachine/eventmachine/issues/602
# While we don't require this gem directly, no dependents forced the upgrade to a version
# greater than 1.0.9, so we just required the latest available version here.

View File

@@ -77,7 +77,7 @@ GEM
activejob (5.0.7.2)
activesupport (= 5.0.7.2)
globalid (>= 0.3.6)
activemerchant (1.107.4)
activemerchant (1.119.0)
activesupport (>= 4.2)
builder (>= 2.1.2, < 4.0.0)
i18n (>= 0.6.9)
@@ -105,8 +105,8 @@ GEM
tzinfo (~> 1.1)
acts-as-taggable-on (7.0.0)
activerecord (>= 5.0, < 6.2)
acts_as_list (0.9.19)
activerecord (>= 3.0)
acts_as_list (1.0.4)
activerecord (>= 4.2)
addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0)
andand (1.3.3)
@@ -114,7 +114,7 @@ GEM
railties (>= 4.2, < 7)
sprockets (>= 3.0, < 5)
tilt
angular_rails_csrf (4.2.0)
angular_rails_csrf (4.5.0)
railties (>= 3, < 7)
angularjs-file-upload-rails (2.4.1)
angularjs-rails (1.5.5)
@@ -130,7 +130,7 @@ GEM
json (~> 1.4)
nokogiri (~> 1)
bcrypt (3.1.16)
bugsnag (6.19.0)
bugsnag (6.20.0)
concurrent-ruby (~> 1.0)
builder (3.2.4)
bullet (6.1.4)
@@ -138,13 +138,13 @@ GEM
uniform_notifier (~> 1.11)
byebug (11.1.3)
cancancan (1.15.0)
capybara (3.32.2)
capybara (3.35.3)
addressable
mini_mime (>= 0.1.3)
nokogiri (~> 1.8)
rack (>= 1.6.0)
rack-test (>= 0.6.3)
regexp_parser (~> 1.5)
regexp_parser (>= 1.5, < 3.0)
xpath (~> 3.2)
childprocess (3.0.0)
chronic (0.10.2)
@@ -152,6 +152,8 @@ GEM
climate_control (0.2.0)
cocaine (0.5.8)
climate_control (>= 0.0.3, < 1.0)
codecov (0.5.1)
simplecov (>= 0.15, < 0.22)
coderay (1.1.3)
coffee-rails (4.2.2)
coffee-script (>= 2.2.0)
@@ -187,12 +189,13 @@ GEM
daemons (1.3.1)
dalli (2.7.11)
database_cleaner (1.99.0)
ddtrace (0.46.0)
ddtrace (0.48.0)
ffi (~> 1.0)
msgpack
debugger-linecache (1.2.0)
delayed_job (4.1.9)
activesupport (>= 3.0, < 6.2)
delayed_job_active_record (4.1.5)
delayed_job_active_record (4.1.6)
activerecord (>= 3.0, < 6.2)
delayed_job (>= 3.0, < 5)
delayed_job_web (1.4.4)
@@ -211,22 +214,35 @@ GEM
devise-token_authenticatable (1.1.0)
devise (>= 4.0.0, < 5.0.0)
diff-lcs (1.4.4)
docile (1.3.4)
docile (1.3.5)
erubi (1.10.0)
erubis (2.7.0)
eventmachine (1.2.7)
excon (0.79.0)
execjs (2.7.0)
factory_bot (5.2.0)
activesupport (>= 4.2.0)
factory_bot_rails (5.2.0)
factory_bot (~> 5.2.0)
railties (>= 4.2.0)
faraday (1.0.1)
factory_bot (6.1.0)
activesupport (>= 5.0.0)
factory_bot_rails (6.1.0)
factory_bot (~> 6.1.0)
railties (>= 5.0.0)
faraday (1.3.0)
faraday-net_http (~> 1.0)
multipart-post (>= 1.2, < 3)
ffaker (2.16.0)
ruby2_keywords
faraday-net_http (1.0.1)
ffaker (2.18.0)
ffi (1.15.0)
figaro (1.2.0)
thor (>= 0.14.0, < 2)
flipper (0.20.4)
flipper-active_record (0.20.4)
activerecord (>= 5.0, < 7)
flipper (~> 0.20.4)
flipper-ui (0.20.4)
erubi (>= 1.0.0, < 2.0.0)
flipper (~> 0.20.4)
rack (>= 1.4, < 3)
rack-protection (>= 1.5.3, < 2.2.0)
fog-aws (2.0.1)
fog-core (~> 1.38)
fog-json (~> 1.0)
@@ -252,7 +268,7 @@ GEM
fuubar (2.5.1)
rspec-core (~> 3.0)
ruby-progressbar (~> 1.4)
geocoder (1.6.6)
geocoder (1.6.7)
get_process_mem (0.2.7)
ffi (~> 1.0)
globalid (0.4.2)
@@ -266,9 +282,9 @@ GEM
tilt
hashdiff (1.0.1)
highline (2.0.3)
i18n (1.8.9)
i18n (1.8.10)
concurrent-ruby (~> 1.0)
i18n-js (3.8.1)
i18n-js (3.8.2)
i18n (>= 0.6.6)
immigrant (0.3.6)
activerecord (>= 3.0)
@@ -300,13 +316,13 @@ GEM
kaminari-core (= 1.2.1)
kaminari-core (1.2.1)
kgio (2.11.3)
knapsack (1.20.0)
knapsack (1.22.0)
rake
launchy (2.4.3)
addressable (~> 2.3)
letter_opener (1.7.0)
launchy (~> 2.2)
libv8 (8.4.255.0)
libv8-node (15.14.0.0)
loofah (2.9.0)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
@@ -316,10 +332,10 @@ GEM
mime-types (3.3.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2020.1104)
mini_mime (1.0.2)
mini_portile2 (2.4.0)
mini_racer (0.3.1)
libv8 (~> 8.4.255)
mini_mime (1.0.3)
mini_portile2 (2.5.0)
mini_racer (0.4.0)
libv8-node (~> 15.14.0.0)
minitest (5.14.4)
monetize (1.11.0)
money (~> 6.12)
@@ -332,9 +348,10 @@ GEM
mustermann (1.1.1)
ruby2_keywords (~> 0.0.1)
nio4r (2.5.2)
nokogiri (1.10.10)
mini_portile2 (~> 2.4.0)
oauth2 (1.4.4)
nokogiri (1.11.2)
mini_portile2 (~> 2.5.0)
racc (~> 1.4)
oauth2 (1.4.7)
faraday (>= 0.8, < 2.0)
jwt (>= 1.0, < 3.0)
multi_json (~> 1.3)
@@ -353,7 +370,7 @@ GEM
parallel (1.20.1)
paranoia (2.4.3)
activerecord (>= 4.0, < 6.2)
parser (3.0.0.0)
parser (3.0.1.0)
ast (~> 2.4.1)
paypal-sdk-core (0.3.4)
multi_json (~> 1.0)
@@ -371,6 +388,7 @@ GEM
byebug (~> 11.0)
pry (~> 0.13.0)
public_suffix (4.0.6)
racc (1.5.2)
rack (2.2.3)
rack-mini-profiler (2.3.1)
rack (>= 1.2.0)
@@ -425,13 +443,13 @@ GEM
rb-inotify (0.10.1)
ffi (~> 1.0)
redcarpet (3.5.1)
regexp_parser (1.8.2)
regexp_parser (2.1.1)
request_store (1.5.0)
rack (>= 1.4)
responders (3.0.1)
actionpack (>= 5.0)
railties (>= 5.0)
rexml (3.2.4)
rexml (3.2.5)
roadie (3.5.1)
css_parser (~> 1.4)
nokogiri (~> 1.8)
@@ -477,7 +495,7 @@ GEM
rswag-ui (2.4.0)
actionpack (>= 3.1, < 7.0)
railties (>= 3.1, < 7.0)
rubocop (1.11.0)
rubocop (1.13.0)
parallel (~> 1.10)
parser (>= 3.0.0.0)
rainbow (>= 2.2.2, < 4.0)
@@ -511,10 +529,12 @@ GEM
rubyzip (>= 1.2.2)
shoulda-matchers (4.5.1)
activesupport (>= 4.2.0)
simplecov (0.18.5)
simplecov (0.21.2)
docile (~> 1.1)
simplecov-html (~> 0.11)
simplecov_json_formatter (~> 0.1)
simplecov-html (0.12.3)
simplecov_json_formatter (0.1.2)
sinatra (2.1.0)
mustermann (~> 1.0)
rack (~> 2.2)
@@ -540,8 +560,8 @@ GEM
stringex (2.8.5)
stripe (5.30.0)
temple (0.8.2)
test-prof (0.11.3)
test-unit (3.4.0)
test-prof (1.0.2)
test-unit (3.4.1)
power_assert
thor (0.20.3)
thread_safe (0.3.6)
@@ -552,23 +572,27 @@ GEM
uglifier (4.2.0)
execjs (>= 0.3.0, < 3)
unicode-display_width (2.0.0)
unicorn (5.8.0)
unicorn (6.0.0)
kgio (~> 2.6)
raindrops (~> 0.7)
unicorn-rails (2.2.1)
rack
unicorn
unicorn-worker-killer (0.4.4)
unicorn-worker-killer (0.4.5)
get_process_mem (~> 0)
unicorn (>= 4, < 6)
unicorn (>= 4, < 7)
uniform_notifier (1.14.1)
view_component (2.28.0)
activesupport (>= 5.0.0, < 7.0)
view_component_storybook (0.8.0)
view_component (>= 2.2)
warden (1.2.9)
rack (>= 2.0.9)
webdrivers (4.6.0)
nokogiri (~> 1.6)
rubyzip (>= 1.3.0)
selenium-webdriver (>= 3.0, < 4.0)
webmock (3.12.1)
webmock (3.12.2)
addressable (>= 2.3.6)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
@@ -579,7 +603,7 @@ GEM
chronic (>= 0.6.3)
wicked_pdf (2.1.0)
activesupport
wkhtmltopdf-binary (0.12.5)
wkhtmltopdf-binary (0.12.6.5)
xml-simple (1.1.8)
xpath (3.2.0)
nokogiri (~> 1.8)
@@ -595,7 +619,7 @@ DEPENDENCIES
activerecord-postgresql-adapter
activerecord-session_store
acts-as-taggable-on (~> 7.0)
acts_as_list (= 0.9.19)
acts_as_list (= 1.0.4)
andand
angular-rails-templates (>= 0.3.0)
angular_rails_csrf
@@ -611,6 +635,7 @@ DEPENDENCIES
cancancan (~> 1.15.0)
capybara
catalog!
codecov
coffee-rails (~> 4.2.2)
combine_pdf
compass-rails
@@ -628,9 +653,12 @@ DEPENDENCIES
devise-token_authenticatable
dfc_provider!
eventmachine (>= 1.2.3)
factory_bot_rails (= 5.2.0)
factory_bot_rails (= 6.1.0)
ffaker
figaro
flipper
flipper-active_record
flipper-ui
fog-aws (>= 0.6.0)
foundation-icons-sass-rails
foundation-rails (= 5.5.2.1)
@@ -641,7 +669,7 @@ DEPENDENCIES
haml
highline (= 2.0.3)
i18n
i18n-js (~> 3.8.1)
i18n-js (~> 3.8.2)
immigrant
jquery-migrate-rails
jquery-rails (= 4.4.0)
@@ -652,9 +680,9 @@ DEPENDENCIES
kaminari (~> 1.2.1)
knapsack
letter_opener (>= 1.4.1)
mini_racer (= 0.3.1)
mini_racer (= 0.4.0)
monetize (~> 1.11)
oauth2 (~> 1.4.4)
oauth2 (~> 1.4.7)
ofn-qz!
order_management!
paper_trail (~> 10.3.1)
@@ -698,15 +726,17 @@ DEPENDENCIES
uglifier (>= 1.0.3)
unicorn-rails
unicorn-worker-killer
view_component
view_component_storybook
web!
webdrivers
webmock
whenever
wicked_pdf
wkhtmltopdf-binary (= 0.12.5)
wkhtmltopdf-binary
RUBY VERSION
ruby 2.4.4p296
ruby 2.5.8p224
BUNDLED WITH
1.17.3

View File

@@ -1,6 +1,6 @@
[![Build Status](https://semaphoreci.com/api/v1/openfoodfoundation/openfoodnetwork-2/branches/master/badge.svg)](https://semaphoreci.com/openfoodfoundation/openfoodnetwork-2)
[![Code Climate](https://codeclimate.com/github/openfoodfoundation/openfoodnetwork.png)](https://codeclimate.com/github/openfoodfoundation/openfoodnetwork)
[![Build](https://github.com/openfoodfoundation/openfoodnetwork/actions/workflows/build.yml/badge.svg)](https://github.com/openfoodfoundation/openfoodnetwork/actions/workflows/build.yml)
[![codecov](https://codecov.io/gh/openfoodfoundation/openfoodnetwork/branch/master/graph/badge.svg?token=FBSOod4qiu)](https://codecov.io/gh/openfoodfoundation/openfoodnetwork)
[![Code Climate](https://codeclimate.com/github/openfoodfoundation/openfoodnetwork.png)](https://codeclimate.com/github/openfoodfoundation/openfoodnetwork)
# Open Food Network
@@ -34,7 +34,7 @@ We also have a [Super Admin Guide][super-admin-guide] to help with configuration
## Testing
If you'd like to help out with testing, please introduce yourself on the #testing channel on [Slack][slack-invite] and download the [ZenHub browser extension][zenhub] to view the development pipeline.
If you'd like to help out with testing, please introduce yourself on the #testing channel on [Slack][slack-invite] and download the [ZenHub browser extension][zenhub] to view the development pipeline. Also, do have a look in our [Welcome New QAs board](https://github.com/openfoodfoundation/openfoodnetwork/projects/31) for some good first issues, both on manual and automated testing (RSpec/Capybara).
We use [BrowserStack](https://www.browserstack.com/) as a manual testing tool. BrowserStack provides open source projects with unlimited and free of charge accounts. A big thanks to them!

View File

@@ -52,6 +52,9 @@
//= require admin/spree/handlebar_extensions
// OFN specific
//= require ../shared/shared
//= require_tree ../shared/directives
//= require_tree ../templates/shared
//= require_tree ../templates/admin
//= require ./admin_ofn
//= require ./customers/customers

View File

@@ -147,7 +147,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
if confirm("Are you sure?")
$http(
method: "DELETE"
url: "/api/products/" + product.id
url: "/api/v0/products/" + product.id
).success (data) ->
$scope.products.splice $scope.products.indexOf(product), 1
DirtyProducts.deleteProduct product.id
@@ -162,7 +162,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
if confirm(t("are_you_sure"))
$http(
method: "DELETE"
url: "/api/products/" + product.permalink_live + "/variants/" + variant.id
url: "/api/v0/products/" + product.permalink_live + "/variants/" + variant.id
).success (data) ->
$scope.removeVariant(product, variant)
else

View File

@@ -1,7 +1,7 @@
angular.module('admin.enterpriseFees').directive 'spreeDeleteResource', ->
(scope, element, attrs) ->
if scope.enterprise_fee.id
url = '/api/enterprise_fees/' + scope.enterprise_fee.id
url = '/api/v0/enterprise_fees/' + scope.enterprise_fee.id
html = '<a href="' + url + '" class="delete-resource icon_link icon-trash no-text" data-action="remove" data-confirm="' + t('are_you_sure') + '" url="' + url + '"></a>'
#var html = '<a href="'+url+'" class="delete-resource" data-confirm="Are you sure?"><img alt="Delete" src="/assets/admin/icons/delete.png" /> Delete</a>';
element.append html

View File

@@ -1,5 +1,5 @@
angular.module("admin.indexUtils").factory "resources", ($resource) ->
LineItem = $resource '/api/orders/:order_number/line_items/:line_item_id.json',
LineItem = $resource '/api/v0/orders/:order_number/line_items/:line_item_id.json',
{ order_number: '@order_number', line_item_id: '@line_item_id'},
'update': { method: 'PUT' }
Customer = $resource '/admin/customers/:customer_id.json',

View File

@@ -1,5 +1,5 @@
angular.module('admin.orderCycles').factory('ExchangeProduct', ($resource) ->
ExchangeProductResource = $resource('/api/exchanges/:exchange_id/products.json', {}, {
ExchangeProductResource = $resource('/api/v0/exchanges/:exchange_id/products.json', {}, {
'index': { method: 'GET' }
'variant_count': { method: 'GET', params: { action_name: "variant_count" }}
})

View File

@@ -9,7 +9,7 @@ angular.module("admin.products").controller "editUnitsCtrl", ($scope, VariantUni
if $scope.product.variant_unit == 'items'
$scope.variant_unit_with_scale = 'items'
else
$scope.variant_unit_with_scale = $scope.product.variant_unit + '_' + $scope.product.variant_unit_scale
$scope.variant_unit_with_scale = $scope.product.variant_unit + '_' + $scope.product.variant_unit_scale.replace(/\.0$/, '');
$scope.setFields = ->
if $scope.variant_unit_with_scale == 'items'

View File

@@ -1,12 +1,13 @@
angular.module("admin.products")
.controller "unitsCtrl", ($scope, VariantUnitManager, OptionValueNamer) ->
.controller "unitsCtrl", ($scope, VariantUnitManager, OptionValueNamer, UnitPrices) ->
$scope.product = { master: {} }
$scope.product.master.product = $scope.product
$scope.placeholder_text = ""
$scope.$watchCollection '[product.variant_unit_with_scale, product.master.unit_value_with_description]', ->
$scope.$watchCollection '[product.variant_unit_with_scale, product.master.unit_value_with_description, product.price, product.variant_unit_name]', ->
$scope.processVariantUnitWithScale()
$scope.processUnitValueWithDescription()
$scope.processUnitPrice()
$scope.placeholder_text = new OptionValueNamer($scope.product.master).name()
$scope.variant_unit_options = VariantUnitManager.variantUnitOptions()
@@ -32,6 +33,14 @@ angular.module("admin.products")
$scope.product.master.unit_value *= $scope.product.variant_unit_scale if $scope.product.master.unit_value && $scope.product.variant_unit_scale
$scope.product.master.unit_description = match[3]
$scope.processUnitPrice = ->
price = $scope.product.price
scale = $scope.product.variant_unit_scale
unit_type = $scope.product.variant_unit
unit_value = $scope.product.master.unit_value
variant_unit_name = $scope.product.variant_unit_name
$scope.unit_price = UnitPrices.displayableUnitPrice(price, scale, unit_type, unit_value, variant_unit_name)
$scope.hasVariants = (product) ->
Object.keys(product.variants).length > 0

View File

@@ -1,8 +1,24 @@
angular.module("admin.products").controller "variantUnitsCtrl", ($scope, VariantUnitManager, $timeout) ->
angular.module("admin.products").controller "variantUnitsCtrl", ($scope, VariantUnitManager, $timeout, UnitPrices) ->
$scope.unitName = (scale, type) ->
VariantUnitManager.getUnitName(scale, type)
$scope.$watchCollection "[unit_value_human, variant.price]", ->
$scope.processUnitPrice()
$scope.processUnitPrice = ->
if ($scope.variant)
price = $scope.variant.price
scale = $scope.scale
unit_type = angular.element("#product_variant_unit").val()
if (unit_type != "items")
$scope.updateValue()
unit_value = $scope.unit_value
else
unit_value = 1
variant_unit_name = angular.element("#product_variant_unit_name").val()
$scope.unit_price = UnitPrices.displayableUnitPrice(price, scale, unit_type, unit_value, variant_unit_name)
$scope.scale = angular.element('#product_variant_unit_scale').val()
$scope.updateValue = ->
@@ -11,4 +27,6 @@ angular.module("admin.products").controller "variantUnitsCtrl", ($scope, Variant
variant_unit_value = angular.element('#variant_unit_value').val()
$scope.unit_value_human = variant_unit_value / $scope.scale
$timeout -> $scope.processUnitPrice()
$timeout -> $scope.updateValue()

View File

@@ -1 +1 @@
angular.module("admin.products", ["textAngular", "admin.utils"])
angular.module("admin.products", ["textAngular", "admin.utils", "OFNShared"])

View File

@@ -8,7 +8,7 @@ angular.module("ofn.admin").factory "ProductImageService", (FileUploader, SpreeA
autoUpload: true
configure: (product) =>
@imageUploader.url = "/api/product_images/#{product.id}"
@imageUploader.url = "/api/v0/product_images/#{product.id}"
@imagePreview = product.image_url
@imageUploader.onSuccessItem = (image, response) =>
product.thumb_url = response.thumb_url

View File

@@ -0,0 +1,32 @@
angular.module("admin.products").factory "UnitPrices", (VariantUnitManager, localizeCurrencyFilter) ->
class UnitPrices
@displayableUnitPrice: (price, scale, unit_type, unit_value, variant_unit_name) ->
if price && !isNaN(price) && unit_type && unit_value
value = localizeCurrencyFilter(UnitPrices.price(price, scale, unit_type, unit_value, variant_unit_name))
unit = UnitPrices.unit(scale, unit_type, variant_unit_name)
return value + " / " + unit
return null
@price: (price, scale, unit_type, unit_value) ->
price / @denominator(scale, unit_type, unit_value)
@denominator: (scale, unit_type, unit_value) ->
unit = @unit(scale, unit_type)
if unit == "lb"
unit_value / 453.6
else if unit == "kg"
unit_value / 1000
else
unit_value
@unit: (scale, unit_type, variant_unit_name = '') ->
if variant_unit_name.length > 0
variant_unit_name
else if unit_type == "items"
"item"
else if VariantUnitManager.systemOfMeasurement(scale, unit_type) == "imperial"
"lb"
else if unit_type == "weight"
"kg"
else if unit_type == "volume"
"L"

View File

@@ -67,3 +67,9 @@ angular.module("admin.products").factory "VariantUnitManager", (availableUnits)
scaleSystem = @units[unitType][scale]['system']
(parseFloat(scale) for scale, scaleInfo of @units[unitType] when scaleInfo['system'] == scaleSystem).sort (a, b) ->
a - b
@systemOfMeasurement: (scale, unitType) ->
if @units[unitType][scale]
@units[unitType][scale]['system']
else
'custom'

View File

@@ -9,12 +9,12 @@ angular.module("admin.resources").factory 'EnterpriseResource', ($resource) ->
'update':
method: 'PUT'
'removeLogo':
url: '/api/enterprises/:id/logo.json'
url: '/api/v0/enterprises/:id/logo.json'
method: 'DELETE'
'removePromoImage':
url: '/api/enterprises/:id/promo_image.json'
url: '/api/v0/enterprises/:id/promo_image.json'
method: 'DELETE'
'removeTermsAndConditions':
url: '/api/enterprises/:id/terms_and_conditions.json'
url: '/api/v0/enterprises/:id/terms_and_conditions.json'
method: 'DELETE'
})

View File

@@ -1,17 +1,17 @@
angular.module("admin.resources").factory 'OrderResource', ($resource) ->
$resource('/admin/orders/:id/:action.json', {}, {
'index':
url: '/api/orders.json'
url: '/api/v0/orders.json'
method: 'GET'
'update':
method: 'PUT'
'capture':
url: '/api/orders/:id/capture.json'
url: '/api/v0/orders/:id/capture.json'
method: 'PUT'
params:
id: '@id'
'ship':
url: '/api/orders/:id/ship.json'
url: '/api/v0/orders/:id/ship.json'
method: 'PUT'
params:
id: '@id'

View File

@@ -1,6 +1,6 @@
angular.module("admin.resources").factory 'ProductResource', ($resource) ->
$resource('/admin/product/:id/:action.json', {}, {
'index':
url: '/api/products/bulk_products.json'
url: '/api/v0/products/bulk_products.json'
method: 'GET'
})

View File

@@ -10,8 +10,8 @@ angular.module("ofn.admin").factory "BulkProducts", (ProductResource, dataFetche
angular.extend(@pagination, data.pagination)
cloneProduct: (product) ->
$http.post("/api/products/" + product.id + "/clone").success (data) =>
dataFetcher("/api/products/" + data.id + "?template=bulk_show").then (newProduct) =>
$http.post("/api/v0/products/" + product.id + "/clone").success (data) =>
dataFetcher("/api/v0/products/" + data.id + "?template=bulk_show").then (newProduct) =>
@unpackProduct newProduct
@insertProductAfter(product, newProduct)

View File

@@ -23,8 +23,6 @@ angular.module("admin.tagRules").controller "TagRulesCtrl", ($scope, $http, $fil
preferred_customer_tags: (tag.text for tag in tagGroup.tags).join(",")
type: "TagRule::#{ruleType}"
switch ruleType
when "DiscountOrder"
newRule.calculator = { preferred_flat_percent: 0 }
when "FilterShippingMethods"
newRule.peferred_shipping_method_tags = []
newRule.preferred_matched_shipping_methods_visibility = "visible"

View File

@@ -8,7 +8,6 @@ angular.module("admin.tagRules").directive 'newTagRuleDialog', ($compile, $templ
template = $compile($templateCache.get('admin/new_tag_rule_dialog.html'))(scope)
scope.ruleTypes = [
# { id: "DiscountOrder", name: 'Apply a discount to orders' }
{ id: "FilterProducts", name: t('js.tag_rules.show_hide_variants') }
{ id: "FilterShippingMethods", name: t('js.tag_rules.show_hide_shipping') }
{ id: "FilterPaymentMethods", name: t('js.tag_rules.show_hide_payment') }

View File

@@ -1,11 +1,11 @@
angular.module("admin.utils").factory "StatusMessage", ($timeout) ->
angular.module("admin.utils").factory "StatusMessage", ->
new class StatusMessage
types:
progress: {timeout: false, style: {color: '#ff9906'}}
alert: {timeout: 5000, style: {color: 'grey'}}
notice: {timeout: false, style: {color: 'grey'}}
success: {timeout: 5000, style: {color: '#9fc820'}}
failure: {timeout: false, style: {color: '#da5354'}}
progress: {style: {color: '#ff9906'}}
alert: {style: {color: 'grey'}}
notice: {style: {color: 'grey'}}
success: {style: {color: '#9fc820'}}
failure: {style: {color: '#da5354'}}
statusMessage:
text: ""
@@ -25,13 +25,7 @@ angular.module("admin.utils").factory "StatusMessage", ($timeout) ->
display: (type, text) ->
@statusMessage.text = text
@statusMessage.style = @types[type].style
$timeout.cancel @statusMessage.timeout if @statusMessage.timeout
timeout = @types[type].timeout
if timeout
@statusMessage.timeout = $timeout =>
@clear()
, timeout, true
null # So we don't return weird timeouts
null
clear: ->
@statusMessage.text = ''

View File

@@ -42,7 +42,7 @@ angular.module("admin.variantOverrides").controller "AdminVariantOverridesCtrl",
$scope.fetchProducts()
$scope.fetchProducts = ->
url = "/api/products/overridable?page=::page::;per_page=100"
url = "/api/v0/products/overridable?page=::page::;per_page=100"
PagedFetcher.fetch url, $scope.addProducts
$scope.addProducts = (data) ->

View File

@@ -20,6 +20,8 @@
#= require ../shared/ng-infinite-scroll.min.js
#= require ../shared/angular-local-storage.js
#= require ../shared/angular-slideables.js
#= require ../shared/shared
#= require_tree ../shared/directives
#= require angularjs-file-upload
#= require i18n/translations

View File

@@ -24,7 +24,7 @@ Darkswarm.controller "HubNodeCtrl", ($scope, HashNavigation, CurrentHub, $http,
$scope.shopfront_loading = true
$scope.toggle_tab(event)
$http.get("/api/shops/" + $scope.hub.id)
$http.get("/api/v0/shops/" + $scope.hub.id)
.success (data) ->
$scope.shopfront_loading = false
$scope.hub = data

View File

@@ -24,7 +24,7 @@ Darkswarm.controller "ProducerNodeCtrl", ($scope, HashNavigation, $anchorScroll,
$scope.shopfront_loading = true
$scope.toggle_tab(event)
$http.get("/api/shops/" + $scope.producer.id)
$http.get("/api/v0/shops/" + $scope.producer.id)
.success (data) ->
$scope.shopfront_loading = false
$scope.producer = data

View File

@@ -10,7 +10,8 @@ window.Darkswarm = angular.module("Darkswarm", [
'uiGmapgoogle-maps',
'duScroll',
'angularFileUpload',
'angularSlideables'
'angularSlideables',
'OFNShared'
]).config ($httpProvider, $tooltipProvider, $locationProvider, $anchorScrollProvider) ->
$httpProvider.defaults.headers['common']['X-Requested-With'] = 'XMLHttpRequest'
$httpProvider.defaults.headers.common['Accept'] = "application/json, text/javascript, */*"

View File

@@ -3,7 +3,15 @@ Darkswarm.directive "bodyScroll", ($rootScope, BodyScroll) ->
scope: true
link: (scope, elem, attrs) ->
$rootScope.$on "toggleBodyScroll", ->
if BodyScroll.disabled
elem.addClass "disable-scroll"
if BodyScroll.disabled && document.body.scrollHeight > document.body.clientHeight
document.body.style.top = "-#{window.scrollY}px"
document.body.style.position = 'fixed'
document.body.style.overflowY = 'scroll'
document.body.style.width = '100%'
else
elem.removeClass "disable-scroll"
scrollY = parseInt(document.body.style.top) * -1
document.body.style.position = ''
document.body.style.top = ''
document.body.style.overflowY = ''
document.body.style.width = ''
window.scrollTo(0, scrollY) if scrollY

View File

@@ -4,4 +4,5 @@ Darkswarm.directive "shopVariantWithUnitPrice", ->
templateUrl: 'shop_variant_with_unit_price.html'
scope:
variant: '='
show_unit_price: '=showunitprice'
controller: 'ShopVariantCtrl'

View File

@@ -1,5 +1,5 @@
angular.module("Darkswarm").factory 'Customer', ($resource, $injector, Messages) ->
Customer = $resource('/api/customers/:id/:action.json', {}, {
Customer = $resource('/api/v0/customers/:id/:action.json', {}, {
'index':
method: 'GET'
isArray: true

View File

@@ -8,5 +8,5 @@ Darkswarm.factory "EnterpriseImageService", (FileUploader, spreeApiKey) ->
autoUpload: true
configure: (enterprise) =>
@imageUploader.url = "/api/enterprises/#{enterprise.id}/update_image"
@imageUploader.url = "/api/v0/enterprises/#{enterprise.id}/update_image"
@imageUploader.onSuccessItem = (image, response) => @imageSrc = response

View File

@@ -5,7 +5,7 @@ Darkswarm.factory "EnterpriseModal", ($modal, $rootScope, $http)->
scope = $rootScope.$new(true) # Spawn an isolate to contain the enterprise
scope.embedded_layout = window.location.search.indexOf("embedded_shopfront=true") != -1
$http.get("/api/shops/" + enterprise.id).success (data) ->
$http.get("/api/v0/shops/" + enterprise.id).success (data) ->
scope.enterprise = data
$modal.open(templateUrl: "enterprise_modal.html", scope: scope)
.error (data) ->

View File

@@ -18,7 +18,7 @@ Darkswarm.factory "EnterpriseRegistrationService", ($http, RegistrationService,
Loading.message = t('creating') + " " + @enterprise.name
$http(
method: "POST"
url: "/api/enterprises"
url: "/api/v0/enterprises"
data:
enterprise: @prepare()
use_geocoder: @useGeocoder()
@@ -43,7 +43,7 @@ Darkswarm.factory "EnterpriseRegistrationService", ($http, RegistrationService,
Loading.message = t('updating') + " " + @enterprise.name
$http(
method: "PUT"
url: "/api/enterprises/#{@enterprise.id}"
url: "/api/v0/enterprises/#{@enterprise.id}"
data:
enterprise: @prepare()
use_geocoder: @useGeocoder()

View File

@@ -1,21 +1,21 @@
Darkswarm.factory 'OrderCycleResource', ($resource) ->
$resource('/api/order_cycles/:id.json', {}, {
$resource('/api/v0/order_cycles/:id.json', {}, {
'products':
method: 'GET'
isArray: true
url: '/api/order_cycles/:id/products.json'
url: '/api/v0/order_cycles/:id/products.json'
params:
id: '@id'
'taxons':
method: 'GET'
isArray: true
url: '/api/order_cycles/:id/taxons.json'
url: '/api/v0/order_cycles/:id/taxons.json'
params:
id: '@id'
'properties':
method: 'GET'
isArray: true
url: '/api/order_cycles/:id/properties.json'
url: '/api/v0/order_cycles/:id/properties.json'
params:
id: '@id'
})

View File

@@ -1,7 +1,7 @@
Darkswarm.factory 'ShopsResource', ($resource) ->
$resource('/api/shops/:id.json', {}, {
$resource('/api/v0/shops/:id.json', {}, {
'closed_shops':
method: 'GET'
isArray: true
url: '/api/shops/closed_shops.json'
url: '/api/v0/shops/closed_shops.json'
})

View File

@@ -1,17 +1,19 @@
Darkswarm.directive "questionMarkWithTooltip", ($tooltip)->
OFNShared.directive "questionMarkWithTooltip", ($tooltip)->
# We use the $tooltip service from Angular foundation to give us boilerplate
# Subsequently we patch the scope, template and restrictions
tooltip = $tooltip 'questionMarkWithTooltip', 'questionMarkWithTooltip', 'click'
tooltip.scope =
context: "="
tooltip.templateUrl = "question_mark_with_tooltip_icon.html"
key: "="
tooltip.templateUrl = "shared/question_mark_with_tooltip_icon.html"
tooltip.replace = true
tooltip.restrict = 'E'
tooltip
# This is automatically referenced via naming convention in $tooltip
Darkswarm.directive 'questionMarkWithTooltipPopup', ->
OFNShared.directive 'questionMarkWithTooltipPopup', ->
restrict: 'EA'
replace: true
templateUrl: 'question_mark_with_tooltip.html'
templateUrl: 'shared/question_mark_with_tooltip.html'
scope: false

View File

@@ -0,0 +1,4 @@
window.OFNShared = angular.module("OFNShared", [
"mm.foundation"
]).config ($httpProvider) ->
$httpProvider.defaults.headers.common["Accept"] = "application/json, text/javascript, */*"

View File

@@ -2,12 +2,21 @@
.columns.small-12
%h3{"ng-bind" => "::variant.extended_name"}
.row.variant-bulk-buy-price-summary
.columns.small-6
.variant-unit {{ ::variant.unit_to_display }}
.columns.small-6
.flex.variant-bulk-buy-price-summary{style: "justify-content: space-around;"}
.variant-unit
{{ ::variant.unit_to_display }}
.div
{{ variant.line_item.total_price | localizeCurrency }}
.unit-price{"ng-if": "show_unit_price"}
%question-mark-with-tooltip{"question-mark-with-tooltip": "_",
"question-mark-with-tooltip-append-to-body": "true",
"question-mark-with-tooltip-placement": "top",
"question-mark-with-tooltip-animation": true,
style: "margin-right: 5px",
key: "'js.shopfront.unit_price_tooltip'"}
{{ variant.unit_price_price | localizeCurrency }}&nbsp;/&nbsp;{{ variant.unit_price_unit }}
.row
.columns.small-12.medium-6
.variant-bulk-buy-quantity-label

View File

@@ -1,6 +1,6 @@
.joyride-tip-guide.question-mark-tooltip{class: "{{ context }}", ng: {class: "{ in: tt_isOpen, fade: tt_animation }", show: "tt_isOpen"}}
.background{ng: {click: "tt_isOpen = false"}}
.joyride-content-wrapper
{{ "js.shopfront.unit_price_tooltip" | t }}
{{ key | t }}
%span.joyride-nub.bottom

View File

@@ -12,8 +12,9 @@
%question-mark-with-tooltip{"question-mark-with-tooltip" => "_",
"question-mark-with-tooltip-append-to-body" => "true",
"question-mark-with-tooltip-placement" => "top",
"question-mark-with-tooltip-animation" => true}
{{ variant.unit_price_price | localizeCurrency }} / {{ variant.unit_price_unit }}
"question-mark-with-tooltip-animation" => true,
key: "'js.shopfront.unit_price_tooltip'"}
{{ variant.unit_price_price | localizeCurrency }}&nbsp;/&nbsp;{{ variant.unit_price_unit }}
.medium-2.large-2.columns.total-price
%span{"ng-class" => "{filled: variant.line_item.total_price}"}

View File

@@ -48,3 +48,6 @@
@import 'components/*';
@import 'pages/*';
@import '*';
@import "../shared/question-mark-icon";
@import "question-mark-tooltip";

View File

@@ -0,0 +1,18 @@
.joyride-tip-guide {
background: $color-3;
color: $white;
font-family: inherit;
font-weight: $font-weight-normal;
position: absolute;
z-index: 101;
padding: 5px 15px;
.joyride-nub.bottom {
border: 10px solid;
display: block;
height: 0;
position: absolute;
width: 0;
}
}

View File

@@ -117,6 +117,13 @@ button.bulk-buy-add.variant-quantity {
}
}
// Hide number arrows on Chrome, Safari, Edge, Opera
.variant-quantity::-webkit-outer-spin-button,
.variant-quantity::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
.variant-bulk-buy-price-summary {
color: $disabled-med;
margin-bottom: 1em;

View File

@@ -21,3 +21,5 @@
ofn-modal {
display: block;
}
@import "../shared/question-mark-icon";

View File

@@ -165,7 +165,3 @@ a.button.large {
.flex {
display: flex;
}
.disable-scroll {
overflow: hidden;
}

View File

@@ -1,3 +1,5 @@
@import "variables/variables";
.unit-price {
display: flex;
align-items: center;
@@ -26,14 +28,27 @@
background-image: none;
background-color: $teal-500;
&:focus {
outline: 0;
}
&::before {
@include icon-font;
content: "";
color: $white;
vertical-align: super;
}
}
}
// Question mark icon into a field
.field .question-mark-icon {
width: 15px;
min-width: 15px;
height: 15px;
margin-left: 2px;
}
.joyride-tip-guide.question-mark-tooltip {
width: 16rem;
max-width: 65%;
@@ -41,6 +56,7 @@
margin-left: -7.4rem;
margin-top: -0.1rem;
background-color: transparent;
z-index: $modal-zIndex + 1;
.background {
position: fixed;

View File

@@ -0,0 +1,28 @@
$padding-small: 0.5rem;
$radius-small: 0.25em;
$white: #fff;
$dynamic-blue: #3d8dd1;
$teal-500: #0096ad;
/* Defined in foundation-rails components/_reveal.scss */
$modal-zIndex: 1005;
@font-face {
font-family: 'OFN';
src: font-url('OFN-v2.eot');
src: font-url('OFN-v2.eot') format('embedded-opentype'),
font-url('OFN-v2.woff') format('woff'),
font-url('OFN-v2.ttf') format('truetype'),
font-url('OFN-v2.svg') format('svg');
font-weight: normal;
font-style: normal;
}
@mixin icon-font {
font-family: "OFN";
display: inline-block;
font-weight: normal;
font-style: normal;
font-variant: normal;
text-transform: none;
}

View File

@@ -0,0 +1,7 @@
# frozen_string_literal: true
class DistributorTitleComponent < ViewComponent::Base
def initialize(name:)
@name = name
end
end

View File

@@ -0,0 +1,7 @@
# frozen_string_literal: true
class ExampleComponent < ViewComponent::Base
def initialize(title:)
@title = title
end
end

View File

@@ -0,0 +1 @@
%h1 #{@title}

View File

@@ -18,21 +18,13 @@ module Admin
format.html
format.json do
render json: @collection,
each_serializer: index_each_serializer,
each_serializer: ::Api::Admin::CustomerWithBalanceSerializer,
tag_rule_mapping: tag_rule_mapping,
customer_tags: customer_tags_by_id
end
end
end
def index_each_serializer
if OpenFoodNetwork::FeatureToggle.enabled?(:customer_balance, spree_current_user)
::Api::Admin::CustomerWithBalanceSerializer
else
::Api::Admin::CustomerWithCalculatedBalanceSerializer
end
end
def show
render_as_json @customer, ams_prefix: params[:ams_prefix]
end
@@ -53,15 +45,12 @@ module Admin
# copy of Admin::ResourceController without flash notice
def destroy
invoke_callbacks(:destroy, :before)
if @object.destroy
invoke_callbacks(:destroy, :after)
respond_with(@object) do |format|
format.html { redirect_to location_after_destroy }
format.js { render partial: "spree/admin/shared/destroy" }
end
else
invoke_callbacks(:destroy, :fails)
respond_with(@object) do |format|
format.html { redirect_to location_after_destroy }
format.json { render json: { errors: @object.errors.full_messages }, status: :conflict }
@@ -73,21 +62,18 @@ module Admin
def collection
if json_request? && params[:enterprise_id].present?
customers_relation.
includes(:bill_address, :ship_address, user: :credit_cards)
CustomersWithBalance.new(managed_enterprise_id).query.
includes(
:enterprise,
{ bill_address: [:state, :country] },
{ ship_address: [:state, :country] },
user: :credit_cards
)
else
Customer.where('1=0')
end
end
def customers_relation
if OpenFoodNetwork::FeatureToggle.enabled?(:customer_balance, spree_current_user)
CustomersWithBalance.new(managed_enterprise_id).query
else
Customer.of(managed_enterprise_id)
end
end
def managed_enterprise_id
@managed_enterprise_id ||= Enterprise.managed_by(spree_current_user).
select('enterprises.id').find_by(id: params[:enterprise_id])

View File

@@ -48,12 +48,11 @@ module Admin
end
def update
invoke_callbacks(:update, :before)
tag_rules_attributes = params[object_name].delete :tag_rules_attributes
update_tag_rules(tag_rules_attributes) if tag_rules_attributes.present?
update_enterprise_notifications
if @object.update(enterprise_params)
invoke_callbacks(:update, :after)
flash[:success] = flash_message_for(@object, :successfully_updated)
respond_with(@object) do |format|
format.html { redirect_to location_after_save }
@@ -61,7 +60,6 @@ module Admin
format.json { render_as_json @object, ams_prefix: 'index', spree_current_user: spree_current_user }
end
else
invoke_callbacks(:update, :fails)
respond_with(@object) do |format|
format.json { render json: { errors: @object.errors.messages }, status: :unprocessable_entity }
end
@@ -219,7 +217,6 @@ module Admin
tag_rules_attributes.select{ |_i, attrs| attrs[:type].present? }.each do |_i, attrs|
rule = @object.tag_rules.find_by(id: attrs.delete(:id)) ||
attrs[:type].constantize.new(enterprise: @object)
create_calculator_for(rule, attrs) if rule.type == "TagRule::DiscountOrder" && rule.calculator.nil?
rule.update(attrs.permit(PermittedAttributes::TagRules.attributes))
end

View File

@@ -1,5 +1,3 @@
require 'action_callbacks'
module Admin
class ResourceController < Spree::Admin::BaseController
helper_method :new_object_url, :edit_object_url, :object_url, :collection_url
@@ -11,7 +9,6 @@ module Admin
respond_to :js, except: [:show, :index]
def new
invoke_callbacks(:new_action, :before)
respond_with(@object) do |format|
format.html { render layout: !request.xhr? }
format.js { render layout: false }
@@ -26,32 +23,26 @@ module Admin
end
def update
invoke_callbacks(:update, :before)
if @object.update(permitted_resource_params)
invoke_callbacks(:update, :after)
flash[:success] = flash_message_for(@object, :successfully_updated)
respond_with(@object) do |format|
format.html { redirect_to location_after_save }
format.js { render layout: false }
end
else
invoke_callbacks(:update, :fails)
respond_with(@object)
end
end
def create
invoke_callbacks(:create, :before)
@object.attributes = permitted_resource_params
if @object.save
invoke_callbacks(:create, :after)
flash[:success] = flash_message_for(@object, :successfully_created)
respond_with(@object) do |format|
format.html { redirect_to location_after_save }
format.js { render layout: false }
end
else
invoke_callbacks(:create, :fails)
respond_with(@object)
end
end
@@ -67,16 +58,13 @@ module Admin
end
def destroy
invoke_callbacks(:destroy, :before)
if @object.destroy
invoke_callbacks(:destroy, :after)
flash[:success] = flash_message_for(@object, :successfully_removed)
respond_with(@object) do |format|
format.html { redirect_to collection_url }
format.js { render partial: "spree/admin/shared/destroy" }
end
else
invoke_callbacks(:destroy, :fails)
respond_with(@object) do |format|
format.html { redirect_to collection_url }
end
@@ -92,7 +80,6 @@ module Admin
class << self
attr_accessor :parent_data
attr_accessor :callbacks
def belongs_to(model_name, options = {})
@parent_data ||= {}
@@ -100,26 +87,6 @@ module Admin
@parent_data[:model_class] = model_name.to_s.classify.constantize
@parent_data[:find_by] = options[:find_by] || :id
end
def new_action
@callbacks ||= {}
@callbacks[:new_action] ||= ActionCallbacks.new
end
def create
@callbacks ||= {}
@callbacks[:create] ||= ActionCallbacks.new
end
def update
@callbacks ||= {}
@callbacks[:update] ||= ActionCallbacks.new
end
def destroy
@callbacks ||= {}
@callbacks[:destroy] ||= ActionCallbacks.new
end
end
def model_class
@@ -212,17 +179,6 @@ module Admin
collection_url
end
def invoke_callbacks(action, callback_type)
callbacks = self.class.callbacks || {}
return if callbacks[action].nil?
case callback_type.to_sym
when :before then callbacks[action].before_methods.each { |method| __send__ method }
when :after then callbacks[action].after_methods.each { |method| __send__ method }
when :fails then callbacks[action].fails_methods.each { |method| __send__ method }
end
end
# URL helpers
def new_object_url(options = {})
if parent_data.present?

View File

@@ -9,7 +9,8 @@ module Admin
before_action :editable_order_cycle_ids_for_create, only: [:create]
before_action :editable_order_cycle_ids_for_update, only: [:update]
before_action :check_dependent_subscriptions, only: [:destroy]
update.after :sync_subscriptions_for_update
after_action :sync_subscriptions_for_update, only: :update
respond_to :json
@@ -120,7 +121,7 @@ module Admin
end
def sync_subscriptions_for_update
return unless params[:schedule][:order_cycle_ids]
return unless params[:schedule][:order_cycle_ids] && @object.errors.blank?
sync_subscriptions
end

View File

@@ -1,103 +0,0 @@
# Base controller for OFN's API
require_dependency 'spree/api/controller_setup'
require "spree/core/controller_helpers/ssl"
module Api
class BaseController < ActionController::Metal
include RawParams
include ActionController::StrongParameters
include ActionController::RespondWith
include Spree::Api::ControllerSetup
include Spree::Core::ControllerHelpers::SSL
include ::ActionController::Head
include ::ActionController::ConditionalGet
include ActionView::Layouts
layout false
attr_accessor :current_api_user
before_action :set_content_type
before_action :authenticate_user
rescue_from Exception, with: :error_during_processing
rescue_from CanCan::AccessDenied, with: :unauthorized
rescue_from ActiveRecord::RecordNotFound, with: :not_found
helper Spree::Api::ApiHelpers
ssl_allowed
# Include these because we inherit from ActionController::Metal
# rather than ActionController::Base and these are required for AMS
include ActionController::Serialization
include ActionController::UrlFor
include Rails.application.routes.url_helpers
use_renderers :json
check_authorization
def respond_with_conflict(json_hash)
render json: json_hash, status: :conflict
end
private
# Use logged in user (spree_current_user) for API authentication (current_api_user)
def authenticate_user
return if @current_api_user = spree_current_user
if api_key.blank?
# An anonymous user
@current_api_user = Spree.user_class.new
return
end
return if @current_api_user = Spree.user_class.find_by(spree_api_key: api_key.to_s)
invalid_api_key
end
def set_content_type
headers["Content-Type"] = "application/json"
end
def error_during_processing(exception)
Bugsnag.notify(exception)
render(json: { exception: exception.message },
status: :unprocessable_entity) && return
end
def current_ability
Spree::Ability.new(current_api_user)
end
def api_key
request.headers["X-Spree-Token"] || params[:token]
end
helper_method :api_key
def invalid_resource!(resource)
@resource = resource
render(json: { error: I18n.t(:invalid_resource, scope: "spree.api"),
errors: @resource.errors },
status: :unprocessable_entity)
end
def invalid_api_key
render(json: { error: I18n.t(:invalid_api_key, key: api_key, scope: "spree.api") },
status: :unauthorized) && return
end
def unauthorized
render(json: { error: I18n.t(:unauthorized, scope: "spree.api") },
status: :unauthorized) && return
end
def not_found
render(json: { error: I18n.t(:resource_not_found, scope: "spree.api") },
status: :not_found) && return
end
end
end

View File

@@ -1,37 +0,0 @@
module Api
class CustomersController < Api::BaseController
skip_authorization_check only: :index
def index
@customers = current_api_user.customers
render json: @customers, each_serializer: CustomerSerializer
end
def update
@customer = Customer.find(params[:id])
authorize! :update, @customer
client_secret = RecurringPayments.setup_for(@customer) if params[:customer][:allow_charges]
if @customer.update(customer_params)
add_recurring_payment_info(client_secret)
render json: @customer, serializer: CustomerSerializer, status: :ok
else
invalid_resource!(@customer)
end
end
private
def add_recurring_payment_info(client_secret)
return unless client_secret
@customer.gateway_recurring_payment_client_secret = client_secret
@customer.gateway_shop_id = @customer.enterprise.stripe_account&.stripe_user_id
end
def customer_params
params.require(:customer).permit(:code, :email, :enterprise_id, :allow_charges)
end
end
end

View File

@@ -1,42 +0,0 @@
# frozen_string_literal: true
require 'api/admin/enterprise_serializer'
module Api
class EnterpriseAttachmentController < Api::BaseController
class MissingImplementationError < StandardError; end
class UnknownEnterpriseAuthorizationActionError < StandardError; end
before_action :load_enterprise
respond_to :json
def destroy
return respond_with_conflict(error: destroy_attachment_does_not_exist_error_message) unless @enterprise.public_send("#{attachment_name}?")
@enterprise.update!(attachment_name => nil)
render json: @enterprise, serializer: Admin::EnterpriseSerializer, spree_current_user: spree_current_user
end
protected
def attachment_name
raise MissingImplementationError, "Method attachment_name should be defined"
end
def enterprise_authorize_action
raise MissingImplementationError, "Method enterprise_authorize_action should be defined"
end
def load_enterprise
@enterprise = Enterprise.find_by(permalink: params[:enterprise_id].to_s)
raise UnknownEnterpriseAuthorizationActionError if enterprise_authorize_action.blank?
authorize!(enterprise_authorize_action, @enterprise)
end
def destroy_attachment_does_not_exist_error_message
I18n.t("api.enterprise_#{attachment_name}.destroy_attachment_does_not_exist")
end
end
end

View File

@@ -1,21 +0,0 @@
module Api
class EnterpriseFeesController < Api::BaseController
respond_to :json
def destroy
authorize! :destroy, enterprise_fee
if enterprise_fee.destroy
render plain: I18n.t(:successfully_removed), status: :no_content
else
render plain: enterprise_fee.errors.full_messages.first, status: :forbidden
end
end
private
def enterprise_fee
@enterprise_fee ||= EnterpriseFee.find_by id: params[:id]
end
end
end

View File

@@ -1,82 +0,0 @@
module Api
class EnterprisesController < Api::BaseController
include GeocodeEnterpriseAddress
before_action :override_owner, only: [:create, :update]
before_action :check_type, only: :update
before_action :override_sells, only: [:create, :update]
before_action :override_visible, only: [:create, :update]
respond_to :json
def create
authorize! :create, Enterprise
# params[:user_ids] breaks the enterprise creation
# We remove them from params and save them after creating the enterprise
user_ids = enterprise_params.delete(:user_ids)
@enterprise = Enterprise.new(enterprise_params)
if @enterprise.save
geocode_address_if_use_geocoder
@enterprise.user_ids = user_ids
render json: @enterprise.id, status: :created
else
invalid_resource!(@enterprise)
end
end
def update
@enterprise = Enterprise.find_by(permalink: params[:id]) || Enterprise.find(params[:id])
authorize! :update, @enterprise
if @enterprise.update(enterprise_params)
geocode_address_if_use_geocoder
render json: @enterprise.id, status: :ok
else
invalid_resource!(@enterprise)
end
end
def update_image
@enterprise = Enterprise.find_by(permalink: params[:id]) || Enterprise.find(params[:id])
authorize! :update, @enterprise
if params[:logo] && @enterprise.update( logo: params[:logo] )
render plain: @enterprise.logo.url(:medium), status: :ok
elsif params[:promo] && @enterprise.update( promo_image: params[:promo] )
render plain: @enterprise.promo_image.url(:medium), status: :ok
else
invalid_resource!(@enterprise)
end
end
private
def override_owner
enterprise_params[:owner_id] = current_api_user.id
end
def check_type
enterprise_params.delete :type unless current_api_user.admin?
end
def override_sells
has_hub = current_api_user.owned_enterprises.is_hub.any?
new_enterprise_is_producer = !!enterprise_params[:is_primary_producer]
enterprise_params[:sells] = if has_hub && !new_enterprise_is_producer
'any'
else
'unspecified'
end
end
def override_visible
enterprise_params[:visible] = false
end
def enterprise_params
@enterprise_params ||= PermittedAttributes::Enterprise.new(params).call.
to_h.with_indifferent_access
end
end
end

View File

@@ -1,100 +0,0 @@
# frozen_string_literal: true
# This controller lists products that can be added to an exchange
#
# Pagination is optional and can be required by using param[:page]
module Api
class ExchangeProductsController < Api::BaseController
include PaginationData
DEFAULT_PER_PAGE = 100
skip_authorization_check only: [:index]
# If exchange_id is present in the URL:
# Lists Products that can be added to that Exchange
#
# If exchange_id is not present in the URL:
# Lists Products of the Enterprise given that can be added to the given Order Cycle
# In this case parameters are: enterprise_id, order_cycle_id and incoming
# (order_cycle_id is not necessary for incoming exchanges)
def index
if exchange_params[:exchange_id].present?
load_data_from_exchange
else
load_data_from_other_params
end
render_variant_count && return if params[:action_name] == "variant_count"
render_paginated_products paginated_products
end
private
def render_variant_count
render plain: {
count: variants.count
}.to_json
end
def variants
renderer.exchange_variants(@incoming, @enterprise)
end
def products
renderer.exchange_products(@incoming, @enterprise)
end
def renderer
@renderer ||= ExchangeProductsRenderer.
new(@order_cycle, spree_current_user)
end
def paginated_products
return products unless pagination_required?
products.
page(params[:page]).
per(params[:per_page] || DEFAULT_PER_PAGE)
end
def load_data_from_exchange
exchange = Exchange.find_by(id: exchange_params[:exchange_id])
@order_cycle = exchange.order_cycle
@incoming = exchange.incoming
@enterprise = exchange.sender
end
def load_data_from_other_params
@enterprise = Enterprise.find_by(id: exchange_params[:enterprise_id])
# This will be a string (eg "true") when it arrives via params, but we want a boolean
@incoming = ActiveModel::Type::Boolean.new.cast exchange_params[:incoming]
if exchange_params[:order_cycle_id]
@order_cycle = OrderCycle.find_by(id: exchange_params[:order_cycle_id])
elsif !@incoming
raise "order_cycle_id is required to list products for new outgoing exchange"
end
end
def render_paginated_products(paginated_products)
serialized_products = ActiveModel::ArraySerializer.new(
paginated_products,
each_serializer: Api::Admin::ForOrderCycle::SuppliedProductSerializer,
order_cycle: @order_cycle
)
render json: {
products: serialized_products,
pagination: pagination_data(paginated_products)
}
end
def exchange_params
params.permit(:enterprise_id, :exchange_id, :order_cycle_id, :incoming).
to_h.with_indifferent_access
end
end
end

View File

@@ -1,16 +0,0 @@
module Api
class LogosController < Api::EnterpriseAttachmentController
private
def attachment_name
:logo
end
def enterprise_authorize_action
case action_name.to_sym
when :destroy
:remove_logo
end
end
end
end

View File

@@ -1,101 +0,0 @@
module Api
class OrderCyclesController < Api::BaseController
include EnterprisesHelper
include ApiActionCaching
skip_authorization_check
skip_before_action :authenticate_user, :ensure_api_key, only: [:taxons, :properties]
caches_action :taxons, :properties,
expires_in: CacheService::FILTERS_EXPIRY,
cache_path: proc { |controller| controller.request.url }
def products
return render_no_products unless order_cycle.open?
products = ProductsRenderer.new(
distributor,
order_cycle,
customer,
search_params
).products_json
render plain: products
rescue ProductsRenderer::NoProducts
render_no_products
end
def taxons
taxons = Spree::Taxon.
joins(:products).
where(spree_products: { id: distributed_products }).
select('DISTINCT spree_taxons.*')
render plain: ActiveModel::ArraySerializer.new(
taxons, each_serializer: Api::TaxonSerializer
).to_json
end
def properties
render plain: ActiveModel::ArraySerializer.new(
product_properties | producer_properties, each_serializer: Api::PropertySerializer
).to_json
end
private
def render_no_products
render status: :not_found, json: {}
end
def product_properties
Spree::Property.
joins(:products).
where(spree_products: { id: distributed_products }).
select('DISTINCT spree_properties.*')
end
def producer_properties
producers = Enterprise.
joins(:supplied_products).
where(spree_products: { id: distributed_products })
Spree::Property.
joins(:producer_properties).
where(producer_properties: { producer_id: producers }).
select('DISTINCT spree_properties.*')
end
def search_params
permitted_search_params = params.slice :q, :page, :per_page
if permitted_search_params.key? :q
permitted_search_params[:q].slice!(*permitted_ransack_params)
end
permitted_search_params
end
def permitted_ransack_params
[:name_or_meta_keywords_or_variants_display_as_or_variants_display_name_or_supplier_name_cont,
:properties_id_or_supplier_properties_id_in_any,
:primary_taxon_id_in_any]
end
def distributor
@distributor ||= Enterprise.find_by(id: params[:distributor])
end
def order_cycle
@order_cycle ||= OrderCycle.find_by(id: params[:id])
end
def customer
@current_api_user.andand.customer_of(distributor) || nil
end
def distributed_products
OrderCycleDistributedProducts.new(distributor, order_cycle, customer).products_relation
end
end
end

View File

@@ -1,67 +0,0 @@
module Api
class OrdersController < Api::BaseController
include PaginationData
def show
authorize! :read, order
render json: order, serializer: Api::OrderDetailedSerializer, current_order: order
end
def index
authorize! :admin, Spree::Order
orders = SearchOrders.new(params, current_api_user).orders
render json: {
orders: serialized_orders(orders),
pagination: pagination_data(orders)
}
end
def ship
authorize! :admin, order
if order.ship
render json: order.reload, serializer: Api::Admin::OrderSerializer, status: :ok
else
render json: { error: I18n.t('api.orders.failed_to_update') }, status: :unprocessable_entity
end
end
def capture
authorize! :admin, order
pending_payment = order.pending_payments.first
return payment_capture_failed unless order.payment_required? && pending_payment
if pending_payment.capture!
render json: order.reload, serializer: Api::Admin::OrderSerializer, status: :ok
else
payment_capture_failed
end
rescue Spree::Core::GatewayError => e
error_during_processing(e)
end
private
def payment_capture_failed
render json: { error: I18n.t(:payment_processing_failed) }, status: :unprocessable_entity
end
def serialized_orders(orders)
ActiveModel::ArraySerializer.new(
orders,
each_serializer: Api::Admin::OrderSerializer
)
end
def order
@order ||= Spree::Order.
where(number: params[:id]).
includes(line_items: { variant: [:product, :stock_items, :default_price] }).
first!
end
end
end

View File

@@ -1,19 +0,0 @@
module Api
class ProductImagesController < Api::BaseController
respond_to :json
def update_product_image
@product = Spree::Product.find(params[:product_id])
authorize! :update, @product
if @product.images.first.nil?
@image = Spree::Image.create(attachment: params[:file], viewable_id: @product.master.id, viewable_type: 'Spree::Variant')
render json: @image, serializer: ImageSerializer, status: :created
else
@image = @product.images.first
@image.update(attachment: params[:file])
render json: @image, serializer: ImageSerializer, status: :ok
end
end
end
end

View File

@@ -1,159 +0,0 @@
require 'open_food_network/permissions'
require 'spree/core/product_duplicator'
module Api
class ProductsController < Api::BaseController
include PaginationData
respond_to :json
DEFAULT_PER_PAGE = 15
before_action :set_default_available_on, only: :create
skip_authorization_check only: [:show, :bulk_products, :overridable]
def show
@product = find_product(params[:id])
render json: @product, serializer: Api::Admin::ProductSerializer
end
def create
authorize! :create, Spree::Product
@product = Spree::Product.new(product_params)
begin
if @product.save
render json: @product, serializer: Api::Admin::ProductSerializer, status: :created
else
invalid_resource!(@product)
end
rescue ActiveRecord::RecordNotUnique
@product.permalink = nil
retry
end
end
def update
authorize! :update, Spree::Product
@product = find_product(params[:id])
if @product.update(product_params)
render json: @product, serializer: Api::Admin::ProductSerializer, status: :ok
else
invalid_resource!(@product)
end
end
def destroy
authorize! :delete, Spree::Product
@product = find_product(params[:id])
authorize! :delete, @product
@product.destroy
render json: @product, serializer: Api::Admin::ProductSerializer, status: :no_content
end
def bulk_products
product_query = OpenFoodNetwork::Permissions.
new(current_api_user).
editable_products.
merge(product_scope)
if params[:import_date].present?
product_query = product_query.
imported_on(params[:import_date]).
group_by_products_id
end
@products = product_query.
ransack(query_params_with_defaults).
result.
page(params[:page] || 1).
per(params[:per_page] || DEFAULT_PER_PAGE)
render_paged_products @products
end
def overridable
producer_ids = OpenFoodNetwork::Permissions.new(current_api_user).
variant_override_producers.by_name.select('enterprises.id')
@products = paged_products_for_producers producer_ids
render_paged_products @products, ::Api::Admin::ProductSimpleSerializer
end
# POST /api/products/:product_id/clone
#
def clone
authorize! :create, Spree::Product
original_product = find_product(params[:product_id])
authorize! :update, original_product
@product = original_product.duplicate
render json: @product, serializer: Api::Admin::ProductSerializer, status: :created
end
private
def find_product(id)
product_scope.find_by!(permalink: id.to_s)
rescue ActiveRecord::RecordNotFound
product_scope.find(id)
end
def product_scope
if current_api_user.has_spree_role?("admin") || current_api_user.enterprises.present?
scope = Spree::Product
if params[:show_deleted]
scope = scope.with_deleted
end
else
scope = Spree::Product.active
end
scope.includes(product_query_includes)
end
def product_query_includes
[
master: [:images],
variants: [:default_price, :stock_locations, :stock_items, :variant_overrides,
{ option_values: :option_type }]
]
end
def paged_products_for_producers(producer_ids)
Spree::Product.where(nil).
merge(product_scope).
includes(variants: [:product, :default_price, :stock_items]).
where(supplier_id: producer_ids).
by_producer.by_name.
ransack(params[:q]).result.
page(params[:page]).per(params[:per_page])
end
def render_paged_products(products, product_serializer = ::Api::Admin::ProductSerializer)
serialized_products = ActiveModel::ArraySerializer.new(
products,
each_serializer: product_serializer
)
render json: {
products: serialized_products,
pagination: pagination_data(products)
}
end
def query_params_with_defaults
(params[:q] || {}).reverse_merge(s: 'created_at desc')
end
def product_params
@product_params ||=
params.permit(product: PermittedAttributes::Product.attributes)[:product].to_h
end
def set_default_available_on
product_params[:available_on] ||= Time.zone.now
end
end
end

View File

@@ -1,16 +0,0 @@
module Api
class PromoImagesController < Api::EnterpriseAttachmentController
private
def attachment_name
:promo_image
end
def enterprise_authorize_action
case action_name.to_sym
when :destroy
:remove_promo_image
end
end
end
end

View File

@@ -1,112 +0,0 @@
require 'open_food_network/scope_variant_to_hub'
module Api
class ShipmentsController < Api::BaseController
respond_to :json
before_action :find_order
before_action :find_and_update_shipment, only: [:ship, :ready, :add, :remove]
def create
variant = scoped_variant(params[:variant_id])
quantity = params[:quantity].to_i
@shipment = get_or_create_shipment(params[:stock_location_id])
@order.contents.add(variant, quantity, nil, @shipment)
@shipment.refresh_rates
@shipment.save!
render json: @shipment.reload, serializer: Api::ShipmentSerializer, status: :ok
end
def update
authorize! :read, Spree::Shipment
@shipment = @order.shipments.find_by!(number: params[:id])
params[:shipment] ||= []
unlock = params[:shipment].delete(:unlock)
if unlock == 'yes'
@shipment.fee_adjustment.open
end
@shipment.update(shipment_params[:shipment])
if unlock == 'yes'
@shipment.fee_adjustment.close
end
render json: @shipment.reload, serializer: Api::ShipmentSerializer, status: :ok
end
def ready
authorize! :read, Spree::Shipment
unless @shipment.ready?
if @shipment.can_ready?
@shipment.ready!
else
render(json: { error: I18n.t(:cannot_ready, scope: "spree.api.shipment") },
status: :unprocessable_entity) && return
end
end
render json: @shipment, serializer: Api::ShipmentSerializer, status: :ok
end
def ship
authorize! :read, Spree::Shipment
unless @shipment.shipped?
@shipment.ship!
end
render json: @shipment, serializer: Api::ShipmentSerializer, status: :ok
end
def add
variant = scoped_variant(params[:variant_id])
quantity = params[:quantity].to_i
@order.contents.add(variant, quantity, nil, @shipment)
render json: @shipment, serializer: Api::ShipmentSerializer, status: :ok
end
def remove
variant = scoped_variant(params[:variant_id])
quantity = params[:quantity].to_i
@order.contents.remove(variant, quantity, @shipment)
@shipment.reload if @shipment.persisted?
render json: @shipment, serializer: Api::ShipmentSerializer, status: :ok
end
private
def find_order
@order = Spree::Order.find_by!(number: params[:order_id])
authorize! :read, @order
end
def find_and_update_shipment
@shipment = @order.shipments.find_by!(number: params[:id])
@shipment.update(shipment_params[:shipment]) if shipment_params[:shipment].present?
@shipment.reload
end
def scoped_variant(variant_id)
variant = Spree::Variant.find(variant_id)
OpenFoodNetwork::ScopeVariantToHub.new(@order.distributor).scope(variant)
variant
end
def get_or_create_shipment(stock_location_id)
@order.shipment || @order.shipments.create(stock_location_id: stock_location_id)
end
def shipment_params
params.permit(
[:id, :order_id, :variant_id, :quantity,
{ shipment: [:tracking, :selected_shipping_rate_id] }]
)
end
end
end

View File

@@ -1,27 +0,0 @@
# frozen_string_literal: true
module Api
class ShopsController < BaseController
respond_to :json
skip_authorization_check only: [:show, :closed_shops]
def show
enterprise = Enterprise.find_by(id: params[:id])
render plain: Api::EnterpriseShopfrontSerializer.new(enterprise).to_json, status: :ok
end
def closed_shops
@active_distributor_ids = []
@earliest_closing_times = []
serialized_closed_shops = ActiveModel::ArraySerializer.new(
ShopsListService.new.closed_shops,
each_serializer: Api::EnterpriseSerializer,
data: OpenFoodNetwork::EnterpriseInjectionData.new
)
render json: serialized_closed_shops
end
end
end

View File

@@ -1,44 +0,0 @@
# frozen_string_literal: true
module Api
class StatesController < Api::BaseController
respond_to :json
skip_authorization_check
def index
render json: states, each_serializer: Api::StateSerializer, status: :ok
end
def show
@state = scope.find(params[:id])
render json: @state, serializer: Api::StateSerializer, status: :ok
end
private
def scope
if params[:country_id]
@country = Spree::Country.find(params[:country_id])
@country.states
else
Spree::State.all
end
end
def states
states = scope.ransack(params[:q]).result.
includes(:country).order('name ASC')
if pagination?
states = states.page(params[:page]).per(params[:per_page])
end
states
end
def pagination?
params[:page] || params[:per_page]
end
end
end

View File

@@ -1,16 +0,0 @@
module Api
class StatusesController < ::BaseController
respond_to :json
def job_queue
render json: { alive: job_queue_alive? }
end
private
def job_queue_alive?
Spree::Config.last_job_queue_heartbeat_at.present? &&
Time.parse(Spree::Config.last_job_queue_heartbeat_at).in_time_zone > 6.minutes.ago
end
end
end

View File

@@ -1,12 +0,0 @@
module Api
class TaxonomiesController < Api::BaseController
respond_to :json
skip_authorization_check only: :jstree
def jstree
@taxonomy = Spree::Taxonomy.find(params[:id])
render json: @taxonomy.root, serializer: Api::TaxonJstreeSerializer
end
end
end

View File

@@ -1,76 +0,0 @@
module Api
class TaxonsController < Api::BaseController
respond_to :json
skip_authorization_check only: [:index, :show, :jstree]
def index
@taxons = if taxonomy
taxonomy.root.children
elsif params[:ids]
Spree::Taxon.where(id: raw_params[:ids].split(","))
else
Spree::Taxon.ransack(raw_params[:q]).result
end
render json: @taxons, each_serializer: Api::TaxonSerializer
end
def jstree
@taxon = taxon
render json: @taxon.children, each_serializer: Api::TaxonJstreeSerializer
end
def create
authorize! :create, Spree::Taxon
@taxon = Spree::Taxon.new(taxon_params)
@taxon.taxonomy_id = params[:taxonomy_id]
taxonomy = Spree::Taxonomy.find_by(id: params[:taxonomy_id])
if taxonomy.nil?
@taxon.errors[:taxonomy_id] = I18n.t(:invalid_taxonomy_id, scope: 'spree.api')
invalid_resource!(@taxon) && return
end
@taxon.parent_id = taxonomy.root.id unless params.dig(:taxon, :parent_id)
if @taxon.save
render json: @taxon, serializer: Api::TaxonSerializer, status: :created
else
invalid_resource!(@taxon)
end
end
def update
authorize! :update, Spree::Taxon
if taxon.update(taxon_params)
render json: taxon, serializer: Api::TaxonSerializer, status: :ok
else
invalid_resource!(taxon)
end
end
def destroy
authorize! :delete, Spree::Taxon
taxon.destroy
render json: taxon, serializer: Api::TaxonSerializer, status: :no_content
end
private
def taxonomy
return if params[:taxonomy_id].blank?
@taxonomy ||= Spree::Taxonomy.find(params[:taxonomy_id])
end
def taxon
@taxon ||= taxonomy.taxons.find(params[:id])
end
def taxon_params
return if params[:taxon].blank?
params.require(:taxon).permit([:name, :parent_id])
end
end
end

View File

@@ -1,18 +0,0 @@
# frozen_string_literal: true
module Api
class TermsAndConditionsController < Api::EnterpriseAttachmentController
private
def attachment_name
:terms_and_conditions
end
def enterprise_authorize_action
case action_name.to_sym
when :destroy
:remove_terms_and_conditions
end
end
end
end

View File

@@ -0,0 +1,105 @@
# frozen_string_literal: true
# Base controller for OFN's API
require_dependency 'spree/api/controller_setup'
require "spree/core/controller_helpers/ssl"
module Api
module V0
class BaseController < ActionController::Metal
include RawParams
include ActionController::StrongParameters
include ActionController::RespondWith
include Spree::Api::ControllerSetup
include Spree::Core::ControllerHelpers::SSL
include ::ActionController::Head
include ::ActionController::ConditionalGet
include ActionView::Layouts
layout false
attr_accessor :current_api_user
before_action :set_content_type
before_action :authenticate_user
rescue_from Exception, with: :error_during_processing
rescue_from CanCan::AccessDenied, with: :unauthorized
rescue_from ActiveRecord::RecordNotFound, with: :not_found
ssl_allowed
# Include these because we inherit from ActionController::Metal
# rather than ActionController::Base and these are required for AMS
include ActionController::Serialization
include ActionController::UrlFor
include Rails.application.routes.url_helpers
use_renderers :json
check_authorization
def respond_with_conflict(json_hash)
render json: json_hash, status: :conflict
end
private
# Use logged in user (spree_current_user) for API authentication (current_api_user)
def authenticate_user
return if @current_api_user = spree_current_user
if api_key.blank?
# An anonymous user
@current_api_user = Spree.user_class.new
return
end
return if @current_api_user = Spree.user_class.find_by(spree_api_key: api_key.to_s)
invalid_api_key
end
def set_content_type
headers["Content-Type"] = "application/json"
end
def error_during_processing(exception)
Bugsnag.notify(exception)
render(json: { exception: exception.message },
status: :unprocessable_entity) && return
end
def current_ability
Spree::Ability.new(current_api_user)
end
def api_key
request.headers["X-Spree-Token"] || params[:token]
end
helper_method :api_key
def invalid_resource!(resource)
@resource = resource
render(json: { error: I18n.t(:invalid_resource, scope: "spree.api"),
errors: @resource.errors },
status: :unprocessable_entity)
end
def invalid_api_key
render(json: { error: I18n.t(:invalid_api_key, key: api_key, scope: "spree.api") },
status: :unauthorized) && return
end
def unauthorized
render(json: { error: I18n.t(:unauthorized, scope: "spree.api") },
status: :unauthorized) && return
end
def not_found
render(json: { error: I18n.t(:resource_not_found, scope: "spree.api") },
status: :not_found) && return
end
end
end
end

View File

@@ -0,0 +1,41 @@
# frozen_string_literal: true
module Api
module V0
class CustomersController < Api::V0::BaseController
skip_authorization_check only: :index
def index
@customers = current_api_user.customers
render json: @customers, each_serializer: CustomerSerializer
end
def update
@customer = Customer.find(params[:id])
authorize! :update, @customer
client_secret = RecurringPayments.setup_for(@customer) if params[:customer][:allow_charges]
if @customer.update(customer_params)
add_recurring_payment_info(client_secret)
render json: @customer, serializer: CustomerSerializer, status: :ok
else
invalid_resource!(@customer)
end
end
private
def add_recurring_payment_info(client_secret)
return unless client_secret
@customer.gateway_recurring_payment_client_secret = client_secret
@customer.gateway_shop_id = @customer.enterprise.stripe_account&.stripe_user_id
end
def customer_params
params.require(:customer).permit(:code, :email, :enterprise_id, :allow_charges)
end
end
end
end

View File

@@ -0,0 +1,49 @@
# frozen_string_literal: true
require 'api/admin/enterprise_serializer'
module Api
module V0
class EnterpriseAttachmentController < Api::V0::BaseController
class MissingImplementationError < StandardError; end
class UnknownEnterpriseAuthorizationActionError < StandardError; end
before_action :load_enterprise
respond_to :json
def destroy
unless @enterprise.public_send("#{attachment_name}?")
return respond_with_conflict(error: destroy_attachment_does_not_exist_error_message)
end
@enterprise.update!(attachment_name => nil)
render json: @enterprise,
serializer: Admin::EnterpriseSerializer,
spree_current_user: spree_current_user
end
protected
def attachment_name
raise MissingImplementationError, "Method attachment_name should be defined"
end
def enterprise_authorize_action
raise MissingImplementationError, "Method enterprise_authorize_action should be defined"
end
def load_enterprise
@enterprise = Enterprise.find_by(permalink: params[:enterprise_id].to_s)
raise UnknownEnterpriseAuthorizationActionError if enterprise_authorize_action.blank?
authorize!(enterprise_authorize_action, @enterprise)
end
def destroy_attachment_does_not_exist_error_message
I18n.t("api.enterprise_#{attachment_name}.destroy_attachment_does_not_exist")
end
end
end
end

View File

@@ -0,0 +1,25 @@
# frozen_string_literal: true
module Api
module V0
class EnterpriseFeesController < Api::V0::BaseController
respond_to :json
def destroy
authorize! :destroy, enterprise_fee
if enterprise_fee.destroy
render plain: I18n.t(:successfully_removed), status: :no_content
else
render plain: enterprise_fee.errors.full_messages.first, status: :forbidden
end
end
private
def enterprise_fee
@enterprise_fee ||= EnterpriseFee.find_by id: params[:id]
end
end
end
end

View File

@@ -0,0 +1,86 @@
# frozen_string_literal: true
module Api
module V0
class EnterprisesController < Api::V0::BaseController
include GeocodeEnterpriseAddress
before_action :override_owner, only: [:create, :update]
before_action :check_type, only: :update
before_action :override_sells, only: [:create, :update]
before_action :override_visible, only: [:create, :update]
respond_to :json
def create
authorize! :create, Enterprise
# params[:user_ids] breaks the enterprise creation
# We remove them from params and save them after creating the enterprise
user_ids = enterprise_params.delete(:user_ids)
@enterprise = Enterprise.new(enterprise_params)
if @enterprise.save
geocode_address_if_use_geocoder
@enterprise.user_ids = user_ids
render json: @enterprise.id, status: :created
else
invalid_resource!(@enterprise)
end
end
def update
@enterprise = Enterprise.find_by(permalink: params[:id]) || Enterprise.find(params[:id])
authorize! :update, @enterprise
if @enterprise.update(enterprise_params)
geocode_address_if_use_geocoder
render json: @enterprise.id, status: :ok
else
invalid_resource!(@enterprise)
end
end
def update_image
@enterprise = Enterprise.find_by(permalink: params[:id]) || Enterprise.find(params[:id])
authorize! :update, @enterprise
if params[:logo] && @enterprise.update( logo: params[:logo] )
render plain: @enterprise.logo.url(:medium), status: :ok
elsif params[:promo] && @enterprise.update( promo_image: params[:promo] )
render plain: @enterprise.promo_image.url(:medium), status: :ok
else
invalid_resource!(@enterprise)
end
end
private
def override_owner
enterprise_params[:owner_id] = current_api_user.id
end
def check_type
enterprise_params.delete :type unless current_api_user.admin?
end
def override_sells
has_hub = current_api_user.owned_enterprises.is_hub.any?
new_enterprise_is_producer = !!enterprise_params[:is_primary_producer]
enterprise_params[:sells] = if has_hub && !new_enterprise_is_producer
'any'
else
'unspecified'
end
end
def override_visible
enterprise_params[:visible] = false
end
def enterprise_params
@enterprise_params ||= PermittedAttributes::Enterprise.new(params).call.
to_h.with_indifferent_access
end
end
end
end

View File

@@ -0,0 +1,102 @@
# frozen_string_literal: true
# This controller lists products that can be added to an exchange
#
# Pagination is optional and can be required by using param[:page]
module Api
module V0
class ExchangeProductsController < Api::V0::BaseController
include PaginationData
DEFAULT_PER_PAGE = 100
skip_authorization_check only: [:index]
# If exchange_id is present in the URL:
# Lists Products that can be added to that Exchange
#
# If exchange_id is not present in the URL:
# Lists Products of the Enterprise given that can be added to the given Order Cycle
# In this case parameters are: enterprise_id, order_cycle_id and incoming
# (order_cycle_id is not necessary for incoming exchanges)
def index
if exchange_params[:exchange_id].present?
load_data_from_exchange
else
load_data_from_other_params
end
render_variant_count && return if params[:action_name] == "variant_count"
render_paginated_products paginated_products
end
private
def render_variant_count
render plain: {
count: variants.count
}.to_json
end
def variants
renderer.exchange_variants(@incoming, @enterprise)
end
def products
renderer.exchange_products(@incoming, @enterprise)
end
def renderer
@renderer ||= ExchangeProductsRenderer.
new(@order_cycle, spree_current_user)
end
def paginated_products
return products unless pagination_required?
products.
page(params[:page]).
per(params[:per_page] || DEFAULT_PER_PAGE)
end
def load_data_from_exchange
exchange = Exchange.find_by(id: exchange_params[:exchange_id])
@order_cycle = exchange.order_cycle
@incoming = exchange.incoming
@enterprise = exchange.sender
end
def load_data_from_other_params
@enterprise = Enterprise.find_by(id: exchange_params[:enterprise_id])
# This will be a string (eg "true") when it arrives via params, but we want a boolean
@incoming = ActiveModel::Type::Boolean.new.cast exchange_params[:incoming]
if exchange_params[:order_cycle_id]
@order_cycle = OrderCycle.find_by(id: exchange_params[:order_cycle_id])
elsif !@incoming
raise "order_cycle_id is required to list products for new outgoing exchange"
end
end
def render_paginated_products(paginated_products)
serialized_products = ActiveModel::ArraySerializer.new(
paginated_products,
each_serializer: Api::Admin::ForOrderCycle::SuppliedProductSerializer,
order_cycle: @order_cycle
)
render json: {
products: serialized_products,
pagination: pagination_data(paginated_products)
}
end
def exchange_params
params.permit(:enterprise_id, :exchange_id, :order_cycle_id, :incoming).
to_h.with_indifferent_access
end
end
end
end

View File

@@ -0,0 +1,20 @@
# frozen_string_literal: true
module Api
module V0
class LogosController < Api::V0::EnterpriseAttachmentController
private
def attachment_name
:logo
end
def enterprise_authorize_action
case action_name.to_sym
when :destroy
:remove_logo
end
end
end
end
end

View File

@@ -0,0 +1,105 @@
# frozen_string_literal: true
module Api
module V0
class OrderCyclesController < Api::V0::BaseController
include EnterprisesHelper
include ApiActionCaching
skip_authorization_check
skip_before_action :authenticate_user, :ensure_api_key, only: [:taxons, :properties]
caches_action :taxons, :properties,
expires_in: CacheService::FILTERS_EXPIRY,
cache_path: proc { |controller| controller.request.url }
def products
return render_no_products unless order_cycle.open?
products = ProductsRenderer.new(
distributor,
order_cycle,
customer,
search_params
).products_json
render plain: products
rescue ProductsRenderer::NoProducts
render_no_products
end
def taxons
taxons = Spree::Taxon.
joins(:products).
where(spree_products: { id: distributed_products }).
select('DISTINCT spree_taxons.*')
render plain: ActiveModel::ArraySerializer.new(
taxons, each_serializer: Api::TaxonSerializer
).to_json
end
def properties
render plain: ActiveModel::ArraySerializer.new(
product_properties | producer_properties, each_serializer: Api::PropertySerializer
).to_json
end
private
def render_no_products
render status: :not_found, json: {}
end
def product_properties
Spree::Property.
joins(:products).
where(spree_products: { id: distributed_products }).
select('DISTINCT spree_properties.*')
end
def producer_properties
producers = Enterprise.
joins(:supplied_products).
where(spree_products: { id: distributed_products })
Spree::Property.
joins(:producer_properties).
where(producer_properties: { producer_id: producers }).
select('DISTINCT spree_properties.*')
end
def search_params
permitted_search_params = params.slice :q, :page, :per_page
if permitted_search_params.key? :q
permitted_search_params[:q].slice!(*permitted_ransack_params)
end
permitted_search_params
end
def permitted_ransack_params
[:name_or_meta_keywords_or_variants_display_as_or_variants_display_name_or_supplier_name_cont,
:properties_id_or_supplier_properties_id_in_any,
:primary_taxon_id_in_any]
end
def distributor
@distributor ||= Enterprise.find_by(id: params[:distributor])
end
def order_cycle
@order_cycle ||= OrderCycle.find_by(id: params[:id])
end
def customer
@current_api_user.andand.customer_of(distributor) || nil
end
def distributed_products
OrderCycleDistributedProducts.new(distributor, order_cycle, customer).products_relation
end
end
end
end

View File

@@ -0,0 +1,72 @@
# frozen_string_literal: true
module Api
module V0
class OrdersController < Api::V0::BaseController
include PaginationData
def show
authorize! :read, order
render json: order, serializer: Api::OrderDetailedSerializer, current_order: order
end
def index
authorize! :admin, Spree::Order
orders = SearchOrders.new(params, current_api_user).orders
render json: {
orders: serialized_orders(orders),
pagination: pagination_data(orders)
}
end
def ship
authorize! :admin, order
if order.ship
render json: order.reload, serializer: Api::Admin::OrderSerializer, status: :ok
else
render json: { error: I18n.t('api.orders.failed_to_update') },
status: :unprocessable_entity
end
end
def capture
authorize! :admin, order
pending_payment = order.pending_payments.first
return payment_capture_failed unless order.payment_required? && pending_payment
if pending_payment.capture!
render json: order.reload, serializer: Api::Admin::OrderSerializer, status: :ok
else
payment_capture_failed
end
rescue Spree::Core::GatewayError => e
error_during_processing(e)
end
private
def payment_capture_failed
render json: { error: I18n.t(:payment_processing_failed) }, status: :unprocessable_entity
end
def serialized_orders(orders)
ActiveModel::ArraySerializer.new(
orders,
each_serializer: Api::Admin::OrderSerializer
)
end
def order
@order ||= Spree::Order.
where(number: params[:id]).
includes(line_items: { variant: [:product, :stock_items, :default_price] }).
first!
end
end
end
end

View File

@@ -0,0 +1,27 @@
# frozen_string_literal: true
module Api
module V0
class ProductImagesController < Api::V0::BaseController
respond_to :json
def update_product_image
@product = Spree::Product.find(params[:product_id])
authorize! :update, @product
if @product.images.first.nil?
@image = Spree::Image.create(
attachment: params[:file],
viewable_id: @product.master.id,
viewable_type: 'Spree::Variant'
)
render json: @image, serializer: ImageSerializer, status: :created
else
@image = @product.images.first
@image.update(attachment: params[:file])
render json: @image, serializer: ImageSerializer, status: :ok
end
end
end
end
end

View File

@@ -0,0 +1,163 @@
# frozen_string_literal: true
require 'open_food_network/permissions'
require 'spree/core/product_duplicator'
module Api
module V0
class ProductsController < Api::V0::BaseController
include PaginationData
respond_to :json
DEFAULT_PER_PAGE = 15
before_action :set_default_available_on, only: :create
skip_authorization_check only: [:show, :bulk_products, :overridable]
def show
@product = find_product(params[:id])
render json: @product, serializer: Api::Admin::ProductSerializer
end
def create
authorize! :create, Spree::Product
@product = Spree::Product.new(product_params)
begin
if @product.save
render json: @product, serializer: Api::Admin::ProductSerializer, status: :created
else
invalid_resource!(@product)
end
rescue ActiveRecord::RecordNotUnique
@product.permalink = nil
retry
end
end
def update
authorize! :update, Spree::Product
@product = find_product(params[:id])
if @product.update(product_params)
render json: @product, serializer: Api::Admin::ProductSerializer, status: :ok
else
invalid_resource!(@product)
end
end
def destroy
authorize! :delete, Spree::Product
@product = find_product(params[:id])
authorize! :delete, @product
@product.destroy
render json: @product, serializer: Api::Admin::ProductSerializer, status: :no_content
end
def bulk_products
product_query = OpenFoodNetwork::Permissions.
new(current_api_user).
editable_products.
merge(product_scope)
if params[:import_date].present?
product_query = product_query.
imported_on(params[:import_date]).
group_by_products_id
end
@products = product_query.
ransack(query_params_with_defaults).
result.
page(params[:page] || 1).
per(params[:per_page] || DEFAULT_PER_PAGE)
render_paged_products @products
end
def overridable
producer_ids = OpenFoodNetwork::Permissions.new(current_api_user).
variant_override_producers.by_name.select('enterprises.id')
@products = paged_products_for_producers producer_ids
render_paged_products @products, ::Api::Admin::ProductSimpleSerializer
end
# POST /api/products/:product_id/clone
#
def clone
authorize! :create, Spree::Product
original_product = find_product(params[:product_id])
authorize! :update, original_product
@product = original_product.duplicate
render json: @product, serializer: Api::Admin::ProductSerializer, status: :created
end
private
def find_product(id)
product_scope.find_by!(permalink: id.to_s)
rescue ActiveRecord::RecordNotFound
product_scope.find(id)
end
def product_scope
if current_api_user.has_spree_role?("admin") || current_api_user.enterprises.present?
scope = Spree::Product
if params[:show_deleted]
scope = scope.with_deleted
end
else
scope = Spree::Product.active
end
scope.includes(product_query_includes)
end
def product_query_includes
[
master: [:images],
variants: [:default_price, :stock_locations, :stock_items, :variant_overrides,
{ option_values: :option_type }]
]
end
def paged_products_for_producers(producer_ids)
Spree::Product.where(nil).
merge(product_scope).
includes(variants: [:product, :default_price, :stock_items]).
where(supplier_id: producer_ids).
by_producer.by_name.
ransack(params[:q]).result.
page(params[:page]).per(params[:per_page])
end
def render_paged_products(products, product_serializer = ::Api::Admin::ProductSerializer)
serialized_products = ActiveModel::ArraySerializer.new(
products,
each_serializer: product_serializer
)
render json: {
products: serialized_products,
pagination: pagination_data(products)
}
end
def query_params_with_defaults
(params[:q] || {}).reverse_merge(s: 'created_at desc')
end
def product_params
@product_params ||=
params.permit(product: PermittedAttributes::Product.attributes)[:product].to_h
end
def set_default_available_on
product_params[:available_on] ||= Time.zone.now
end
end
end
end

View File

@@ -0,0 +1,20 @@
# frozen_string_literal: true
module Api
module V0
class PromoImagesController < Api::V0::EnterpriseAttachmentController
private
def attachment_name
:promo_image
end
def enterprise_authorize_action
case action_name.to_sym
when :destroy
:remove_promo_image
end
end
end
end
end

Some files were not shown because too many files have changed in this diff Show More