accept changesfrom upstream and resolve merge conflict ~ automatic go invoices

This commit is contained in:
viehlieb 2022-02-03 12:15:21 +01:00
commit bb3e049630
27 changed files with 263 additions and 151 deletions

1
.gitignore vendored
View file

@ -7,6 +7,7 @@ tmp
public/assets public/assets
public/system public/system
public/uploads public/uploads
storage
vendor/bundle vendor/bundle
# no configuration # no configuration

View file

@ -1 +1 @@
2.6.6 2.6.9

View file

@ -39,6 +39,7 @@ RUN export DATABASE_URL=mysql2://localhost/temp?encoding=utf8 && \
mariadb -e "CREATE DATABASE temp" && \ mariadb -e "CREATE DATABASE temp" && \
cp config/app_config.yml.SAMPLE config/app_config.yml && \ cp config/app_config.yml.SAMPLE config/app_config.yml && \
cp config/database.yml.MySQL_SAMPLE config/database.yml && \ cp config/database.yml.MySQL_SAMPLE config/database.yml && \
cp config/storage.yml.SAMPLE config/storage.yml && \
bundle exec rake db:setup assets:precompile && \ bundle exec rake db:setup assets:precompile && \
rm -Rf tmp/* && \ rm -Rf tmp/* && \
/etc/init.d/mysql stop && \ /etc/init.d/mysql stop && \
@ -56,6 +57,8 @@ USER nobody
EXPOSE 3000 EXPOSE 3000
VOLUME /usr/src/app/storage
# cleanup, and by default start web process from Procfile # cleanup, and by default start web process from Procfile
ENTRYPOINT ["./docker-entrypoint.sh"] ENTRYPOINT ["./docker-entrypoint.sh"]
CMD ["./proc-start", "web"] CMD ["./proc-start", "web"]

View file

@ -28,7 +28,7 @@ Deploying
--------- ---------
Setup foodsoft to [run in production](doc/SETUP_PRODUCTION.md), or join an existing Setup foodsoft to [run in production](doc/SETUP_PRODUCTION.md), or join an existing
[hosting platform](https://foodcoops.github.io/foodsoft-hosting/). [hosting platform](https://foodcoops.net/foodsoft-hosting/).
License License
@ -52,7 +52,7 @@ files are marked as public domain in the file header.
If you have any remaining questions, please If you have any remaining questions, please
[open an issue](https://github.com/foodcoops/foodsoft/issues/new) or contact [open an issue](https://github.com/foodcoops/foodsoft/issues/new) or contact
the [mailing list](http://foodsoft.51229.x6.nabble.com/foodsoft-discuss-f5.html). the [mailing list](http://foodsoft.274.s1.nabble.com/foodsoft-discuss-f5.html).
Please see [LICENSE](LICENSE.md) for the full and authoritative text. Some Please see [LICENSE](LICENSE.md) for the full and authoritative text. Some
bundled third-party components have [other licenses](vendor/README.md). bundled third-party components have [other licenses](vendor/README.md).

View file

@ -115,7 +115,7 @@ class Mailer < ActionMailer::Base
@user = user @user = user
@feedback = feedback @feedback = feedback
mail to: FoodsoftConfig[:notification][:error_recipients], mail to: feedback_recipients,
from: user, from: user,
subject: I18n.t('mailer.feedback.subject') subject: I18n.t('mailer.feedback.subject')
end end
@ -196,4 +196,9 @@ class Mailer < ActionMailer::Base
address.display_name = name address.display_name = name
address.format address.format
end end
# use the (new) feedback_recipients option, but fallback to error_recipients for backwards compatibility
def feedback_recipients
FoodsoftConfig[:notification][:feedback_recipients] || FoodsoftConfig[:notification][:error_recipients]
end
end end

View file

@ -62,6 +62,10 @@ class Article < ApplicationRecord
validates_presence_of :name, :unit, :price, :tax, :deposit, :unit_quantity, :supplier_id, :article_category validates_presence_of :name, :unit, :price, :tax, :deposit, :unit_quantity, :supplier_id, :article_category
validates_length_of :name, :in => 4..60 validates_length_of :name, :in => 4..60
validates_length_of :unit, :in => 1..15 validates_length_of :unit, :in => 1..15
validates_length_of :note, :maximum => 255
validates_length_of :origin, :maximum => 255
validates_length_of :manufacturer, :maximum => 255
validates_length_of :order_number, :maximum => 255
validates_numericality_of :price, :greater_than_or_equal_to => 0 validates_numericality_of :price, :greater_than_or_equal_to => 0
validates_numericality_of :unit_quantity, :greater_than => 0 validates_numericality_of :unit_quantity, :greater_than => 0
validates_numericality_of :deposit, :tax validates_numericality_of :deposit, :tax

View file

@ -118,9 +118,9 @@ default: &defaults
# label: Birthday # label: Birthday
# as: date_picker # as: date_picker
# Uncomment to add tracking code for web statistics, e.g. for Piwik. (Added to bottom of page) # Uncomment to add tracking code for web statistics, e.g. for Matomo. (Added to bottom of page)
#webstats_tracking_code: | #webstats_tracking_code: |
# <!-- Piwik --> # <!-- Matomo -->
# ...... # ......
# email address to be used as sender # email address to be used as sender
@ -137,6 +137,8 @@ default: &defaults
notification: notification:
error_recipients: error_recipients:
- admin@foodcoop.test - admin@foodcoop.test
feedback_recipients:
- support@foodcoop.test
sender_address: "\"Foodsoft Error\" <foodsoft@foodcoop.test>" sender_address: "\"Foodsoft Error\" <foodsoft@foodcoop.test>"
email_prefix: "[Foodsoft]" email_prefix: "[Foodsoft]"

View file

@ -31,6 +31,9 @@ Rails.application.configure do
config.cache_store = :null_store config.cache_store = :null_store
end end
# Store uploaded files on the local file system (see config/storage.yml for options)
config.active_storage.service = :local
# Don't care if the mailer can't send. # Don't care if the mailer can't send.
config.action_mailer.raise_delivery_errors = false config.action_mailer.raise_delivery_errors = false

View file

@ -40,6 +40,9 @@ Rails.application.configure do
# config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
# Store uploaded files on the local file system (see config/storage.yml for options)
config.active_storage.service = :local
# Mount Action Cable outside main process or domain # Mount Action Cable outside main process or domain
# config.action_cable.mount_path = nil # config.action_cable.mount_path = nil
# config.action_cable.url = 'wss://example.com/cable' # config.action_cable.url = 'wss://example.com/cable'

View file

@ -31,6 +31,10 @@ Rails.application.configure do
# Disable request forgery protection in test environment. # Disable request forgery protection in test environment.
config.action_controller.allow_forgery_protection = false config.action_controller.allow_forgery_protection = false
# Store uploaded files on the local file system in a temporary directory
config.active_storage.service = :test
config.action_mailer.perform_caching = false config.action_mailer.perform_caching = false
# Tell Action Mailer not to deliver emails to the real world. # Tell Action Mailer not to deliver emails to the real world.

View file

@ -0,0 +1,15 @@
require 'active_storage/service/disk_service'
module FoodsoftActiveStorageDiskService
def self.included(base) # :nodoc:
base.class_eval do
def path_for(key)
File.join root, FoodsoftConfig.scope, folder_for(key), key
end
end
end
end
ActiveSupport.on_load(:after_initialize) do
ActiveStorage::Service::DiskService.include FoodsoftActiveStorageDiskService
end

34
config/storage.yml.SAMPLE Normal file
View file

@ -0,0 +1,34 @@
test:
service: Disk
root: <%= Rails.root.join("tmp/storage") %>
local:
service: Disk
root: <%= Rails.root.join("storage") %>
# Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
# amazon:
# service: S3
# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
# region: us-east-1
# bucket: your_own_bucket
# Remember not to checkin your GCS keyfile to a repository
# google:
# service: GCS
# project: your_project
# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %>
# bucket: your_own_bucket
# Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)
# microsoft:
# service: AzureStorage
# storage_account_name: your_account_name
# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %>
# container: your_container_name
# mirror:
# service: Mirror
# primary: local
# mirrors: [ amazon, google, microsoft ]

View file

@ -0,0 +1,27 @@
# This migration comes from active_storage (originally 20170806125915)
class CreateActiveStorageTables < ActiveRecord::Migration[4.2][5.2]
def change
create_table :active_storage_blobs do |t|
t.string :key, null: false
t.string :filename, null: false
t.string :content_type
t.text :metadata
t.bigint :byte_size, null: false
t.string :checksum, null: false
t.datetime :created_at, null: false
t.index [:key], unique: true
end
create_table :active_storage_attachments do |t|
t.string :name, null: false
t.references :record, null: false, polymorphic: true, index: false
t.references :blob, null: false
t.datetime :created_at, null: false
t.index [:record_type, :record_id, :name, :blob_id], name: "index_active_storage_attachments_uniqueness", unique: true
t.foreign_key :active_storage_blobs, column: :blob_id
end
end
end

View file

@ -10,15 +10,36 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2021_12_08_142719) do ActiveRecord::Schema.define(version: 2021_02_05_090257) do
create_table "article_categories", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "active_storage_attachments", id: :integer, force: :cascade do |t|
t.string "name", null: false
t.string "record_type", null: false
t.bigint "record_id", null: false
t.bigint "blob_id", null: false
t.datetime "created_at", null: false
t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
end
create_table "active_storage_blobs", id: :integer, force: :cascade do |t|
t.string "key", null: false
t.string "filename", null: false
t.string "content_type"
t.text "metadata"
t.bigint "byte_size", null: false
t.string "checksum", null: false
t.datetime "created_at", null: false
t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
end
create_table "article_categories", id: :integer, force: :cascade do |t|
t.string "name", default: "", null: false t.string "name", default: "", null: false
t.string "description" t.string "description"
t.index ["name"], name: "index_article_categories_on_name", unique: true t.index ["name"], name: "index_article_categories_on_name", unique: true
end end
create_table "article_prices", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "article_prices", id: :integer, force: :cascade do |t|
t.integer "article_id", null: false t.integer "article_id", null: false
t.decimal "price", precision: 8, scale: 2, default: "0.0", null: false t.decimal "price", precision: 8, scale: 2, default: "0.0", null: false
t.decimal "tax", precision: 8, scale: 2, default: "0.0", null: false t.decimal "tax", precision: 8, scale: 2, default: "0.0", null: false
@ -28,7 +49,7 @@ ActiveRecord::Schema.define(version: 2021_12_08_142719) do
t.index ["article_id"], name: "index_article_prices_on_article_id" t.index ["article_id"], name: "index_article_prices_on_article_id"
end end
create_table "articles", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "articles", id: :integer, force: :cascade do |t|
t.string "name", default: "", null: false t.string "name", default: "", null: false
t.integer "supplier_id", default: 0, null: false t.integer "supplier_id", default: 0, null: false
t.integer "article_category_id", default: 0, null: false t.integer "article_category_id", default: 0, null: false
@ -54,14 +75,14 @@ ActiveRecord::Schema.define(version: 2021_12_08_142719) do
t.index ["type"], name: "index_articles_on_type" t.index ["type"], name: "index_articles_on_type"
end end
create_table "assignments", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "assignments", id: :integer, force: :cascade do |t|
t.integer "user_id", default: 0, null: false t.integer "user_id", default: 0, null: false
t.integer "task_id", default: 0, null: false t.integer "task_id", default: 0, null: false
t.boolean "accepted", default: false t.boolean "accepted", default: false
t.index ["user_id", "task_id"], name: "index_assignments_on_user_id_and_task_id", unique: true t.index ["user_id", "task_id"], name: "index_assignments_on_user_id_and_task_id", unique: true
end end
create_table "bank_accounts", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "bank_accounts", id: :integer, force: :cascade do |t|
t.string "name", null: false t.string "name", null: false
t.string "iban" t.string "iban"
t.string "description" t.string "description"
@ -70,7 +91,7 @@ ActiveRecord::Schema.define(version: 2021_12_08_142719) do
t.string "import_continuation_point" t.string "import_continuation_point"
end end
create_table "bank_transactions", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "bank_transactions", id: :integer, force: :cascade do |t|
t.integer "bank_account_id", null: false t.integer "bank_account_id", null: false
t.string "external_id" t.string "external_id"
t.date "date" t.date "date"
@ -84,7 +105,7 @@ ActiveRecord::Schema.define(version: 2021_12_08_142719) do
t.index ["financial_link_id"], name: "index_bank_transactions_on_financial_link_id" t.index ["financial_link_id"], name: "index_bank_transactions_on_financial_link_id"
end end
create_table "documents", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "documents", id: :integer, force: :cascade do |t|
t.string "name" t.string "name"
t.string "mime" t.string "mime"
t.binary "data", limit: 4294967295 t.binary "data", limit: 4294967295
@ -95,16 +116,16 @@ ActiveRecord::Schema.define(version: 2021_12_08_142719) do
t.index ["parent_id"], name: "index_documents_on_parent_id" t.index ["parent_id"], name: "index_documents_on_parent_id"
end end
create_table "financial_links", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "financial_links", id: :integer, force: :cascade do |t|
t.text "note" t.text "note"
end end
create_table "financial_transaction_classes", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "financial_transaction_classes", id: :integer, force: :cascade do |t|
t.string "name", null: false t.string "name", null: false
t.boolean "ignore_for_account_balance", default: false, null: false t.boolean "ignore_for_account_balance", default: false, null: false
end end
create_table "financial_transaction_types", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "financial_transaction_types", id: :integer, force: :cascade do |t|
t.string "name", null: false t.string "name", null: false
t.integer "financial_transaction_class_id", null: false t.integer "financial_transaction_class_id", null: false
t.string "name_short" t.string "name_short"
@ -112,7 +133,7 @@ ActiveRecord::Schema.define(version: 2021_12_08_142719) do
t.index ["name_short"], name: "index_financial_transaction_types_on_name_short" t.index ["name_short"], name: "index_financial_transaction_types_on_name_short"
end end
create_table "financial_transactions", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "financial_transactions", id: :integer, force: :cascade do |t|
t.integer "ordergroup_id" t.integer "ordergroup_id"
t.decimal "amount", precision: 8, scale: 2, default: "0.0", null: false t.decimal "amount", precision: 8, scale: 2, default: "0.0", null: false
t.text "note", null: false t.text "note", null: false
@ -126,7 +147,7 @@ ActiveRecord::Schema.define(version: 2021_12_08_142719) do
t.index ["reverts_id"], name: "index_financial_transactions_on_reverts_id", unique: true t.index ["reverts_id"], name: "index_financial_transactions_on_reverts_id", unique: true
end end
create_table "group_order_article_quantities", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "group_order_article_quantities", id: :integer, force: :cascade do |t|
t.integer "group_order_article_id", default: 0, null: false t.integer "group_order_article_id", default: 0, null: false
t.integer "quantity", default: 0 t.integer "quantity", default: 0
t.integer "tolerance", default: 0 t.integer "tolerance", default: 0
@ -134,7 +155,7 @@ ActiveRecord::Schema.define(version: 2021_12_08_142719) do
t.index ["group_order_article_id"], name: "index_group_order_article_quantities_on_group_order_article_id" t.index ["group_order_article_id"], name: "index_group_order_article_quantities_on_group_order_article_id"
end end
create_table "group_order_articles", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "group_order_articles", id: :integer, force: :cascade do |t|
t.integer "group_order_id", default: 0, null: false t.integer "group_order_id", default: 0, null: false
t.integer "order_article_id", default: 0, null: false t.integer "order_article_id", default: 0, null: false
t.integer "quantity", default: 0, null: false t.integer "quantity", default: 0, null: false
@ -147,17 +168,7 @@ ActiveRecord::Schema.define(version: 2021_12_08_142719) do
t.index ["order_article_id"], name: "index_group_order_articles_on_order_article_id" t.index ["order_article_id"], name: "index_group_order_articles_on_order_article_id"
end end
create_table "group_order_invoices", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "group_orders", id: :integer, force: :cascade do |t|
t.integer "group_order_id"
t.bigint "invoice_number"
t.date "invoice_date"
t.string "payment_method"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["group_order_id"], name: "index_group_order_invoices_on_group_order_id", unique: true
end
create_table "group_orders", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t|
t.integer "ordergroup_id" t.integer "ordergroup_id"
t.integer "order_id", default: 0, null: false t.integer "order_id", default: 0, null: false
t.decimal "price", precision: 8, scale: 2, default: "0.0", null: false t.decimal "price", precision: 8, scale: 2, default: "0.0", null: false
@ -170,7 +181,7 @@ ActiveRecord::Schema.define(version: 2021_12_08_142719) do
t.index ["ordergroup_id"], name: "index_group_orders_on_ordergroup_id" t.index ["ordergroup_id"], name: "index_group_orders_on_ordergroup_id"
end end
create_table "groups", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "groups", id: :integer, force: :cascade do |t|
t.string "type", default: "", null: false t.string "type", default: "", null: false
t.string "name", default: "", null: false t.string "name", default: "", null: false
t.string "description" t.string "description"
@ -195,7 +206,7 @@ ActiveRecord::Schema.define(version: 2021_12_08_142719) do
t.index ["name"], name: "index_groups_on_name", unique: true t.index ["name"], name: "index_groups_on_name", unique: true
end end
create_table "invites", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "invites", id: :integer, force: :cascade do |t|
t.string "token", default: "", null: false t.string "token", default: "", null: false
t.datetime "expires_at", null: false t.datetime "expires_at", null: false
t.integer "group_id", default: 0, null: false t.integer "group_id", default: 0, null: false
@ -204,7 +215,7 @@ ActiveRecord::Schema.define(version: 2021_12_08_142719) do
t.index ["token"], name: "index_invites_on_token" t.index ["token"], name: "index_invites_on_token"
end end
create_table "invoices", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "invoices", id: :integer, force: :cascade do |t|
t.integer "supplier_id" t.integer "supplier_id"
t.string "number" t.string "number"
t.date "date" t.date "date"
@ -222,7 +233,7 @@ ActiveRecord::Schema.define(version: 2021_12_08_142719) do
t.index ["supplier_id"], name: "index_invoices_on_supplier_id" t.index ["supplier_id"], name: "index_invoices_on_supplier_id"
end end
create_table "links", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "links", id: :integer, force: :cascade do |t|
t.string "name", null: false t.string "name", null: false
t.string "url", null: false t.string "url", null: false
t.integer "workgroup_id" t.integer "workgroup_id"
@ -230,7 +241,7 @@ ActiveRecord::Schema.define(version: 2021_12_08_142719) do
t.string "authorization" t.string "authorization"
end end
create_table "mail_delivery_status", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "mail_delivery_status", id: :integer, force: :cascade do |t|
t.datetime "created_at" t.datetime "created_at"
t.string "email", null: false t.string "email", null: false
t.string "message", null: false t.string "message", null: false
@ -239,13 +250,13 @@ ActiveRecord::Schema.define(version: 2021_12_08_142719) do
t.index ["email"], name: "index_mail_delivery_status_on_email" t.index ["email"], name: "index_mail_delivery_status_on_email"
end end
create_table "memberships", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "memberships", id: :integer, force: :cascade do |t|
t.integer "group_id", default: 0, null: false t.integer "group_id", default: 0, null: false
t.integer "user_id", default: 0, null: false t.integer "user_id", default: 0, null: false
t.index ["user_id", "group_id"], name: "index_memberships_on_user_id_and_group_id", unique: true t.index ["user_id", "group_id"], name: "index_memberships_on_user_id_and_group_id", unique: true
end end
create_table "message_recipients", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "message_recipients", id: :integer, force: :cascade do |t|
t.integer "message_id", null: false t.integer "message_id", null: false
t.integer "user_id", null: false t.integer "user_id", null: false
t.integer "email_state", default: 0, null: false t.integer "email_state", default: 0, null: false
@ -254,7 +265,7 @@ ActiveRecord::Schema.define(version: 2021_12_08_142719) do
t.index ["user_id", "read_at"], name: "index_message_recipients_on_user_id_and_read_at" t.index ["user_id", "read_at"], name: "index_message_recipients_on_user_id_and_read_at"
end end
create_table "messages", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "messages", id: :integer, force: :cascade do |t|
t.integer "sender_id" t.integer "sender_id"
t.string "subject", null: false t.string "subject", null: false
t.text "body" t.text "body"
@ -266,7 +277,7 @@ ActiveRecord::Schema.define(version: 2021_12_08_142719) do
t.binary "received_email", limit: 16777215 t.binary "received_email", limit: 16777215
end end
create_table "oauth_access_grants", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "oauth_access_grants", id: :integer, force: :cascade do |t|
t.integer "resource_owner_id", null: false t.integer "resource_owner_id", null: false
t.integer "application_id", null: false t.integer "application_id", null: false
t.string "token", null: false t.string "token", null: false
@ -278,7 +289,7 @@ ActiveRecord::Schema.define(version: 2021_12_08_142719) do
t.index ["token"], name: "index_oauth_access_grants_on_token", unique: true t.index ["token"], name: "index_oauth_access_grants_on_token", unique: true
end end
create_table "oauth_access_tokens", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "oauth_access_tokens", id: :integer, force: :cascade do |t|
t.integer "resource_owner_id" t.integer "resource_owner_id"
t.integer "application_id" t.integer "application_id"
t.string "token", null: false t.string "token", null: false
@ -292,7 +303,7 @@ ActiveRecord::Schema.define(version: 2021_12_08_142719) do
t.index ["token"], name: "index_oauth_access_tokens_on_token", unique: true t.index ["token"], name: "index_oauth_access_tokens_on_token", unique: true
end end
create_table "oauth_applications", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "oauth_applications", id: :integer, force: :cascade do |t|
t.string "name", null: false t.string "name", null: false
t.string "uid", null: false t.string "uid", null: false
t.string "secret", null: false t.string "secret", null: false
@ -304,7 +315,7 @@ ActiveRecord::Schema.define(version: 2021_12_08_142719) do
t.index ["uid"], name: "index_oauth_applications_on_uid", unique: true t.index ["uid"], name: "index_oauth_applications_on_uid", unique: true
end end
create_table "order_articles", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "order_articles", id: :integer, force: :cascade do |t|
t.integer "order_id", default: 0, null: false t.integer "order_id", default: 0, null: false
t.integer "article_id", default: 0, null: false t.integer "article_id", default: 0, null: false
t.integer "quantity", default: 0, null: false t.integer "quantity", default: 0, null: false
@ -318,7 +329,7 @@ ActiveRecord::Schema.define(version: 2021_12_08_142719) do
t.index ["order_id"], name: "index_order_articles_on_order_id" t.index ["order_id"], name: "index_order_articles_on_order_id"
end end
create_table "order_comments", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "order_comments", id: :integer, force: :cascade do |t|
t.integer "order_id" t.integer "order_id"
t.integer "user_id" t.integer "user_id"
t.text "text" t.text "text"
@ -326,7 +337,7 @@ ActiveRecord::Schema.define(version: 2021_12_08_142719) do
t.index ["order_id"], name: "index_order_comments_on_order_id" t.index ["order_id"], name: "index_order_comments_on_order_id"
end end
create_table "orders", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "orders", id: :integer, force: :cascade do |t|
t.integer "supplier_id" t.integer "supplier_id"
t.text "note" t.text "note"
t.datetime "starts" t.datetime "starts"
@ -345,7 +356,7 @@ ActiveRecord::Schema.define(version: 2021_12_08_142719) do
t.index ["state"], name: "index_orders_on_state" t.index ["state"], name: "index_orders_on_state"
end end
create_table "page_versions", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "page_versions", id: :integer, force: :cascade do |t|
t.integer "page_id" t.integer "page_id"
t.integer "lock_version" t.integer "lock_version"
t.text "body" t.text "body"
@ -356,7 +367,7 @@ ActiveRecord::Schema.define(version: 2021_12_08_142719) do
t.index ["page_id"], name: "index_page_versions_on_page_id" t.index ["page_id"], name: "index_page_versions_on_page_id"
end end
create_table "pages", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "pages", id: :integer, force: :cascade do |t|
t.string "title" t.string "title"
t.text "body" t.text "body"
t.string "permalink" t.string "permalink"
@ -370,20 +381,20 @@ ActiveRecord::Schema.define(version: 2021_12_08_142719) do
t.index ["title"], name: "index_pages_on_title" t.index ["title"], name: "index_pages_on_title"
end end
create_table "periodic_task_groups", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "periodic_task_groups", id: :integer, force: :cascade do |t|
t.date "next_task_date" t.date "next_task_date"
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
end end
create_table "poll_choices", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "poll_choices", id: :integer, force: :cascade do |t|
t.integer "poll_vote_id", null: false t.integer "poll_vote_id", null: false
t.integer "choice", null: false t.integer "choice", null: false
t.integer "value", null: false t.integer "value", null: false
t.index ["poll_vote_id", "choice"], name: "index_poll_choices_on_poll_vote_id_and_choice", unique: true t.index ["poll_vote_id", "choice"], name: "index_poll_choices_on_poll_vote_id_and_choice", unique: true
end end
create_table "poll_votes", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "poll_votes", id: :integer, force: :cascade do |t|
t.integer "poll_id", null: false t.integer "poll_id", null: false
t.integer "user_id", null: false t.integer "user_id", null: false
t.integer "ordergroup_id" t.integer "ordergroup_id"
@ -393,7 +404,7 @@ ActiveRecord::Schema.define(version: 2021_12_08_142719) do
t.index ["poll_id", "user_id", "ordergroup_id"], name: "index_poll_votes_on_poll_id_and_user_id_and_ordergroup_id", unique: true t.index ["poll_id", "user_id", "ordergroup_id"], name: "index_poll_votes_on_poll_id_and_user_id_and_ordergroup_id", unique: true
end end
create_table "polls", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "polls", id: :integer, force: :cascade do |t|
t.integer "created_by_user_id", null: false t.integer "created_by_user_id", null: false
t.string "name", null: false t.string "name", null: false
t.text "description" t.text "description"
@ -413,7 +424,7 @@ ActiveRecord::Schema.define(version: 2021_12_08_142719) do
t.index ["final_choice"], name: "index_polls_on_final_choice" t.index ["final_choice"], name: "index_polls_on_final_choice"
end end
create_table "printer_job_updates", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "printer_job_updates", id: :integer, force: :cascade do |t|
t.integer "printer_job_id", null: false t.integer "printer_job_id", null: false
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.string "state", null: false t.string "state", null: false
@ -421,7 +432,7 @@ ActiveRecord::Schema.define(version: 2021_12_08_142719) do
t.index ["printer_job_id", "created_at"], name: "index_printer_job_updates_on_printer_job_id_and_created_at" t.index ["printer_job_id", "created_at"], name: "index_printer_job_updates_on_printer_job_id_and_created_at"
end end
create_table "printer_jobs", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "printer_jobs", id: :integer, force: :cascade do |t|
t.integer "order_id" t.integer "order_id"
t.string "document", null: false t.string "document", null: false
t.integer "created_by_user_id", null: false t.integer "created_by_user_id", null: false
@ -430,7 +441,7 @@ ActiveRecord::Schema.define(version: 2021_12_08_142719) do
t.index ["finished_at"], name: "index_printer_jobs_on_finished_at" t.index ["finished_at"], name: "index_printer_jobs_on_finished_at"
end end
create_table "settings", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "settings", id: :integer, force: :cascade do |t|
t.string "var", null: false t.string "var", null: false
t.text "value" t.text "value"
t.integer "thing_id" t.integer "thing_id"
@ -440,7 +451,7 @@ ActiveRecord::Schema.define(version: 2021_12_08_142719) do
t.index ["thing_type", "thing_id", "var"], name: "index_settings_on_thing_type_and_thing_id_and_var", unique: true t.index ["thing_type", "thing_id", "var"], name: "index_settings_on_thing_type_and_thing_id_and_var", unique: true
end end
create_table "stock_changes", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "stock_changes", id: :integer, force: :cascade do |t|
t.integer "stock_event_id" t.integer "stock_event_id"
t.integer "order_id" t.integer "order_id"
t.integer "stock_article_id" t.integer "stock_article_id"
@ -450,7 +461,7 @@ ActiveRecord::Schema.define(version: 2021_12_08_142719) do
t.index ["stock_event_id"], name: "index_stock_changes_on_stock_event_id" t.index ["stock_event_id"], name: "index_stock_changes_on_stock_event_id"
end end
create_table "stock_events", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "stock_events", id: :integer, force: :cascade do |t|
t.integer "supplier_id" t.integer "supplier_id"
t.date "date" t.date "date"
t.datetime "created_at" t.datetime "created_at"
@ -460,13 +471,13 @@ ActiveRecord::Schema.define(version: 2021_12_08_142719) do
t.index ["supplier_id"], name: "index_stock_events_on_supplier_id" t.index ["supplier_id"], name: "index_stock_events_on_supplier_id"
end end
create_table "supplier_categories", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "supplier_categories", id: :integer, force: :cascade do |t|
t.string "name", null: false t.string "name", null: false
t.string "description" t.string "description"
t.integer "financial_transaction_class_id" t.integer "financial_transaction_class_id"
end end
create_table "suppliers", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "suppliers", id: :integer, force: :cascade do |t|
t.string "name", default: "", null: false t.string "name", default: "", null: false
t.string "address", default: "", null: false t.string "address", default: "", null: false
t.string "phone", default: "", null: false t.string "phone", default: "", null: false
@ -488,7 +499,7 @@ ActiveRecord::Schema.define(version: 2021_12_08_142719) do
t.index ["name"], name: "index_suppliers_on_name", unique: true t.index ["name"], name: "index_suppliers_on_name", unique: true
end end
create_table "tasks", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "tasks", id: :integer, force: :cascade do |t|
t.string "name", default: "", null: false t.string "name", default: "", null: false
t.text "description" t.text "description"
t.date "due_date" t.date "due_date"
@ -505,7 +516,7 @@ ActiveRecord::Schema.define(version: 2021_12_08_142719) do
t.index ["workgroup_id"], name: "index_tasks_on_workgroup_id" t.index ["workgroup_id"], name: "index_tasks_on_workgroup_id"
end end
create_table "users", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| create_table "users", id: :integer, force: :cascade do |t|
t.string "nick" t.string "nick"
t.string "password_hash", default: "", null: false t.string "password_hash", default: "", null: false
t.string "password_salt", default: "", null: false t.string "password_salt", default: "", null: false

View file

@ -11,132 +11,121 @@ If instead you just want to run Foodsoft without changing its code, please refer
[deployment](https://github.com/foodcoops/foodsoft/wiki/Deployment-notes). [deployment](https://github.com/foodcoops/foodsoft/wiki/Deployment-notes).
**System requirements**: **System requirements**:
[RVM](https://rvm.io/rvm/install), [rbenv](https://github.com/rbenv/rbenv),
[Ruby 2+](https://www.ruby-lang.org/en/downloads/), [Ruby 2.6+](https://www.ruby-lang.org/en/downloads/),
[Bundler](http://bundler.io/), [Bundler](http://bundler.io/),
[MySQL](http://mysql.com/)/[PostgreSQL](http://www.postgresql.org/)/[SQLite](http://sqlite.org/). [MySQL](http://mysql.com/) / [SQLite](http://sqlite.org/),
[Redis](http://redis.io/) (optional).
**Optional**:
[Redis](http://redis.io/).
### Getting started ### Getting started
0. Clone the repository from GitHub: 1. Clone the repository from GitHub:
git clone https://github.com/foodcoops/foodsoft.git git clone https://github.com/foodcoops/foodsoft.git
This brings up the bleeding-edge development version, which might contain some This brings up the bleeding-edge development version, which might contain some unfinished parts.
unfinished parts. If you want to be safe, choose the last release: If you want to be safe, choose the last release:
`git checkout $(git tag -l | grep ^v | sort -rn | head -n1)`
*Note:* When developing on Windows you might run into issues with shell scripts git checkout $(git tag -l | grep ^v | sort -rn | head -n1)
because of Git auto-crlf. Have a look how to avoid that in the
[Docker Development Setup](./SETUP_DEVELOPMENT_DOCKER.md#prerequisites-windows-only)
instructions.
1. Install RVM and Ruby 2.6+ (if you have not done so before): *Note:* When developing on Windows you might run into issues with shell scripts because of Git auto-crlf.
Have a look how to avoid that in the [Docker Development Setup](./SETUP_DEVELOPMENT_DOCKER.md#prerequisites-windows-only)
instructions.
\curl -L https://get.rvm.io | bash 1. Install and setup rbenv and Bundler. For Debian/Ubuntu:
source ~/.rvm/scripts/rvm
rvm install 2.6
We try to keep Foodsoft compatible with Ruby 2.6 as well as any later versions, sudo apt install rbenv
so if you use this and don't want to use RVM, that might actually work.
For other distributions have a look at the rbenv [documentation](https://github.com/rbenv/rbenv).
2. Install system dependencies. Add the following line to your `.bashrc`:
For Debian/Ubuntu, that's eval "$(rbenv init -)"
[libv8-dev](https://packages.debian.org/stable/libv8-dev)
[libmysqlclient-dev](https://packages.debian.org/stable/libmysqlclient-dev)
[libxml2-dev](https://packages.debian.org/stable/libxml2-dev)
[libxslt1-dev](https://packages.debian.org/stable/libxslt1-dev)
[libffi-dev](https://packages.debian.org/stable/libffi-dev)
[libreadline-dev](https://packages.debian.org/stable/libreadline-dev)
[libmagic-dev](https://packages.debian.org/stable/libmagic-dev):
# Debian/Ubuntu Install [ruby-build](https://github.com/rbenv/ruby-build):
sudo apt-get install libv8-dev libmysqlclient-dev libxml2-dev libxslt1-dev libffi-dev libreadline-dev libmagic-dev
For CentOS/Redhat you need mkdir -p "$(rbenv root)"/plugins
[v8](https://apps.fedoraproject.org/packages/v8) git clone https://github.com/rbenv/ruby-build.git "$(rbenv root)"/plugins/ruby-build
[community-mysql-devel](https://apps.fedoraproject.org/packages/community-mysql-devel)
[libxml2-devel](https://apps.fedoraproject.org/packages/libxml2-devel) Change to the Foodsoft directory and install the [recommended](https://github.com/foodcoops/foodsoft/blob/master/.ruby-version)
[libxslt-devel](https://apps.fedoraproject.org/packages/libxslt-devel) Ruby version:
[libffi-devel](https://apps.fedoraproject.org/packages/libffi-devel)
[readline-devel](https://apps.fedoraproject.org/packages/readline-devel) rbenv install "$(cat .ruby-version)"
[file-devel](https://apps.fedoraproject.org/packages/file-devel):
Now you can install [Bundler](https://bundler.io/):
rbenv exec gem install bundler
1. Install system dependencies.
For Debian/Ubuntu, that's:
sudo apt install libv8-dev default-libmysqlclient-dev libxml2-dev libxslt1-dev libffi-dev libreadline-dev libmagic-dev
For CentOS/Redhat you need:
# CentOS/Redhat
sudo yum install v8 community-mysql-devel libxml2-devel libxslt-devel libffi-devel readline-devel file-devel sudo yum install v8 community-mysql-devel libxml2-devel libxslt-devel libffi-devel readline-devel file-devel
3. Install Ruby dependencies: 1. Install Ruby dependencies:
bundle install rbenv exec bundle install
4. Setup your development environment: 1. Setup your development environment:
rake foodsoft:setup_development rbenv exec rails foodsoft:setup_development
This will interactively prompt with several questions relating to your This will interactively prompt with several questions relating to your
required environment. required environment.
**Important**: After selecting your database type, `rake` will create the file `config/database.yml`, **Important**: After selecting your database type, `rails` will create the file `config/database.yml`,
which then then be edited with working `username` and `password` credentials for the database. These fields which then then be edited with working `username` and `password` credentials for the database. These fields
must be added for *development* AND (temporary) *test* databases. Then continue with confirmation in rake dialogue. must be added for *development* AND (temporary) *test* databases. Then continue with confirmation in rails dialogue.
5. Start rails by running: 1. Start rails by running:
bundle exec rails s rbenv exec rails s
6. Open your favorite browser and open the web application at: 1. Open your favorite browser and open the web browser at:
http://localhost:3000/ http://localhost:3000/
You might want to watch a You might want to watch a [kitten video](https://www.youtube.com/watch?v=9Iq5yCoHp4o) while it's loading.
[kitten video](https://www.youtube.com/watch?v=9Iq5yCoHp4o)
while it's loading.
7. Login using the default credentials: `admin/secret` 1. Login using the default credentials: `admin/secret`
8. Change the admin password, just in case. 1. Change the admin password, just in case.
9. Have phun! 1. Have phun!
For running integration tests, you also need the Chromium/Chrome web browser. For running integration tests, you also need the Chromium/Chrome web browser.
On Debian that would be `sudo apt-get install chromium`, on Ubuntu On Debian that would be `apt-get install chromium`, on Ubuntu
`sudo apt-get install chromium-browser`. `sudo apt-get install chromium-browser`.
### Manual configuration ### Manual configuration
The rake task `foodsoft:setup_development` helps you to setup foodsoft. The rails task `foodsoft:setup_development` helps you to setup foodsoft.
If you want to have more control, you can do these steps manually as If you want to have more control, you can do these steps manually as explained here.
explained here.
1. **Configure database** 1. **Configure database**
Create the database configuration from the default: Create the database configuration from the default:
cp config/database.yml.SQLite_SAMPLE config/database.yml cp config/database.yml.SQLite_SAMPLE config/database.yml
If you are fine with using a file-based sqlite database you are all set. If you are fine with using a file-based sqlite database you are all set.
The sqlite files (`development/test/production`) will reside in the `db` The sqlite files (`development/test/production`) will reside in the `db` directory. Otherwise you would want to copy one
directory. Otherwise you would want to copy one of the other of the other `database.yml.*_SAMPLE` files and edit `database.yml` to suit your needs.
`database.yml.*_SAMPLE` files and edit `database.yml` to suit your needs.
1. **Configure development environment**
2. **Configure development environment** Again, you need to create your own copy of the default configuration:
Again, you need to create your own copy of the default configuration:
cp config/environments/development.rb.SAMPLE config/environments/development.rb cp config/environments/development.rb.SAMPLE config/environments/development.rb
Edit development.rb to specify your settings (at least the ActionMailer SMTP Edit development.rb to specify your settings (at least the ActionMailer SMTP settings). If you just leave the file as is,
settings). If you just leave the file as is, emails will not work but emails will not work but everything else should be okay.
everything else should be okay.
1. **Foodsoft settings**
3. **Foodsoft settings**
You need to create your own copy of the foodsoft configuration settings: You need to create your own copy of the foodsoft configuration settings:
@ -144,37 +133,36 @@ explained here.
Edit `app_config.yml` to suit your needs or just keep the defaults for now. Edit `app_config.yml` to suit your needs or just keep the defaults for now.
1. **Create database (schema) and load defaults**
4. **Create database (schema) and load defaults** rbenv exec rails db:setup
rake db:setup With this, you also get a ready to go user with username 'admin' and password 'secret'.
With this, you also get a ready to go user with username 'admin' and 1. (optional) Get **background jobs** done
password 'secret'.
Time intensive tasks may block the web request. To run these in a separate task, you can install Redis and enable Resque:
5. (optional) Get **background jobs** done
Time intensive tasks may block the web request. To run these in a separate
task, you can install Redis and enable Resque:
* Comment `Resque.inline = true` in `config/environments/development.rb` * Comment `Resque.inline = true` in `config/environments/development.rb`
* Install [Redis](http://redis.io/) (Ubuntu package `redis-server`) * Install [Redis](http://redis.io/) (Debian/Ubuntu package `redis-server`)
* Run the worker: `rake resque:work QUEUE=foodsoft_notifier` * Run the worker:
```
rbenv exec rails resque:work QUEUE=*
```
To have look on the current queue, failed jobs etc start the resque server with To have look on the current queue, failed jobs etc start the resque server with
`resque-web`. `resque-web`.
1. (optional) **View mails in browser** instead in your logs
6. (optional) **View mails in browser** instead in your logs
We use mailcatcher in development mode to view all delivered mails in a We use mailcatcher in development mode to view all delivered mails in a
browser interface. Just install mailcatcher with gem install mailcatcher browser interface. Just install mailcatcher with `rbenv exec gem install mailcatcher`
and start the service with and start the service with:
mailcatcher mailcatcher
From now on you have a smtp server listening on 1025. To see the emails go to From now on you have a smtp server listening on 1025. To see the emails go to:
http://localhost:1080 http://localhost:1080
@ -187,4 +175,4 @@ within a docker image. While the default [`Dockerfile`](../Dockerfile) is setup
use docker-compose (using [`docker-compose-dev.yml`](../docker-compose-dev.yml)) to use docker-compose (using [`docker-compose-dev.yml`](../docker-compose-dev.yml)) to
setup the whole stack at once. setup the whole stack at once.
See [Setup Development Docker](./SETUP_DEVELOPMENT_DOCKER.md) for a detailed description. See [Setup Development Docker](./SETUP_DEVELOPMENT_DOCKER.md) for a detailed description.

View file

@ -2,7 +2,7 @@
The recommended way to run Foodsoft in production is using docker. Alternative options are The recommended way to run Foodsoft in production is using docker. Alternative options are
discussed [in the wiki](https://github.com/foodcoops/foodsoft/wiki/Deployment-notes). If you discussed [in the wiki](https://github.com/foodcoops/foodsoft/wiki/Deployment-notes). If you
have any questions, please contact the mailing list [foodsoft-discuss](http://foodsoft.51229.x6.nabble.com/foodsoft-discuss-f5.html). have any questions, please contact the mailing list [foodsoft-discuss](http://foodsoft.274.s1.nabble.com/foodsoft-discuss-f5.html).
## Docker ## Docker

View file

@ -257,6 +257,7 @@ class FoodsoftConfig
use_apple_points: true, use_apple_points: true,
# English is the default language, and this makes it show up as default. # English is the default language, and this makes it show up as default.
default_locale: 'en', default_locale: 'en',
time_zone: 'Berlin',
currency_unit: '€', currency_unit: '€',
currency_space: true, currency_space: true,
foodsoft_url: 'https://github.com/foodcoops/foodsoft', foodsoft_url: 'https://github.com/foodcoops/foodsoft',

View file

@ -33,6 +33,7 @@ namespace :foodsoft do
setup_app_config setup_app_config
setup_development setup_development
setup_database setup_database
setup_storage
start_mailcatcher start_mailcatcher
puts yellow "All done! Your foodsoft setup should be running smoothly." puts yellow "All done! Your foodsoft setup should be running smoothly."
start_server start_server
@ -43,6 +44,7 @@ namespace :foodsoft do
puts yellow "This task will help you get your foodcoop running in development via docker." puts yellow "This task will help you get your foodcoop running in development via docker."
setup_app_config setup_app_config
setup_development setup_development
setup_storage
setup_run_rake_db_setup setup_run_rake_db_setup
puts yellow "All done! Your foodsoft setup should be running smoothly via docker." puts yellow "All done! Your foodsoft setup should be running smoothly via docker."
end end
@ -112,6 +114,15 @@ def setup_development
reminder(file) reminder(file)
end end
def setup_storage
file = 'config/storage.yml'
return nil if skip?(file)
puts yellow "Copying #{file}..."
%x(cp -p #{Rails.root.join("#{file}.SAMPLE")} #{Rails.root.join(file)})
reminder(file)
end
def start_mailcatcher def start_mailcatcher
return nil if ENV['MAILCATCHER_PORT'] # skip when it has an existing Docker container return nil if ENV['MAILCATCHER_PORT'] # skip when it has an existing Docker container

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
public/apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB