From 105bcf623615e4eb168654936299fbf1b611d61f Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 3 Jan 2019 16:48:52 +0100 Subject: [PATCH 01/75] [ongoing] interface to close an accounting period --- .../controllers/admin/invoices.js.erb | 61 ++++++++++++++++++- .../admin/invoices/closePeriodModal.html.erb | 45 ++++++++++++++ .../templates/admin/invoices/index.html.erb | 6 +- 3 files changed, 108 insertions(+), 4 deletions(-) create mode 100644 app/assets/templates/admin/invoices/closePeriodModal.html.erb diff --git a/app/assets/javascripts/controllers/admin/invoices.js.erb b/app/assets/javascripts/controllers/admin/invoices.js.erb index bbef9d26a..8724466d4 100644 --- a/app/assets/javascripts/controllers/admin/invoices.js.erb +++ b/app/assets/javascripts/controllers/admin/invoices.js.erb @@ -105,7 +105,7 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I * @param invoice {Object} invoice inherited from angular's $resource */ $scope.generateAvoirForInvoice = function (invoice) { - // open modal + // open modal const modalInstance = $uibModal.open({ templateUrl: '<%= asset_path "admin/invoices/avoirModal.html" %>', controller: 'AvoirModalController', @@ -387,6 +387,14 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I return invoiceSearch(true); }; + $scope.closeAnAccountingPeriod = function() { + // open modal + $uibModal.open({ + templateUrl: '<%= asset_path "admin/invoices/closePeriodModal.html" %>', + controller: 'ClosePeriodModalController' + }); + } + /* PRIVATE SCOPE */ /** @@ -542,7 +550,7 @@ Application.Controllers.controller('AvoirModalController', ['$scope', '$uibModal $scope.openDatePicker = function ($event) { $event.preventDefault(); $event.stopPropagation(); - return $scope.datePicker.opened = true; + $scope.datePicker.opened = true; }; /** @@ -571,7 +579,7 @@ Application.Controllers.controller('AvoirModalController', ['$scope', '$uibModal } }; - /**q + /** * Cancel the refund, dismiss the modal window */ $scope.cancel = function () { $uibModalInstance.dismiss('cancel'); }; @@ -600,3 +608,50 @@ Application.Controllers.controller('AvoirModalController', ['$scope', '$uibModal return initialize(); } ]); + + +/** + * Controller used in the modal window allowing an admin to close an accounting period + */ +Application.Controllers.controller('ClosePeriodModalController', ['$scope', '$uibModalInstance', 'Invoice', 'growl', '_t', + function ($scope, $uibModalInstance, Invoice, growl, _t) { + /* PUBLIC SCOPE */ + $scope.period = { + start_date: null, + end_date: null + }; + + // AngularUI-Bootstrap datepickers parameters to define the period to close + $scope.datePicker = { + format: Fablab.uibDateFormat, + // default: datePicker are not shown + startOpened: false, + endOpened: false, + options: { + startingDay: Fablab.weekStartingDay + } + }; + + /** + * Callback to open the datepicker + */ + $scope.openDatePicker = function ($event, pickedId) { + $event.preventDefault(); + $event.stopPropagation(); + $scope.datePicker[`${pickedId}Opened`] = true; + }; + + /** + * Validate the refunding and generate a refund invoice + */ + $scope.ok = function () { + $uibModalInstance.close({ period: $scope.period }); + growl.info(_t('not_implemented_yet')); + }; + + /** + * Cancel the refund, dismiss the modal window + */ + $scope.cancel = function () { $uibModalInstance.dismiss('cancel'); }; + } +]); diff --git a/app/assets/templates/admin/invoices/closePeriodModal.html.erb b/app/assets/templates/admin/invoices/closePeriodModal.html.erb new file mode 100644 index 000000000..8b36a29ad --- /dev/null +++ b/app/assets/templates/admin/invoices/closePeriodModal.html.erb @@ -0,0 +1,45 @@ + + + diff --git a/app/assets/templates/admin/invoices/index.html.erb b/app/assets/templates/admin/invoices/index.html.erb index a421d2286..f4b4984b6 100644 --- a/app/assets/templates/admin/invoices/index.html.erb +++ b/app/assets/templates/admin/invoices/index.html.erb @@ -10,7 +10,11 @@

{{ 'invoices' }}

- +
+
+ +
+
From c35029d2057295c9d77a90a65a787248bea4c56e Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 3 Jan 2019 17:51:24 +0100 Subject: [PATCH 02/75] i18n for close period modal --- .../templates/admin/invoices/closePeriodModal.html.erb | 10 +++++----- config/locales/app.admin.en.yml | 5 +++++ config/locales/app.admin.es.yml | 5 +++++ config/locales/app.admin.fr.yml | 5 +++++ config/locales/app.admin.pt.yml | 5 +++++ 5 files changed, 25 insertions(+), 5 deletions(-) diff --git a/app/assets/templates/admin/invoices/closePeriodModal.html.erb b/app/assets/templates/admin/invoices/closePeriodModal.html.erb index 8b36a29ad..f52d11b94 100644 --- a/app/assets/templates/admin/invoices/closePeriodModal.html.erb +++ b/app/assets/templates/admin/invoices/closePeriodModal.html.erb @@ -1,10 +1,10 @@ diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml index 110ce47d7..c4666bc23 100644 --- a/config/locales/app.admin.en.yml +++ b/config/locales/app.admin.en.yml @@ -402,6 +402,11 @@ en: logo_successfully_saved: "Logo successfully saved." an_error_occurred_while_saving_the_logo: "An error occurred while saving the logo." online_payment: "Online payment" + close_accounting_period: "Close an accounting period" + close_from_date: "Close from" + start_date_is_required: "Start date is required" + close_until_date: "Close until" + end_date_is_required: "End date is required" members: # management of users, labels, groups, and so on diff --git a/config/locales/app.admin.es.yml b/config/locales/app.admin.es.yml index 8da823cc3..bbc73a549 100644 --- a/config/locales/app.admin.es.yml +++ b/config/locales/app.admin.es.yml @@ -402,6 +402,11 @@ es: logo_successfully_saved: "Logo guardado correctamente." an_error_occurred_while_saving_the_logo: "Se ha producido un error al guardar el logotipo.." online_payment: "Pago online" + close_accounting_period: "Close an accounting period" # translation_missing + close_from_date: "Close from" # translation_missing + start_date_is_required: "Start date is required" # translation_missing + close_until_date: "Close until" # translation_missing + end_date_is_required: "End date is required" # translation_missing members: # management of users, labels, groups, and so on diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml index 6007c2868..14a11f3db 100644 --- a/config/locales/app.admin.fr.yml +++ b/config/locales/app.admin.fr.yml @@ -402,6 +402,11 @@ fr: logo_successfully_saved: "Le logo bien été enregistré." an_error_occurred_while_saving_the_logo: "Une erreur est survenue lors de l'enregistrement du logo." online_payment: "Paiement en ligne" + close_accounting_period: "Clôturer une période comptable" + close_from_date: "Clôturer depuis" + start_date_is_required: "La date de début est requise" + close_until_date: "Clôturer jusqu'au" + end_date_is_required: "La date de fin est requise" members: # gestion des utilisateurs, des groupes, des étiquettes, etc. diff --git a/config/locales/app.admin.pt.yml b/config/locales/app.admin.pt.yml index e732c5e16..937c28117 100755 --- a/config/locales/app.admin.pt.yml +++ b/config/locales/app.admin.pt.yml @@ -402,6 +402,11 @@ pt: logo_successfully_saved: "Logo salvo com sucesso." an_error_occurred_while_saving_the_logo: "Um erro ocorreu ao salvar o logo." online_payment: "Pagamento Online" + close_accounting_period: "Close an accounting period" # translation_missing + close_from_date: "Close from" # translation_missing + start_date_is_required: "Start date is required" # translation_missing + close_until_date: "Close until" # translation_missing + end_date_is_required: "End date is required" # translation_missing members: # management of users, labels, groups, and so on From 0a59dc6b5d76cb19de5081c26d998e26a8499ab5 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Mon, 7 Jan 2019 10:50:10 +0100 Subject: [PATCH 03/75] display previous closings in modal --- .../controllers/admin/invoices.js.erb | 3 +- app/assets/stylesheets/modules/invoice.scss | 40 ++++++++- .../admin/invoices/closePeriodModal.html.erb | 87 ++++++++++++++++++- config/locales/app.admin.en.yml | 5 ++ config/locales/app.admin.es.yml | 5 ++ config/locales/app.admin.fr.yml | 5 ++ config/locales/app.admin.pt.yml | 15 ++-- 7 files changed, 150 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/controllers/admin/invoices.js.erb b/app/assets/javascripts/controllers/admin/invoices.js.erb index acf3706e6..a7f0c62cd 100644 --- a/app/assets/javascripts/controllers/admin/invoices.js.erb +++ b/app/assets/javascripts/controllers/admin/invoices.js.erb @@ -391,7 +391,8 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I // open modal $uibModal.open({ templateUrl: '<%= asset_path "admin/invoices/closePeriodModal.html" %>', - controller: 'ClosePeriodModalController' + controller: 'ClosePeriodModalController', + size: 'lg' }); } diff --git a/app/assets/stylesheets/modules/invoice.scss b/app/assets/stylesheets/modules/invoice.scss index 452138f54..682456da9 100644 --- a/app/assets/stylesheets/modules/invoice.scss +++ b/app/assets/stylesheets/modules/invoice.scss @@ -178,4 +178,42 @@ border-radius: 5px; font-size: small; } -} \ No newline at end of file +} + +table.closings-table { + width: 100%; + border-spacing: 0; + + thead, tbody, tr, th, td { display: block; } + + thead tr { + /* fallback */ + width: 97%; + /* minus scroll bar width */ + width: -webkit-calc(100% - 16px); + width: -moz-calc(100% - 16px); + width: calc(100% - 16px); + } + + thead tr th { + border-bottom: 0; + } + + tr:after { /* clearing float */ + content: ' '; + display: block; + visibility: hidden; + clear: both; + } + + tbody { + height: 200px; + overflow-y: auto; + overflow-x: hidden; + } + + tbody td, thead th { + width: 24%; /* 24% is less than (100% / 4 cols) = 25% */ + float: left; + } +} diff --git a/app/assets/templates/admin/invoices/closePeriodModal.html.erb b/app/assets/templates/admin/invoices/closePeriodModal.html.erb index f52d11b94..186a6dbca 100644 --- a/app/assets/templates/admin/invoices/closePeriodModal.html.erb +++ b/app/assets/templates/admin/invoices/closePeriodModal.html.erb @@ -2,8 +2,8 @@

{{ 'invoices.close_accounting_period' }}

diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml index 9d0364f59..1e96f1610 100644 --- a/config/locales/app.admin.en.yml +++ b/config/locales/app.admin.en.yml @@ -285,6 +285,7 @@ en: invoices: # list of all invoices & invoicing parameters invoices: "Invoices" + accounting_periods: "Accounting periods" invoices_list: "Invoices list" filter_invoices: "Filter invoices" invoice_#_: "Invoice #:" diff --git a/config/locales/app.admin.es.yml b/config/locales/app.admin.es.yml index 69b929cbb..fe4266d0e 100644 --- a/config/locales/app.admin.es.yml +++ b/config/locales/app.admin.es.yml @@ -285,6 +285,7 @@ es: invoices: # list of all invoices & invoicing parameters invoices: "Facturas" + accounting_periods: "Accounting periods" # missing translation invoices_list: "Lista de facturas" filter_invoices: "Filtrar facturas" invoice_#_: "Factura #:" diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml index 7126cdcaa..508e1c7ba 100644 --- a/config/locales/app.admin.fr.yml +++ b/config/locales/app.admin.fr.yml @@ -285,6 +285,7 @@ fr: invoices: # liste de toutes les factures & paramètres de facturation invoices: "Factures" + accounting_periods: "Périodes comptables" invoices_list: "Liste des factures" filter_invoices: "Filtrer les factures" invoice_#_: "Facture n° :" diff --git a/config/locales/app.admin.pt.yml b/config/locales/app.admin.pt.yml index 14f094bf4..4d0f916a3 100755 --- a/config/locales/app.admin.pt.yml +++ b/config/locales/app.admin.pt.yml @@ -285,6 +285,7 @@ pt: invoices: # list of all invoices & invoicing parameters invoices: "Faturas" + accounting_periods: "Accounting periods" # missing translation invoices_list: "Lista de faturas" filter_invoices: "Filtrar faturas" invoice_#_: "Fatura #:" From 8530a6f6550dfa62b33675a379c17f57c4a3aba0 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 20 Mar 2019 16:49:38 +0100 Subject: [PATCH 51/75] superadmin + fixed free space notification --- app/assets/javascripts/controllers/admin/members.js.erb | 2 +- app/models/user.rb | 6 ++++++ .../_notify_admin_free_disk_space.json.jbuilder | 2 +- app/views/application/index.html.erb | 1 + .../notify_admin_free_disk_space.html.erb | 2 +- app/workers/free_disk_space_worker.rb | 2 +- config/application.yml.default | 1 + config/locales/en.yml | 2 +- config/locales/es.yml | 2 +- config/locales/fr.yml | 2 +- config/locales/pt.yml | 2 +- config/secrets.yml | 4 ++++ doc/environment.md | 5 +++++ docker/env.example | 2 ++ 14 files changed, 27 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/controllers/admin/members.js.erb b/app/assets/javascripts/controllers/admin/members.js.erb index 3b4d5fd1a..c8c49ed49 100644 --- a/app/assets/javascripts/controllers/admin/members.js.erb +++ b/app/assets/javascripts/controllers/admin/members.js.erb @@ -150,7 +150,7 @@ Application.Controllers.controller('AdminMembersController', ['$scope', '$sce', }; // admins list - $scope.admins = adminsPromise.admins; + $scope.admins = adminsPromise.admins.filter(function(m) { return m.id != Fablab.superadminId; }); // Admins ordering/sorting. Default: not sorted $scope.orderAdmin = null; diff --git a/app/models/user.rb b/app/models/user.rb index 0cda62a55..74d700f1f 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -93,6 +93,12 @@ class User < ActiveRecord::Base User.with_role(:admin) end + def self.superadmin + return unless Rails.application.secrets.superadmin_email.present? + + User.find_by(email: Rails.application.secrets.superadmin_email) + end + def training_machine?(machine) return true if admin? diff --git a/app/views/api/notifications/_notify_admin_free_disk_space.json.jbuilder b/app/views/api/notifications/_notify_admin_free_disk_space.json.jbuilder index f729e4d01..bf275289d 100644 --- a/app/views/api/notifications/_notify_admin_free_disk_space.json.jbuilder +++ b/app/views/api/notifications/_notify_admin_free_disk_space.json.jbuilder @@ -1,3 +1,3 @@ json.title notification.notification_type -json.description t('warning_disk_space_under_threshold', THRESHOLD: notification.meta_data.threshold) +json.description t('.warning_free_disk_space', AVAILABLE: number_with_delimiter(notification.meta_data['mb_available'])) json.url notification_url(notification, format: :json) diff --git a/app/views/application/index.html.erb b/app/views/application/index.html.erb index 84703a1ca..006d32d36 100644 --- a/app/views/application/index.html.erb +++ b/app/views/application/index.html.erb @@ -28,6 +28,7 @@ Fablab.disqusShortname = "<%= Rails.application.secrets.disqus_shortname %>"; Fablab.defaultHost = "<%= Rails.application.secrets.default_host %>"; Fablab.gaId = "<%= Rails.application.secrets.google_analytics_id %>"; + Fablab.superadminId = parseInt("<%= User.superadmin&.id %>", 10); // i18n stuff Fablab.locale = "<%= Rails.application.secrets.app_locale %>"; diff --git a/app/views/notifications_mailer/notify_admin_free_disk_space.html.erb b/app/views/notifications_mailer/notify_admin_free_disk_space.html.erb index 5543e94f6..ee977d2d7 100644 --- a/app/views/notifications_mailer/notify_admin_free_disk_space.html.erb +++ b/app/views/notifications_mailer/notify_admin_free_disk_space.html.erb @@ -1,4 +1,4 @@ <%= render 'notifications_mailer/shared/hello', recipient: @recipient %> -

<%= t('.body', THRESHOLD: @notification.get_meta_data(:threshold), AVAILABLE: @notification.get_meta_data(:mb_available)) %>

+

<%= t('.body', THRESHOLD: number_with_delimiter(@notification.get_meta_data(:threshold)), AVAILABLE: number_with_delimiter(@notification.get_meta_data(:mb_available))) %>

diff --git a/app/workers/free_disk_space_worker.rb b/app/workers/free_disk_space_worker.rb index 6066e67ed..f5d798365 100644 --- a/app/workers/free_disk_space_worker.rb +++ b/app/workers/free_disk_space_worker.rb @@ -13,7 +13,7 @@ class FreeDiskSpaceWorker return if mb_available > Rails.application.secrets.disk_space_mb_alert NotificationCenter.call type: 'notify_admin_free_disk_space', - receiver: User.admins, + receiver: User.superadmin || User.admins, attached_object: Role.first, meta_data: { mb_available: mb_available, diff --git a/config/application.yml.default b/config/application.yml.default index db4ba61c1..cce865aa7 100644 --- a/config/application.yml.default +++ b/config/application.yml.default @@ -62,6 +62,7 @@ OPENLAB_BASE_URI: 'https://openprojects.fab-manager.com' LOG_LEVEL: 'debug' DISK_SPACE_MB_ALERT: '100' +SUPERADMIN_EMAIL: 'admin@sleede.com' ALLOWED_EXTENSIONS: pdf ai eps cad math svg stl dxf dwg obj step iges igs 3dm 3dmf doc docx png ino scad fcad skp sldprt sldasm slddrw slddrt tex latex ps ALLOWED_MIME_TYPES: application/pdf application/postscript application/illustrator image/x-eps image/svg+xml application/sla application/dxf application/acad application/dwg application/octet-stream application/step application/iges model/iges x-world/x-3dmf application/vnd.openxmlformats-officedocument.wordprocessingml.document image/png text/x-arduino text/plain application/scad application/vnd.sketchup.skp application/x-koan application/vnd-koan koan/x-skm application/vnd.koan application/x-tex application/x-latex diff --git a/config/locales/en.yml b/config/locales/en.yml index 5b9e3390a..0690a80e2 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -310,7 +310,7 @@ en: enjoy_a_discount_of_PERCENT_with_code_CODE: "Enjoy a discount of %{PERCENT}% with code %{CODE}" enjoy_a_discount_of_AMOUNT_with_code_CODE: "Enjoy a discount of %{AMOUNT} with code %{CODE}" notify_admin_free_disk_space: - warning_disk_space_under_threshold: "Warning: the server's available disk space is now %{AVAILABLE} MiB" + warning_free_disk_space: "Warning: the server's available disk space is now %{AVAILABLE} MiB" notify_admin_close_period_reminder: warning_last_closed_period_over_1_year: "Please remind to periodically close your accounting periods. Last closed period ended at %{LAST_END}" warning_no_closed_periods: "Please remind to periodically close your accounting periods. You have to close periods from %{FIRST_DATE}" diff --git a/config/locales/es.yml b/config/locales/es.yml index 0e0dcbe36..7b63df33d 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -310,7 +310,7 @@ es: enjoy_a_discount_of_PERCENT_with_code_CODE: "Disfruta de un descuento de %{PERCENT}% con el código %{CODE}" enjoy_a_discount_of_AMOUNT_with_code_CODE: "Disfruta de un descuento de %{AMOUNT} con el código %{CODE}" notify_admin_free_disk_space: - warning_disk_space_under_threshold: "Warning: the server's available disk space is now %{AVAILABLE} MiB" # missing translation + warning_free_disk_space: "Warning: the server's available disk space is now %{AVAILABLE} MiB" # missing translation notify_admin_close_period_reminder: warning_last_closed_period_over_1_year: "Please remind to periodically close your accounting periods. Last closed period ended at %{LAST_END}" # missing translation warning_no_closed_periods: "Please remind to periodically close your accounting periods. You have to close periods from %{FIRST_DATE}" # missing translation diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 497704060..aedcbcee7 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -310,7 +310,7 @@ fr: enjoy_a_discount_of_PERCENT_with_code_CODE: "Bénéficiez d'une remise de %{PERCENT} % avec le code %{CODE}" enjoy_a_discount_of_AMOUNT_with_code_CODE: "Bénéficiez d'une remise de %{AMOUNT} avec le code %{CODE}" notify_admin_free_disk_space: - warning_disk_space_under_threshold: "Attention: l'espace disque disponible sur le serveur est désormais de %{AVAILABLE} MiO" + warning_free_disk_space: "Attention: l'espace disque disponible sur le serveur est désormais de %{AVAILABLE} MiO" notify_admin_close_period_reminder: warning_last_closed_period_over_1_year: "Pensez à clôturer régulièrement vos périodes comptables. Les comptes sont actuellement clôturés jusqu'au %{LAST_END}" warning_no_closed_periods: "Pensez à clôturer régulièrement vos périodes comptables. Vous devez clôturer des périodes depuis le %{FIRST_DATE}" diff --git a/config/locales/pt.yml b/config/locales/pt.yml index cffb4270e..00ef49273 100755 --- a/config/locales/pt.yml +++ b/config/locales/pt.yml @@ -310,7 +310,7 @@ pt: enjoy_a_discount_of_PERCENT_with_code_CODE: "Desfrute de um desconto de %{PERCENT}% com o código %{CODE}" enjoy_a_discount_of_AMOUNT_with_code_CODE: "Desfrute de um desconto de %{AMOUNT} com o código %{CODE}" notify_admin_free_disk_space: - warning_disk_space_under_threshold: "Warning: the server's available disk space is now %{AVAILABLE} MiB" # missing translation + warning_free_disk_space: "Warning: the server's available disk space is now %{AVAILABLE} MiB" # missing translation notify_admin_close_period_reminder: warning_last_closed_period_over_1_year: "Please remind to periodically close your accounting periods. Last closed period ended at %{LAST_END}" # missing translation warning_no_closed_periods: "Please remind to periodically close your accounting periods. You have to close periods from %{FIRST_DATE}" # missing translation diff --git a/config/secrets.yml b/config/secrets.yml index 189401adf..105cf9671 100644 --- a/config/secrets.yml +++ b/config/secrets.yml @@ -43,6 +43,7 @@ development: elaticsearch_host: <%= ENV["ELASTICSEARCH_HOST"] %> max_image_size: <%= ENV["MAX_IMAGE_SIZE"] %> disk_space_mb_alert: <%= ENV["DISK_SPACE_MB_ALERT"] %> + superadmin_email: <%= ENV["SUPERADMIN_EMAIL"] %> test: secret_key_base: 83daf5e7b80d990f037407bab78dff9904aaf3c195a50f84fa8695a22287e707dfbd9524b403b1dcf116ae1d8c06844c3d7ed942564e5b46be6ae3ead93a9d30 @@ -77,6 +78,7 @@ test: elaticsearch_host: <%= ENV["ELASTICSEARCH_HOST"] %> max_image_size: <%= ENV["MAX_IMAGE_SIZE"] %> disk_space_mb_alert: <%= ENV["DISK_SPACE_MB_ALERT"] %> + superadmin_email: <%= ENV["SUPERADMIN_EMAIL"] %> staging: secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> @@ -119,6 +121,7 @@ staging: elaticsearch_host: <%= ENV["ELASTICSEARCH_HOST"] %> max_image_size: <%= ENV["MAX_IMAGE_SIZE"] %> disk_space_mb_alert: <%= ENV["DISK_SPACE_MB_ALERT"] %> + superadmin_email: <%= ENV["SUPERADMIN_EMAIL"] %> # Do not keep production secrets in the repository, # instead read values from the environment. @@ -164,3 +167,4 @@ production: elaticsearch_host: <%= ENV["ELASTICSEARCH_HOST"] %> max_image_size: <%= ENV["MAX_IMAGE_SIZE"] %> disk_space_mb_alert: <%= ENV["DISK_SPACE_MB_ALERT"] %> + superadmin_email: <%= ENV["SUPERADMIN_EMAIL"] %> diff --git a/doc/environment.md b/doc/environment.md index 8733a8c6a..937e01bcd 100644 --- a/doc/environment.md +++ b/doc/environment.md @@ -152,6 +152,11 @@ The check will run every weeks and if the threshold is exceeded, an alert will b Credentials for the first admin user created when seeding the project. (not present in application.yml because they are only used once when running the database seed with the command `rake db:seed`) + SUPERADMIN_EMAIL + +Optional email of the administrator account in charge of the system administration. +If specified, it will be hidden from the administrators list and he will exclusively receive the notifications related to the system administration. +If not specified, every admins will receive system administration notifications. ## Internationalization settings diff --git a/docker/env.example b/docker/env.example index 51ed48325..f2f5e9a6c 100644 --- a/docker/env.example +++ b/docker/env.example @@ -61,6 +61,8 @@ NAVINUM_API_LOGIN= NAVINUM_API_PASSWORD= LOG_LEVEL=debug +DISK_SPACE_MB_ALERT='100' +SUPERADMIN_EMAIL='admin@sleede.com' ALLOWED_EXTENSIONS=pdf ai eps cad math svg stl dxf dwg obj step iges igs 3dm 3dmf doc docx png ino scad fcad skp sldprt sldasm slddrw slddrt tex latex ps From 99bbfdc4e8766d81717f8cabbd20f7028cc7f2b0 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 20 Mar 2019 16:53:09 +0100 Subject: [PATCH 52/75] updated licence infos --- 3rd-PARTY-LICENSES.md | 34 ++++++++++++++++++++++++++++++++++ LICENSE.md | 39 +-------------------------------------- 2 files changed, 35 insertions(+), 38 deletions(-) create mode 100644 3rd-PARTY-LICENSES.md diff --git a/3rd-PARTY-LICENSES.md b/3rd-PARTY-LICENSES.md new file mode 100644 index 000000000..e112be4c1 --- /dev/null +++ b/3rd-PARTY-LICENSES.md @@ -0,0 +1,34 @@ + +Fab-Manager uses some external components, which are licenced under the +terms of the following licences: + +- [Apache 2 license](http://www.apache.org/licenses/LICENSE-2.0): + - [jasny-bootstrap](https://github.com/jasny/bootstrap/) + - [elasticsearch](https://github.com/elasticsearch/bower-elasticsearch-js) + - [nvd3](https://github.com/novus/nvd3) + - [angular-bootstrap-switch](https://github.com/frapontillo/angular-bootstrap-switch) + - [elasticsearch-rails](https://github.com/elastic/elasticsearch-rails) + - [elasticsearch-model](https://github.com/elastic/elasticsearch-rails/tree/master/elasticsearch-model) + - [elasticsearch-persistence](https://github.com/elastic/elasticsearch-rails/tree/master/elasticsearch-persistence) + - font [Open Sans](http://www.fontsquirrel.com/fonts/open-sans) + +- [General Public License version 2](http://www.gnu.org/licenses/old-licenses/gpl-2.0-faq.en.html): + - [railroady](https://github.com/preston/railroady) + - [unicorn](https://github.com/defunkt/unicorn) + - [prawn](https://github.com/prawnpdf/prawn) + - [prawn-table](https://github.com/prawnpdf/prawn-table) + +- [BSD-2-Clause](https://opensource.org/licenses/BSD-2-Clause) + - [ruby](https://www.ruby-lang.org) + - [rubyzip](https://github.com/rubyzip/rubyzip) + - [byebug](https://github.com/deivid-rodriguez/byebug) + +- [MIT Licence](https://opensource.org/licenses/MIT) + - Errors and omissions excepted, all the other external libraries used + in this project. + +Please refer to the libraries documentation for more information about +their licences. + +Complete lists of used libraries are available in `package.json` for the +JS/EcmaScript libraries and in `Gemfile` for Ruby libraries. diff --git a/LICENSE.md b/LICENSE.md index 3deb50991..e20c7f8ad 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -Copyright (C) 2015 La Casemate +Copyright (C) 2019 Sleede This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published @@ -14,43 +14,6 @@ Copyright (C) 2015 La Casemate along with this program. If not, see . -Fab-Manager uses some external components, which are licenced under the -terms of the following licences: - -- [Apache 2 license](http://www.apache.org/licenses/LICENSE-2.0): - - [jasny-bootstrap](https://github.com/jasny/bootstrap/) - - [elasticsearch](https://github.com/elasticsearch/bower-elasticsearch-js) - - [nvd3](https://github.com/novus/nvd3) - - [angular-bootstrap-switch](https://github.com/frapontillo/angular-bootstrap-switch) - - [elasticsearch-rails](https://github.com/elastic/elasticsearch-rails) - - [elasticsearch-model](https://github.com/elastic/elasticsearch-rails/tree/master/elasticsearch-model) - - [elasticsearch-persistence](https://github.com/elastic/elasticsearch-rails/tree/master/elasticsearch-persistence) - - font [Open Sans](http://www.fontsquirrel.com/fonts/open-sans) - -- [General Public License version 2](http://www.gnu.org/licenses/old-licenses/gpl-2.0-faq.en.html): - - [railroady](https://github.com/preston/railroady) - - [unicorn](https://github.com/defunkt/unicorn) - - [prawn](https://github.com/prawnpdf/prawn) - - [prawn-table](https://github.com/prawnpdf/prawn-table) - -- [BSD-2-Clause](https://opensource.org/licenses/BSD-2-Clause) - - [ruby](https://www.ruby-lang.org) - - [rubyzip](https://github.com/rubyzip/rubyzip) - - [byebug](https://github.com/deivid-rodriguez/byebug) - -- [MIT Licence](https://opensource.org/licenses/MIT) - - Errors and omissions excepted, all the other external libraries used - in this project. - -Please refer to the libraries documentation for more information about -their licences. - -Complete lists of used libraries are available in `bower.json` for the -JS/EcmaScript libraries and in `Gemfile` for Ruby libraries. - - - - GNU AFFERO GENERAL PUBLIC LICENSE Version 3, 19 November 2007 From 4a73798d9934c7fad216483b89cbe6f86892de6e Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 21 Mar 2019 15:37:44 +0100 Subject: [PATCH 53/75] updated changelog --- CHANGELOG_PREMIUM.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG_PREMIUM.md b/CHANGELOG_PREMIUM.md index 59662c4d8..ace5fe7b6 100644 --- a/CHANGELOG_PREMIUM.md +++ b/CHANGELOG_PREMIUM.md @@ -6,4 +6,4 @@ - [TODO DEPLOY] `rake fablab:setup:chain_invoices_items_records` - [TODO DEPLOY] `rake fablab:setup:chain_invoices_records` - [TODO DEPLOY] `rake fablab:setup:chain_history_values_records` -- [TODO DEPLOY] add `DISK_SPACE_MB_ALERT` environment variable (see [doc/environment.md](doc/environment.md) for configuration details) +- [TODO DEPLOY] add `DISK_SPACE_MB_ALERT` and `SUPERADMIN_EMAIL` environment variables (see [doc/environment.md](doc/environment.md) for configuration details) From fd55c8d31555a31a49bda717f3ad3e5e13f0d481 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Thu, 21 Mar 2019 17:15:41 +0100 Subject: [PATCH 54/75] use SHA-3 (256 bits) to compute checksums + simplify accounting period integrity check UI --- Gemfile | 2 + Gemfile.lock | 2 + .../templates/admin/invoices/_period.html.erb | 11 +- db/schema.rb | 178 +++++++++--------- lib/checksum.rb | 5 +- test/fixtures/accounting_periods.yml | 2 +- test/fixtures/history_values.yml | 32 ++++ test/fixtures/invoice_items.yml | 10 +- test/fixtures/invoices.yml | 10 +- 9 files changed, 141 insertions(+), 111 deletions(-) diff --git a/Gemfile b/Gemfile index 0fe754f3e..76e486d31 100644 --- a/Gemfile +++ b/Gemfile @@ -151,3 +151,5 @@ gem 'rack-protection', '1.5.5' # get free disk space gem 'sys-filesystem' + +gem 'sha3' diff --git a/Gemfile.lock b/Gemfile.lock index 182c10e1e..5abb3e777 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -403,6 +403,7 @@ GEM seed_dump (3.2.2) activerecord (~> 4) activesupport (~> 4) + sha3 (1.0.1) sidekiq (3.3.4) celluloid (>= 0.16.0) connection_pool (>= 2.1.1) @@ -577,6 +578,7 @@ DEPENDENCIES sass-rails (= 5.0.1) sdoc (~> 0.4.0) seed_dump + sha3 sidekiq sidekiq-cron sinatra diff --git a/app/assets/templates/admin/invoices/_period.html.erb b/app/assets/templates/admin/invoices/_period.html.erb index 907eb9d4a..f234c2e1d 100644 --- a/app/assets/templates/admin/invoices/_period.html.erb +++ b/app/assets/templates/admin/invoices/_period.html.erb @@ -5,14 +5,7 @@
  • {{ 'invoices.perpetual_total' }} : {{period.perpetual_total | currency}}
  • {{ 'invoices.integrity' }} : - -
    - - - - - -
    -
    + +
  • diff --git a/db/schema.rb b/db/schema.rb index 18c91bd5f..9a1fc5ab6 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -44,14 +44,14 @@ ActiveRecord::Schema.define(version: 20190320091148) do end create_table "addresses", force: :cascade do |t| - t.string "address", limit: 255 - t.string "street_number", limit: 255 - t.string "route", limit: 255 - t.string "locality", limit: 255 - t.string "country", limit: 255 - t.string "postal_code", limit: 255 + t.string "address" + t.string "street_number" + t.string "route" + t.string "locality" + t.string "country" + t.string "postal_code" t.integer "placeable_id" - t.string "placeable_type", limit: 255 + t.string "placeable_type" t.datetime "created_at" t.datetime "updated_at" end @@ -67,9 +67,9 @@ ActiveRecord::Schema.define(version: 20190320091148) do create_table "assets", force: :cascade do |t| t.integer "viewable_id" - t.string "viewable_type", limit: 255 - t.string "attachment", limit: 255 - t.string "type", limit: 255 + t.string "viewable_type" + t.string "attachment" + t.string "type" t.datetime "created_at" t.datetime "updated_at" end @@ -86,12 +86,12 @@ ActiveRecord::Schema.define(version: 20190320091148) do create_table "availabilities", force: :cascade do |t| t.datetime "start_at" t.datetime "end_at" - t.string "available_type", limit: 255 + t.string "available_type" t.datetime "created_at" t.datetime "updated_at" t.integer "nb_total_places" - t.boolean "destroying", default: false - t.boolean "lock", default: false + t.boolean "destroying", default: false + t.boolean "lock", default: false end create_table "availability_tags", force: :cascade do |t| @@ -105,7 +105,7 @@ ActiveRecord::Schema.define(version: 20190320091148) do add_index "availability_tags", ["tag_id"], name: "index_availability_tags_on_tag_id", using: :btree create_table "categories", force: :cascade do |t| - t.string "name", limit: 255 + t.string "name" t.datetime "created_at" t.datetime "updated_at" t.string "slug" @@ -114,7 +114,7 @@ ActiveRecord::Schema.define(version: 20190320091148) do add_index "categories", ["slug"], name: "index_categories_on_slug", unique: true, using: :btree create_table "components", force: :cascade do |t| - t.string "name", limit: 255, null: false + t.string "name", null: false end create_table "coupons", force: :cascade do |t| @@ -132,7 +132,7 @@ ActiveRecord::Schema.define(version: 20190320091148) do create_table "credits", force: :cascade do |t| t.integer "creditable_id" - t.string "creditable_type", limit: 255 + t.string "creditable_type" t.integer "plan_id" t.integer "hours" t.datetime "created_at" @@ -173,7 +173,7 @@ ActiveRecord::Schema.define(version: 20190320091148) do add_index "event_themes", ["slug"], name: "index_event_themes_on_slug", unique: true, using: :btree create_table "events", force: :cascade do |t| - t.string "title", limit: 255 + t.string "title" t.text "description" t.datetime "created_at" t.datetime "updated_at" @@ -211,10 +211,10 @@ ActiveRecord::Schema.define(version: 20190320091148) do add_index "exports", ["user_id"], name: "index_exports_on_user_id", using: :btree create_table "friendly_id_slugs", force: :cascade do |t| - t.string "slug", limit: 255, null: false - t.integer "sluggable_id", null: false + t.string "slug", null: false + t.integer "sluggable_id", null: false t.string "sluggable_type", limit: 50 - t.string "scope", limit: 255 + t.string "scope" t.datetime "created_at" end @@ -224,10 +224,10 @@ ActiveRecord::Schema.define(version: 20190320091148) do add_index "friendly_id_slugs", ["sluggable_type"], name: "index_friendly_id_slugs_on_sluggable_type", using: :btree create_table "groups", force: :cascade do |t| - t.string "name", limit: 255 + t.string "name" t.datetime "created_at" t.datetime "updated_at" - t.string "slug", limit: 255 + t.string "slug" t.boolean "disabled" end @@ -247,7 +247,7 @@ ActiveRecord::Schema.define(version: 20190320091148) do create_table "invoice_items", force: :cascade do |t| t.integer "invoice_id" - t.string "stp_invoice_item_id", limit: 255 + t.string "stp_invoice_item_id" t.integer "amount" t.datetime "created_at" t.datetime "updated_at" @@ -261,17 +261,17 @@ ActiveRecord::Schema.define(version: 20190320091148) do create_table "invoices", force: :cascade do |t| t.integer "invoiced_id" - t.string "invoiced_type", limit: 255 - t.string "stp_invoice_id", limit: 255 + t.string "invoiced_type" + t.string "stp_invoice_id" t.integer "total" t.datetime "created_at" t.datetime "updated_at" t.integer "user_id" - t.string "reference", limit: 255 - t.string "avoir_mode", limit: 255 + t.string "reference" + t.string "avoir_mode" t.datetime "avoir_date" t.integer "invoice_id" - t.string "type", limit: 255 + t.string "type" t.boolean "subscription_to_expire" t.text "description" t.integer "wallet_amount" @@ -288,17 +288,17 @@ ActiveRecord::Schema.define(version: 20190320091148) do add_index "invoices", ["wallet_transaction_id"], name: "index_invoices_on_wallet_transaction_id", using: :btree create_table "licences", force: :cascade do |t| - t.string "name", limit: 255, null: false + t.string "name", null: false t.text "description" end create_table "machines", force: :cascade do |t| - t.string "name", limit: 255, null: false + t.string "name", null: false t.text "description" t.text "spec" t.datetime "created_at" t.datetime "updated_at" - t.string "slug", limit: 255 + t.string "slug" t.boolean "disabled" end @@ -315,14 +315,14 @@ ActiveRecord::Schema.define(version: 20190320091148) do create_table "notifications", force: :cascade do |t| t.integer "receiver_id" t.integer "attached_object_id" - t.string "attached_object_type", limit: 255 + t.string "attached_object_type" t.integer "notification_type_id" - t.boolean "is_read", default: false + t.boolean "is_read", default: false t.datetime "created_at" t.datetime "updated_at" t.string "receiver_type" - t.boolean "is_send", default: false - t.jsonb "meta_data", default: {} + t.boolean "is_send", default: false + t.jsonb "meta_data", default: {} end add_index "notifications", ["notification_type_id"], name: "index_notifications_on_notification_type_id", using: :btree @@ -391,20 +391,20 @@ ActiveRecord::Schema.define(version: 20190320091148) do add_index "organizations", ["profile_id"], name: "index_organizations_on_profile_id", using: :btree create_table "plans", force: :cascade do |t| - t.string "name", limit: 255 + t.string "name" t.integer "amount" - t.string "interval", limit: 255 + t.string "interval" t.integer "group_id" - t.string "stp_plan_id", limit: 255 + t.string "stp_plan_id" t.datetime "created_at" t.datetime "updated_at" - t.integer "training_credit_nb", default: 0 - t.boolean "is_rolling", default: true + t.integer "training_credit_nb", default: 0 + t.boolean "is_rolling", default: true t.text "description" t.string "type" t.string "base_name" - t.integer "ui_weight", default: 0 - t.integer "interval_count", default: 1 + t.integer "ui_weight", default: 0 + t.integer "interval_count", default: 1 t.string "slug" t.boolean "disabled" end @@ -434,11 +434,11 @@ ActiveRecord::Schema.define(version: 20190320091148) do create_table "profiles", force: :cascade do |t| t.integer "user_id" - t.string "first_name", limit: 255 - t.string "last_name", limit: 255 + t.string "first_name" + t.string "last_name" t.boolean "gender" t.date "birthday" - t.string "phone", limit: 255 + t.string "phone" t.text "interest" t.text "software_mastered" t.datetime "created_at" @@ -468,7 +468,7 @@ ActiveRecord::Schema.define(version: 20190320091148) do t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.string "title", limit: 255 + t.string "title" t.integer "step_nb" end @@ -479,27 +479,27 @@ ActiveRecord::Schema.define(version: 20190320091148) do t.integer "user_id" t.datetime "created_at" t.datetime "updated_at" - t.boolean "is_valid", default: false - t.string "valid_token", limit: 255 + t.boolean "is_valid", default: false + t.string "valid_token" end add_index "project_users", ["project_id"], name: "index_project_users_on_project_id", using: :btree add_index "project_users", ["user_id"], name: "index_project_users_on_user_id", using: :btree create_table "projects", force: :cascade do |t| - t.string "name", limit: 255 + t.string "name" t.text "description" t.datetime "created_at" t.datetime "updated_at" t.integer "author_id" t.text "tags" t.integer "licence_id" - t.string "state", limit: 255 - t.string "slug", limit: 255 + t.string "state" + t.string "slug" t.datetime "published_at" end - add_index "projects", ["slug"], name: "index_projects_on_slug", using: :btree + add_index "projects", ["slug"], name: "index_projects_on_slug", unique: true, using: :btree create_table "projects_components", force: :cascade do |t| t.integer "project_id" @@ -539,19 +539,19 @@ ActiveRecord::Schema.define(version: 20190320091148) do t.datetime "created_at" t.datetime "updated_at" t.integer "reservable_id" - t.string "reservable_type", limit: 255 - t.string "stp_invoice_id", limit: 255 + t.string "reservable_type" + t.string "stp_invoice_id" t.integer "nb_reserve_places" end - add_index "reservations", ["reservable_id", "reservable_type"], name: "index_reservations_on_reservable_id_and_reservable_type", using: :btree + add_index "reservations", ["reservable_type", "reservable_id"], name: "index_reservations_on_reservable_type_and_reservable_id", using: :btree add_index "reservations", ["stp_invoice_id"], name: "index_reservations_on_stp_invoice_id", using: :btree add_index "reservations", ["user_id"], name: "index_reservations_on_user_id", using: :btree create_table "roles", force: :cascade do |t| - t.string "name", limit: 255 + t.string "name" t.integer "resource_id" - t.string "resource_type", limit: 255 + t.string "resource_type" t.datetime "created_at" t.datetime "updated_at" end @@ -625,18 +625,18 @@ ActiveRecord::Schema.define(version: 20190320091148) do create_table "statistic_fields", force: :cascade do |t| t.integer "statistic_index_id" - t.string "key", limit: 255 - t.string "label", limit: 255 + t.string "key" + t.string "label" t.datetime "created_at" t.datetime "updated_at" - t.string "data_type", limit: 255 + t.string "data_type" end add_index "statistic_fields", ["statistic_index_id"], name: "index_statistic_fields_on_statistic_index_id", using: :btree create_table "statistic_graphs", force: :cascade do |t| t.integer "statistic_index_id" - t.string "chart_type", limit: 255 + t.string "chart_type" t.integer "limit" t.datetime "created_at" t.datetime "updated_at" @@ -645,17 +645,17 @@ ActiveRecord::Schema.define(version: 20190320091148) do add_index "statistic_graphs", ["statistic_index_id"], name: "index_statistic_graphs_on_statistic_index_id", using: :btree create_table "statistic_indices", force: :cascade do |t| - t.string "es_type_key", limit: 255 - t.string "label", limit: 255 + t.string "es_type_key" + t.string "label" t.datetime "created_at" t.datetime "updated_at" - t.boolean "table", default: true - t.boolean "ca", default: true + t.boolean "table", default: true + t.boolean "ca", default: true end create_table "statistic_sub_types", force: :cascade do |t| - t.string "key", limit: 255 - t.string "label", limit: 255 + t.string "key" + t.string "label" t.datetime "created_at" t.datetime "updated_at" end @@ -672,8 +672,8 @@ ActiveRecord::Schema.define(version: 20190320091148) do create_table "statistic_types", force: :cascade do |t| t.integer "statistic_index_id" - t.string "key", limit: 255 - t.string "label", limit: 255 + t.string "key" + t.string "label" t.boolean "graph" t.datetime "created_at" t.datetime "updated_at" @@ -691,7 +691,7 @@ ActiveRecord::Schema.define(version: 20190320091148) do create_table "subscriptions", force: :cascade do |t| t.integer "plan_id" t.integer "user_id" - t.string "stp_subscription_id", limit: 255 + t.string "stp_subscription_id" t.datetime "created_at" t.datetime "updated_at" t.datetime "expiration_date" @@ -710,7 +710,7 @@ ActiveRecord::Schema.define(version: 20190320091148) do add_index "tags", ["name"], name: "index_tags_on_name", unique: true, using: :btree create_table "themes", force: :cascade do |t| - t.string "name", limit: 255, null: false + t.string "name", null: false end create_table "tickets", force: :cascade do |t| @@ -725,13 +725,13 @@ ActiveRecord::Schema.define(version: 20190320091148) do add_index "tickets", ["reservation_id"], name: "index_tickets_on_reservation_id", using: :btree create_table "trainings", force: :cascade do |t| - t.string "name", limit: 255 + t.string "name" t.datetime "created_at" t.datetime "updated_at" t.integer "nb_total_places" - t.string "slug", limit: 255 + t.string "slug" t.text "description" - t.boolean "public_page", default: true + t.boolean "public_page", default: true t.boolean "disabled" end @@ -787,31 +787,31 @@ ActiveRecord::Schema.define(version: 20190320091148) do add_index "user_trainings", ["user_id"], name: "index_user_trainings_on_user_id", using: :btree create_table "users", force: :cascade do |t| - t.string "email", limit: 255, default: "", null: false - t.string "encrypted_password", limit: 255, default: "", null: false - t.string "reset_password_token", limit: 255 + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false + t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" - t.integer "sign_in_count", default: 0, null: false + t.integer "sign_in_count", default: 0, null: false t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" - t.string "current_sign_in_ip", limit: 255 - t.string "last_sign_in_ip", limit: 255 - t.string "confirmation_token", limit: 255 + t.string "current_sign_in_ip" + t.string "last_sign_in_ip" + t.string "confirmation_token" t.datetime "confirmed_at" t.datetime "confirmation_sent_at" - t.string "unconfirmed_email", limit: 255 - t.integer "failed_attempts", default: 0, null: false - t.string "unlock_token", limit: 255 + t.string "unconfirmed_email" + t.integer "failed_attempts", default: 0, null: false + t.string "unlock_token" t.datetime "locked_at" t.datetime "created_at" t.datetime "updated_at" - t.boolean "is_allow_contact", default: true + t.boolean "is_allow_contact", default: true t.integer "group_id" - t.string "stp_customer_id", limit: 255 - t.string "username", limit: 255 - t.string "slug", limit: 255 - t.boolean "is_active", default: true + t.string "stp_customer_id" + t.string "username" + t.string "slug" + t.boolean "is_active", default: true t.string "provider" t.string "uid" t.string "auth_token" diff --git a/lib/checksum.rb b/lib/checksum.rb index 19ff5b558..ad79b34ec 100644 --- a/lib/checksum.rb +++ b/lib/checksum.rb @@ -35,8 +35,9 @@ class Checksum end def text(data) - sha256 = Digest::SHA256.new - sha256.hexdigest data + require 'sha3' + + SHA3::Digest.hexdigest(:sha256, data) end private diff --git a/test/fixtures/accounting_periods.yml b/test/fixtures/accounting_periods.yml index 172df1fe8..eb22ae4ec 100644 --- a/test/fixtures/accounting_periods.yml +++ b/test/fixtures/accounting_periods.yml @@ -5,4 +5,4 @@ period2015: closed_by: 1 period_total: 1500 perpetual_total: 4500 - footprint: 6ee52479fd210a13abb80d8c186991b504d2376a8d9fc7d35658ca05770eb10c + footprint: 38afdbe83dd1289a56575e72b23045c5414cfca57d52a05912ad0668bb2e2308 diff --git a/test/fixtures/history_values.yml b/test/fixtures/history_values.yml index 7b866882e..7565e8ee6 100644 --- a/test/fixtures/history_values.yml +++ b/test/fixtures/history_values.yml @@ -17,6 +17,7 @@ value_history_1: des autres utilisateurs.

    ' created_at: 2018-12-17 11:23:01.224566000 Z updated_at: 2018-12-17 11:23:01.224566000 Z + footprint: 3e120f804f0f9bf963cc88638372d0eab5289780639b6d68afb5c612a867cb02 value_history_2: id: 2 @@ -25,6 +26,7 @@ value_history_2: value: Imaginer, Fabriquer,
    Partager au Fab Lab
    de La Casemate created_at: 2018-12-17 11:23:01.565164000 Z updated_at: 2018-12-17 11:23:01.565164000 Z + footprint: 3089ca765b3962f625e73a07bfce4a8e99a7982eecab6cade10df58b229a5e4c value_history_3: id: 3 @@ -36,6 +38,7 @@ value_history_3: href='http://lacasemate.fr'>Visitez le site de La Casemate

    " created_at: 2018-12-17 11:23:01.569316000 Z updated_at: 2018-12-17 11:23:01.569316000 Z + footprint: 40dc5f63667c1b2f1fab7e7f1830b82a1f23f554af1cba51a8120f01499643a3 value_history_4: id: 4 @@ -44,6 +47,7 @@ value_history_4: value: fablabgrenoble created_at: 2018-12-17 11:23:01.572468000 Z updated_at: 2018-12-17 11:23:01.572468000 Z + footprint: 862996c5053c96cb19dbcd2823aa28e81576684a36caafd920a7131551056f34 value_history_5: id: 5 @@ -55,6 +59,7 @@ value_history_5: Passé ce délais, aucun changement ne pourra être effectué. created_at: 2018-12-17 11:23:01.569316000 Z updated_at: 2018-12-17 11:23:01.578108000 Z + footprint: 7567cd5581d13300cbb874a999f0a0c0318b5ce43766bab6484f392b189c58cc value_history_6: id: 6 @@ -66,6 +71,7 @@ value_history_6: proposé. Passé ce délais, aucun changement ne pourra être effectué. created_at: 2018-12-17 11:23:01.569316000 Z updated_at: 2018-12-17 11:23:01.584331000 Z + footprint: 4a712790f32ab87dcf3d31d5a7bd4d916b95691d982097858ca817d4dc80d9d3 value_history_7: id: 7 @@ -81,6 +87,7 @@ value_history_7: L''équipe du Fab Lab.

    ' created_at: 2018-12-17 11:23:01.588478000 Z updated_at: 2018-12-17 11:23:01.588478000 Z + footprint: 3c469b70afd7613eb70118e9de8bb3b8162d963e907d07c0cbacd0b98ea150e1 value_history_9: id: 9 @@ -89,6 +96,7 @@ value_history_9: value: iVBORw0KGgoAAAANSUhEUgAAAyAAAABNCAYAAABe8gBxAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA/RpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ1dWlkOjVEMjA4OTI0OTNCRkRCMTE5MTRBODU5MEQzMTUwOEM4IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjAzODFDRjYwMEE1RTExRTQ5NzJDRkFDOTI4MTJEOEM1IiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjAzODFDRjVGMEE1RTExRTQ5NzJDRkFDOTI4MTJEOEM1IiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIElsbHVzdHJhdG9yIENTNCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ1dWlkOjI2NzQ5N2UwLTgyODEtNDg4Ny1iOGZlLTExMzA0ODhhZjRhOCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3RDc4OUVFODZFRjBFMzExQjU4NTg3NzUwQzc4MzhDMCIvPiA8ZGM6dGl0bGU+IDxyZGY6QWx0PiA8cmRmOmxpIHhtbDpsYW5nPSJ4LWRlZmF1bHQiPkxBQ0FTRU1BVEUtTE9HTy1WRUNUT1JJU0U8L3JkZjpsaT4gPC9yZGY6QWx0PiA8L2RjOnRpdGxlPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pri35wEAAEzrSURBVHja7F0HeFRF17676b33RnqvJCEEklACBAg1CSSCiihgRxQQBUERBEQpioiiIApIU0TTezZ103vvvfdkN9lk88+Jyffz8dGzd0syL899ErJ3751y5sx5z5w5QxkbGyMwMGY6GAwG0dDYqFhSXGza0dFhkZuXZ8FgMgzz8/O10cfK6JJHlyS6RCa+MoKuQXR1s9nsNlMT03o5ObkSQ0PDfGsrq1wjY+NSNVVVBpVKxY07A9HX30+pq6tTrygvN21qajIrLikxRnKim5uXqz4yMiJLoVCkJmSJgi4WuoZGR0f7NDQ0ug30DZrQPfX2dnZV0tLSFYZGRlVqamqNqioqLNyy/Al2X5/QSGub0mh7h8JQTp48ISQE/Ss83rejo0NCqiq94jZW3Wi27RDR1WFQhIX5sA79xEhzs9zYyKgSMzVdfozFkiUoFFH00SgxNjZEERXtF3ee3U0REuoQ1lDvo0pL818d+lEdmpqlxkahDhkKY8PDUAfxiY+HiVF2v5i9dbeQomIXVUa6S1hVFRtAGBg8AuX1N964zOmHDg8PixkZGWV8tG/f19O58S5cuPAyPS1tubiYGIPsdyGiCJYsc9++fQdm6em1YdGd4kTFZhNVVVVKBQUFTjm5ufNKSkrmtLa1mgwODqqhthYHYo6MREL4KQ0FZDyOPxMgIiIygAzHGm0t7czZDg5R8+fPjzY2Nq5Ffxe4djp+4sT7ZWVlTqKiokwu9ImwhIRE68FPPjmgqKjIEKR2AnlBhEOJTqe7IpKxoLS0zKmjs8MIEVsgryKTjp4nyQDIEMjSuHJG8oe+x5KUlOyWkpJq0NTQRATXINPI0CjdysoqR1tbu0NcXJzUetXV16seQUBlEUMXe7rpASaTKeE6d+7Nbdu23Z3Kc3rv3F3b9Po7P1PExITYvX2iqPOEJsjlGOpENjLeR5DB209QqV2iOtr1wlpaxeJWFulijg6pErPty4SUFEe5Kq8sFsGqrZdjpmdYI8LkOFRQaMtqbjFGxrs6KrvCaHePOCq38EQdxkUT/X1ESF6Ogf7eJayp0SSiplYqZmmeLe7kSJeY55IvrKLMRPdwc9ARIy2t4szcfHNmCt2RkZ5lN9LaajbS2KSJyqGI6iA1UQfqf+owNjZKlZVhIgLYIyQn14qIVIWYuVmOhKtLqvhs+ywRXe0eigDq6ftx48YNr9CwsC1Il3JDh1LQGKKi8XMUjaOSZ/1yS2urwmeffXYU6T1ppF9GiRmCoaEhSQd7++tvv/32vUfd89358yvTMzJeQvbl4HRtB+Gs7KwtJCh1YpQ9qoJ+nbYEpLGxUeLGrZsfDQwMmAoJCXHlnehdxF/3/kp9792dP2MK8XworyhXotHilyckJvggw9odEQ5FMPSQgU1APz5vX97/XWQ0SvX29lrkdORYpGekb/7l1ysMfX392CWeS64s8fQM0tLS6heEtsrLz9P68+6f+1H7jLcRt2TcxcUl1H/jxghBaKPOri7R0NDQxbR42kvFxcVL+vr6lGDVa1KehJ/R0w3ffWDVTGR4eFgF6VSVlpYWu7T0tI1AZhAp6VRXV89wmeMS6uHhHmZpaVUkIS7OcYLAGByUQ3PESxMEZNrpA5A3DQ31MvTrlAjI2PCwPLuzW5EqJ0cgsvFQY5nd3QtLBuqMljZzIiVtSe+tP0BxjIhoaxVIus/7S3ajz3XpJZ6lhBB5q6bM7FzVvsBgr4GQiLXDJWWuoz09alA2MLopoL8m5PURKzRi7J4+WNlRHuosMh7KyXfvCwqFOhCIfFSK29uGyL286ar0ksV0qpwsaSsLrJpaqf7wyIX9fwevY2blLBxp75hFjIxQxokDKgvl8XUgxgaZkmgMKbK7evSHyyvnDETHvUB89wMBhETU2DBG1t/3msz6NREiOtpMQZPnkZER4u69vz5Ac5un6MPkkASgOZTQCNKoRwTk42c2xJlMqeyc7M2o3DIzKVoA2kxWTrYQ/fpIAlJRWWGdkZG+Aen6adsOwmJiYqQ8GAk/ezoLUFJysgcyNkylubgMDQZASkqK/9CO138mq9+mI/r7+4kUeopTYFDQa5mZmevR/5VBOcNFRv9BP8EFfQQXmuwkKisrl3977tvlV369Urts2bKLARv9f9TT02vl53aj0WgrR0dHFbkp42Bcx8fT/PmdgFRVVcncunP7pYSEhLcaGhrMYWWDTHmaJLiT4x4MKPTeJdd/v77k1u1bX+rq6mY4OzvfWLZk6W0rK6t6Tk3mFCqVjd45NinT0w2w2iQsIjLGgU4aN34JKuUxn//7GUUI+lBscmlBeLS93bbn6g3b3t9vfSThPu+e0q53j0kv88wiONSHo13dxEBoxNzuq7/vYCQmr2b3DyhQxETHiRJVVuYZPS3UiTr8a9xSCInxn+z+foP+iOi3+kPD3xQ1M0lQfGPHMbkX/UOoMjKc0QssFjEQFmnSc+3GawPRsf6jHV06QJao4mIEVeoZDbTxPqKM14UiIkxQJP5dRRwbHVFl5uZtZKRlbOw48XWJ7Av+pxV3vnlFkIgIsg8sqqur3UAPcWu8gpMF5tWmpqbDGhoazGfUbaBf2KDbZhIBgZVukSfoHRFhkf/YENMVOED9ORESEuzP7ZAaeF9NTY0b7E/APfBkgHfz5q2bni9veTl830cfpSQlJW1Hf1aWQZMiDGpuKejJFRaYFEZGRnTv3Lnz+eaXXiw89Omhg3V1dXL82nZRUVH+3FZ+0E6FRUUrkIGvwo/t0tPTI/zV11+99vIrW3Jv3bp1rrOz0xzkCUKhuDmBgkyBPgCZQu8WQmTE+caNG6def/ONwm07tv0aGRnpBN5QDAEAGNKICFCkpEQH45P86lb7pdb7vPAtq7JqSrqB3dtLdJ761rPaZUFUw0uvJg1ExbyMhFSBKidLUCB0j5PyCgYkIgJUWVkKq7rWrfmdXcHV85f8MxAZbTil5yIZ7vvrH6taT++rdev9c3r/vLdnbJilA3UYJx6cjD5A7QHtAs8eHWSYdpw6e6HGfWlq14WfFhJswfCnRsfG+CBSzdXVSiAPra2tprR42gI8mDEwASEZRcXFqiWlpSu4tcR5v9HBYrHEAgMDfXAvPB5BQUHOm198MfzEyZMRTc3NS6SkpKgSEhI89+KCkYrKAr8qBQUHf4aISM7Fixf9Ub/yVftlZmZa1zc0uHGbZEP79Pb2qoeEhi7nN5kKDQ112PTiZtrvN25cHBsbmwXGvzCfbCaeJCOoPDL5+QUv7vv4o5TXtm0LRETEZXJPCYYATMiSyIiXkRbuCwp5u2reYnrvzT8cn+c5ff8EW1cvWh7U/MG+CFZj4yJEDJDBLkVwY4/G+MqKvDwxXFbmXeftk972yeENz/McRnqmeq33+u/r/TZlMFLTNlFlZMTHN75zgehDOJqQoiIx0t5u3fzWe1ENm7YeHu3s5OslwJaWFuHk5GRfsveFPUr/xMTGBuARjIEJCMlISEhYyWAwVHhhzIJHOjMr06e7u1sY98T/oqKyUunNt976/tDhT5OaW5qXyKAJix83f08SEWQc6n3/w4Xft+3Y/kdBYaE6v5QvjhbnA5vCefFuIPbJKckb+cWDPzw8TBz69NNd+w8eSOrs7JwLhj6/hguATgIDBAh3UUnRSkREkl55desNOp1uiLWDgAD1IYQusQcGTBs2vRKNDHjvp/3qSEOjeMPGFw83+G1KGy4sXiGkpPjwPSncqAY4fCQk5NuPfnmzdd8ne5/2e6jeRNv+Qy/XLPDKGYiNfx2RJ1EKL+LgYW/Mv2SK0nPj1if1fpuvsbu7RflVbFJSUua3trbacGtP6oN2SW5e7vL8ggJVPIAxMAEhCQwmgwgPD/PnVVweeFwbGxtto2Oi5+Pe+H+Al/fqtaver772alZaetrrUpJSQqKionxfbpgsIISnsLBw/ZtvvZl25487i3hdppaWFlFafDxPPGmTBKSkpGRhCp1uyuu2qK+vF9++Y/uvQcFBp5BMiQlSJjNxsXEiQkFtuXHnrveyPzn4yYdtbW0iWFsICA9BskaVkZZpO3LidsfJM15Pur/vz3uW1QuWxff+ce8TZLCLAQEgeJ1mH5IqyMsRHSdPn2j98MDuJ90+lF+oVLt09a32Y1//QhERVqX+u1rMez2NiNxgXEJA3foXfh3t6uLLlZC4eJo/L8jHpONjiDmkkpiYuBKPXAxMQEhCTk6OeV19vQcvDRFQMjQabSPujX/R1t4uvO+jfV99ferUPyMjIzqCmDUCwsNQ2bWPnzgRevKrk29AJjlegZ5Kd+vs7LTk1WQGYLPZEnG0uHU8JR8N9TK73t91Ly8//0VYrRLUTdhAJBGpkw4OCTn+8itbaIhcWmGtISgzNJUQkpMVbz964re+v4MsH3Vbx1dn/Oo3vpg00tTsCAY/wU+yCis6cnJE+5enT7YfOfHI8OGe3353qPFcmczMyPSjKshzJdTqqYF4HLTrQGzcxqYd7xznNzGpqKhQyMzMXMnLDcvgOIqNiw3g5dyFgQnItEZgYND4Ji9eGxQZmZneJaWlijO9P/Lz81XfefedkKjo6A9kZWUJXhrNUwWsbiHyJHL12rXzBw5+8unQ0BBPyhEUHOzP63aEiTQtLc23t7eXJzqqvr5e8r1du/6orqlZKsOhTD68tQEp4yttXV1dLnv27kk+//35rRBahiEAQGNxjMVSbnl/3wVEMP7L8zU2NEw0vPTa7taPDt6iSkvLUsAA5cfDhSGTm5wsEKXvGEn0WQ9+3Hn2/IrGV3ZEjw0OGlP48IDDfxt7jBBSUICVpr0dJ8+s4qei0RLivXr7+rR5GRoKBAQRIffMrExzPGgxMAHhMDo6OoTQ4PLldVo0MCYGBwe14+Pjl87k/khPTzd6Z+e7MZVVlZ5APqYDoG/l5eWJ6OjoQ/sP7D/CbRJSXl6uVFxc7M3r8DUgY3V1dbPjaLS53H73IINB7Pto34/V1dWQvIAYG5sehyVDPaBfkf6SPn/h+58/P3pkL4EhGHpBQoJgVVfPbz3w2dv/6U9EIBtf2X6w5+rvJ8fT3fJ7GlNEpNhMplrLxweP3J9VqvPsd+tb9nz0J1VaSo6AyAI+H28UpBPavzx1fLiklC88E+BIiIyM9Bfjg5BjpGPEIiIjcZIcDExAOI3UtLT5bW1ttvzgZYcQsMSkRH82mz0j+yItPd1yz4d7I5lMpoWEuMS0MRInAd7q6JiY/Qc/PcRVIzEhMXF5f3+/Oj9ssqbyKNTw1KlTHxYVF2+aTuTjQSIiLSXd7T7fLQprdYHptPGN6b137n7AzMweT1Hd+PL23T037nwGXnlCQMIDYU8HI4m+qe+vf9wmyMeylj0fX6NKSooRArJ6DQccsru6LDrPXdjOD+XJy88zrKioWMQPex7BOZuUlOSL7CQhPGgxMAHhIAKDAv0pVP5Q9DDQCwsLPZEhbjADyYfx3g/3BiLyoScIG82nQkKioqJOXPzppw3ceB940kLDQgP4pU3FkYynpqWuqays5NpZKX/du+f8172/PpXik82vZADOeHlx8+a3lyxZkoG1uiDN1lRibJCh1X3pil/nmXNePTdvnxSCvRKCBkQ0ui5efq3vn2CD1n0HryLyIU4IWOgspAPuvfnHjuGSUp5vOIyj0dawWCxp/uhaIYgUsc3IyMBJcjAwAeEUampqFJDB7y0myj+nUo6MjkjRaLS1M6kfcnJy1Pbs3XMXkY9Z05l8TAI2p//080/fx8bFkX74ZH5+vjGS8wX8kukJVmH6+/t1ExMTPbnxPjRxUn755ZcvUf3Fp+Op34DBwUFi7ty5Z994/Y1rWKsL4IQtgwzf23f3tn169GcheXnBrIOkBMFITfdu2rojhCImqkwI4r49VObRjk7j/iDenlfU09NDxMbGbuBVxsJH6e3QsFB/PFoxMAHhEEJCQ5b19vZq81P+f0izGRsXu6Gzq3NG9EFra6vIF8ePXRlkDFrOBPIxqczRpfjlVyfPw0FTZL7r78B/1rFYLEl+Mr6BDMXF0wK4EQp19do1/5raGg+xaSpbcNilqopqwv6P9++jUrHqF0igsTk2PKyHlIImIcgkeWxMcWxk1IQQ4KQhcEZIX3AYT/c7pGekz2lsapzDLweiAiA6Iys727u6uloBD1gMTECmCDhjIjEpKYDfjF5QOsgon5OWlj5nuvcB7HU5duL4F2VlZcskJSRnlPyBQm9pbvb45ty3u8l6R3dPDyUzI3MDrxMsPAgYcwUFBUuzs7N1yXwPIvHUsLCwnZDCeWwayhAQODSG2j788MPXNDU0cJ5MwfZKEISgr9BB+QWcBFPExYihvIL5rKpqnm1GDwoO9qNSqHwmnlQI89SOjI5aigcrBiYgU0R+QYFBZWXlIn49hCwuLnbDdO+DX678sjo2NnY37IuYjhuDn2Q8wp6EqKiojxMSE0gJxUpPT3dtbmmezU+etH/tFArsTZGBOGcy3xMTHbO4ta11Dr/Vn1Po7+8nXn7ppbfc3dxKsEbHwOAMERzt6tZh5uTxJO1sQ0ODdE5Ozjp+cxoBwFaKjo72HxkZwXKCgQnIlLwMQYFrmEND0vwYFw6xn8kpKWtra2un7a7ZouJilSu//np6Om8MfhpDHBERmQs//HCUjPMbAgMD+ZbEgoxHx0T79/b2kkdA4mIDpmNYEsgNkA/Xua5f7di+4zbW5hgYHMQYm0AEhCcHeyYnJy/q7u424Ee9BQSkpqbGs6ioyAALCQYmIM8JmLzT0tM38GtcOCgfZJgZJCUnL56uffDjjz8cHBgYMOB1+mNYibj/4oUhXlhYuPb69escPQSroaFBNr8gfzU/etIAsCrR3NzskpmZOZuM5yPyLovadQlZ9YfwQQaDMZ596lEXbA4HYgnhnpwEnEqsP0s/5tNDh/bjfR8YGJzHaGubGS/eGxIaEsCvURng+BgaGpJGZVyDJQTjkXM7boLHI44W51xbV+cizcfed1BCwSHBARv8/P6ebkZGWHiYMy0+/nVpLp+OCwQDlo9h4y4Yhcg4HRMVFR1Afx+a+FwKGY7i0N6wT4FboTtAQv64++fHa9asCVZQUOCItRoaFubZ2dU1S4ZfTyD+t72pUdHRfgsWLOB46tjc3Fy7np4ebU7L2ET4GMhOs5Oj4217e/s0NTW1FiRPsAeDij6XQJO0fHFxsXp3T49BdXW1cWdnpymUBcmdMIxrkKvnJd4gtyLCws379n24TVlZeZhP+5Wn72fD+8fGpmfKMwzyDW0RUWKosEib2+8tKirSKikt9eLnZCzg0ElMStrQP9B/WlrqkbqVgnQAhZNOPTIjVThVxgm9M2P1LiYgT01AaH5UPt/wB0qooqJiWWFRoZaVpVXDdGl7IAC/37ixHxliwtwcmAwmk6BSKcN6unppNtY2MaamJhk62jqV6urq3WA8IgVHQYajVFl5mW5+foELPZXu3dTU5IL+LER2OkQwShsaGlz+vHvX79WtW29wor6JSYkBonzqSbufeCWnJK9rbGr8TFNDk8HJZxcUFTqRMWmB/MrKymZ9efzEChsbm+ZH3bdm9Zr/TAhtbW3idXV1hjm5uc4pKSmLqqqrFiBSog1ERExMFE2uT+9ggBPdd+3c+brjbMcKfuxTNpvNQNcA2Ay8KsPoyIgsKsMgnukwntPaJdiDg8rjJ7tz0fkXHBKyCs1T8vzsGAWdBfNiQkKCk9cyr7RH+0lGO9CFVOAYJzaMUNF75cnQKah8TFTOfk48G+kdOdB/PNS93RPtzVPjFhOQxwANHr7d5PUg42cymQpxcXGrEAG5MF3aPyoqagGEBklJckfJQhiMsIhw6wIPj583+G24Ym1tXSL+mL43NzevXL1qdezAwMDxjMxMhxs3buxKS0/zR4RQmKwVEfBqwwWHYr704os3proEX1hYqFtWVraE39Maw0pTd3e3SVJS0kJfH99gTj67orzcmoz+QiSVePutt/c9jnzcP4YBqqqqTHQVzJ49u+CVLVsuI0IiSU9NdQ0JDdmYm5u7lsHoV4azYR63KgLP6uvrI5YtWXp086bN9/ixP2Gs+fj4XPDf6L8P/S7Hq3KgiZgiLy8/gGc7jOefgKlS3MxKBuGcySnJ/oKQLhwcKxGRkRseRUDU1NRaf7jwgzMnDGGkw1mdnZ2aBw8dTEZtJMvJaBBocw93j+tvvvnmW+j3KWc9A70jKyvbz4s+YbFYox9++OFqZCtmozmKpwfIYALyGERGRS1EBoAhZF7idwBJQuX13/LylgvTYbM2GNm/37yxkxv7PuBdaCCy3N3cz7380kvHkMHY9izfh/Z2d3PLRNeLf//zz7ffnf/uXEdHhxMn+wHKCEpQQV6h0me9z3feK1de5UT8b3BoyGowAKX5OPzqvgmGCAkJ9eckAYH9GYhA6nJ6BQQmXnFxcYa1tVXhVBwLiIwMrvL2jkT9HVlSWvphSEiIf3hE+NstLS3mkDL4YeMD5MRA3yDsg/ffP8SvBypCu6soKw/M0tOD0LA2AgNDcMFVJpCYmGhTW1s7DxwRgmCX5OXlrWtpbflUTVXtf4i+qKgo28LcvJ1T70MERAwRjzFOhxiBvpKTk2Xoz5oF4bMCm8Z8ItRtbJberC5DA4M+9Kc+XpYH70p8DBIS4v3J2OQ1MjLSgWSAozHZUM7GxkbXrKwsm+nQ9lHR0Tb5+fkr4bBFsjCxUQ7arvzgJwcXfv3VV+8/K/l4EKtXrUr95dLl+R7uHt9BAgMOeCvGNykjMpOz6YVNr/x25VfrXe+9d8rY2Lh1qs8GL3RqaiopZ3+gcrchGWdzdJYXFSXKysu8ikuK1TkpAxQqVZkk2ZJA7evBqeeZmZp2or4/j2TAfttr215GpLEMZOz+yRaIKmqnug8//HCHsrLyKIExswGyAeFB91+CmMacv+vAVZYfHRvjh8Y8R53HoF+QXQIhxhwNCwIHCSIFhpGRUYvwYJyZ8ooJyHMgOydbMzcvbzmnjTPYlOrq6npaQV6hBFg1h1m6yD+BgX7Tof1jYmM2I6VI2sYEULhggGtqaNIu/nhx3ipv70ROPVtDQ2P45Jdfvu3t7X3weUkIECMIo1FXV6ft2b1n7c3fbzi+v2vXLxqaGhyLV09ITLSvqqqax2mSDXsfFixY8IW4uHgrJz1R0GeIjKkEBQev5NQzW1tbhdrb28XJWGmDfSs/X7r0/YkvT+yqqKyUY3OoLRCxGHrj9dd/vXL5F7tV3qsOILLXD/ICYDAY7B3bt7/uOHt2DdbiMxRoXhlDuo3d00uMDQ2xKGJiLRRR0YrxS0yseWyYxZr4jK+J0xiDMV4Hdn8/myIi0o7KX4muclSHevQ5Y7wOqJ7EDDoXqr2jQzQ7K9uH03YJ6A9zc/MrmpqaNE6f3QG6NS4uNgAPTIwHgUOwHoH4+PhViCwocDo2HlY+vFd6X/mD+YdOS2uLNScVCTwrJzdnfVtb2+cqKirDgtr2dXV1Eunp6aTuvYH0pNra2rFnTp1eraury/FlSIg//fTgoc/Re+QjIiLel5WVfarME1AuRCRHbG1sg5ctW/r1iuUraGSF1NFocX4UCmeP0YU6CgsLN69fu+5SdXX1cnSpc5LggEyk0un+zKGhn8U5IB9ojFPAKUBGqBI8E8mBzO07d04FBgXtMzUxjbOxsYm0tbVNNjMzK1NTVZ3SUr6amtrgp4cOHfXyWnbn61Onvs/Pz1+4bu3azzZv2hxMYMxI4oGMdYIqLdMkucD9b+kVy8LFLM3zxYwM28YmPNsUISHx4Zpa1cGEJMe+P//exMzO9aJKSVIJHqc4/69qoDpQhIX7JFzmhEotcg8Ss7HOFLcwa0KKZQApGDaqg+hoV5fSYEqaVX9giM9AdCw43aQoEJI0zclITHS0B7IbzDkdMstCpMPPx/dWQWHhnOu/X1/GyT1xoLMLi4q8cnNzNZH+a8QDFQMTkMcAYqgTk5I2cpp8gGdBXV09x23+/Pra2troxKTEHZw0ssHT0N7RYZGUlOSxZs2aCEFtf0Si5nZ2dhqRtS9hIkyl5LNDnwaQQT7uN0B3f7B7b0VFhW19ff3iR/U1rIQB8QDRs7ezv7V58+bT8+fNyyEzpXJra6tEZmbmejJW+MzNzJIdHR177e3to0pLS5dykoDAxFhTW+uelZlpOXfu3IKpPk9LS2tUU1NzGPURQcZGfJABIJCImKnmF+T7ZWVn+aFxypaXl6/R1NDMNTDQTzcxMck2MjIq1tPVq1NSUhp61n53meNS8sOFH1ZeunxpzeYXNgVhDT4DucfgIEERFW1UeOv14wrbXrmCiMejTu1kCuvqdEu6zStV3v3e9c5zP3i2HTn289jwsC5FFHQBjwx4OGh1aIhA5RiQ9Vv/ndLOt74Vd7CrfwQxGkJ16BOztalW2PFq4EBY5PHmXXt/Gq6qnk8FZ800JiGxcXH+nE6YAfOPlKRko4ODQwaFSh3kfNeORxsoxMTGrEIE5Ac8WjEwAXkM6Kl0a2SQuMEmT04CljmdnJzCwMBwnTs34aeff+pCg1+Bk4amEHpWcGiIvyATkMTEJC8yjW/YcP7poUPbra2tm8mui4qy8uh7O3fu3LN3Lx3ODrnf0w6rBRAGJi4u3rls6bJfVqxY8d0cZ+dKbmy8R5OBR3NLiymnEyzAnpX58+ePG8FOjo4RN2/dOk5wMOZ0IlZZNCgk2IcTBAQ9D6yVHvLtK8q4J3CC8FFRv+sXlxTrI1KyBuQAkZ9hWVnZOg0NjRIDff1sY2OTdGNj43z9WbOqFRUVWU9aoVFUUGDsfv+DGwTGzAKSHXZvL4EIxTW1UyfeE7e3ffoNvSIihOKutyNFzU09G198NXqMxdLmyUoIpLJFdRDR0kxS/fLoNkRAnilxg9QyzxLd8H+8ar3WBLKqqhdQxMWnZVcXFhaq5OblruC00wh0tqWlZZyKisqQg719gbKyclF/f785J+chCEelxcf7b9269QcZaRk8bnmsNXjnacAE5Mlehtg4X7Laxt7OLgR+6unpNWppaiVVVVet5KTnFZ5VUlKyory8XMXIyEjgssuAQV5aWuJB1gmvsLrlOtf125UrVtK4Vaf58+YXIGP5fHx8/B7whsNKGJRDQlKyfrnX8gubN236ydTUtIWb7UyjxZNyii565oCtrV00/G5tZZ2LCFhuX1+fLScnM5iAs7KyfDo6Oo4pKSmxpma/QVYQdgu35RzaA677jAlRJPuGaOwaFhQUrACvpLCw8DCqX5Wujk6WkZFhkp2dQ4qFuXmRmppavxAfhcw8DyCDT2hY2Jq8vDzN0dFRnuQTReNQHBG+1AP7D5wUyANcYa8HgzEmv+Wl3WrfnDxFfU6HmbTXkjLFd998r+2zo3eoctzPiMzu6SGkFi/8Wf38mbdEDfSfa2OKiLbWgNqXR1+v3/hiGhrQMgRl+p0tGUejLUc6Qp3j4VeIgMxzdQ2E3xH5YNnZ2UVGRUWZc9IBC3NNTU3NvLS0dOtFCxfmYSuTNwBHFuoLobNnzxyXkZHpQPMMTyYSSP+7Y/uOU5iAPICmpibRxKREX057GSDsR0lJsXi2w+zxk5xhGdXZ2SmopLSEowQEJlJk8KmHhIZ6vfP2278JWvtXVlbqNLe0mJNxLsOEp7ll22uvfc3tem1+YdOFxMTE13t6emTUVNWKXwgI+BaRj6v6+vq93C4LMnBVs3Oyl5PhSdPR0aFbWlhUwf+R8TyKCHdYRGSkLScnM5CN5uZmm+iY6Hl+vn6xUx0verp61aWlZQSvz0KBskAZ7iuHaG9vr2l2To5pekaG/81bt8fQpNGop6ubbmVpFenu4R5hZWVVIiEuIWjDfLwPGxsbrWtra615VQYIFzQ0MJRBeuGkwDUgEGdUfpVP97+mtG/3pak+Tn7blj+6f72WMtLS6kLh4qGk7EEGIbN65Rmt3y7tokhOTY6lVywrQUTman9oxBtUKUliOmFkdIRISk7yJ2FPKjgDuue6zI2b/Ju9rV1wRETEOyRUQyQ+nuaHCQjPSQilqLjYm9NJkJ4F4ID19fH9ExOQB0BPTXXv6uqy4PTGX5js5s6dG6mmpvYfD4+bm3vkrdu3mUgJiHNyEywoqRR6iv/rO3b8JsLnJ1w/iKrqanPm0JAMGSe8gtAvXbr0V2S0cX0jnL29feXixYvPqigr12zetPk3WO7mVRvTEuJXMplMFU570kDGnZ2dg+/PT+/qOi84PCJiL6frACsA8fEJ/lMlIABDQ6OcsPBwvhwPD6yUUBDJ00KTh1Zefv6aW3dusxDhS5vj7HzLc7HnbSRjArPBcyJZAUHWgZ1PS/jExEQFcsMAe2CAkHvxhS84QT7GCaG6OiG1ZHFg90+XuUZAgECJmRiFal66MGXyMQmZlV5/9QeFvDHd7JLU1DTT0tLSBeIcDi+D1Xhtbe0UfX39hsm/Oc9xTpaRkakbHR3V4eTKIJQ9OSVlfXNz8xF1dfVhAoNn4LWzDfQ/Fc1rOA3vA4iOjvanCnG+WWAFxGWOy39lp7G0sCjT0NDI4HTaOyAdVVVVC7Ozs00Frf2RkrUiaxMhInkjXsu8rvHI60B8ceToJ7ve2/UTL8kHhLhFREQEkJFhDE1W7LkuLv+198jKyjINTWaVnPa2QPnzC/K9KysrFadOQAyzoOyCMD7+NZrFxje2o58icPbPjZs3z7z59lvFb73z9uWg4GAHWInCmOYYZRPCKirlnHykpKtLFndZFJugyshUczLsS8LFqYgqJztAsNnTqrtjY2PWIRuC40udsC8V2SUh9xMb/Vn6Pebm5rHgUOK0M6W1rc0SkRB3PIAxxucz3AT3Gb9lpUpZ2VkcP/wOjC9paelmO1vblAc9AtZW1uGcHuhg7KJnSoSEhq4TtD6ob6g3JyO+HUielpZWGuqDnJks49k52eYNDQ3unF4ZA4KNiFWuhblF/v1/n6U3axD9LY7TMg6GeHd3t1ZEVOTSqT7L1tamUFVVNR/qIEiYiOcF3QI/ZdLS0rYcPHQwbcvWV/4MCQ2xxxp92oOjipIiLtYq6HUQUlDopYiKdU+nTFjt7e3UhIQEX06vfkwmv1jg4RH54GfOTs5BZDgyRISFibDwMH88dDEwAXkAcXG0FbDJi9NnAoynJjU3jzM0NOx68LOFCxaEkrEBEryk6Rnpfl1dXQK1WxUpW10yCAj0ga2NTQynsz4JGqJjYnyQoc3x5Q/wpDnY24crKir+j+vRw909cIQE4x5kPDEhMWCqE6WCvMKIra3tvYlUyAIJ0FkQ+obICLWiomLdJwcPpr6z893zJaWlygQGxtNhZBrUYXTimjag0+muLW2tszk9L4LDRVlZOdPIyOh/so7NdnBIQISnZ4zDRG585To/f0VJaQnWSxiYgNxvQEVERgSQERsH3ndrK6vAh31mZmaWJS8nV8Bp7yvEVjc0NDjE0WgugtIHTNQHiCiok3EoHHuMTSAjM3Umy3hbW5tQfHy8nzhJaSrnz5//0APwrK2tEyTExds4PZnBWC0uLV6cnpFhMNVneS1bdg1N8IPToZ8nQrSEU1JS3ti+Y3vGr7/+umxsBp0WjTGjMe3SX8XR4jZQKZw31cApZ29nFwqH5D4IS0vLBgMDgyROr4LA3M5gMDQSExOXY1HFwARkAnn5+Sa1dXUcT/8KEz8yCHrmuc6LfdjnampqLEsrK46HYQHGN+omxG8UlD6Aw/FaWlpkyFgBEREWYaO2Lp/JMk6n0+d3dHTYcLp9IcRQRkam3MLcIu1hn5uYmLQa6BvEk7Gkzx5lS8XH06Ycauju5l6CJuNfIVHBdAFkHhsdHdU9883ZkMOfH97b29tLYGBgCA6qqqrkUtPS1pDhNAIy4OHhEfoo28FljksQGXYJOI4iIiP9wemLgQkIBkJkZOS6ERZLktPedzC69PX1ky0tLesfY/wEk5ESDTyhOTk5a2pqa2QFQhgpFAnU/hzPnzh+0quUVI/+LP32mSzjQcFB/mSQO5hILC0sonV0dAYfNZm5urqSElMMMp6ckrKhp2fqZwlu377jKJocG9nTaAMrtD0ih5S7f/11YvfePacxCcHAEBwkJCZ69vX16XI6TBsiLhQUFIptbW0fmXgAfRaJ9AfHWQIQkMrKyoW5ebkmuIcxAZnx6O7upsQnxPuRkRno32VO+9DHraw4zp6dCmnvOG34gNLq7OzUjY6JWSIgXSEycZGBPnQNzlQZr6quUigqKfEmI8QQ5NbD3SPkcfe4zJkTi8bAIKdDgSDUsLa21jkpOWnOVJ/lYG9fH+Af8H5/f/+06385OTkiPSPjvb37PjxFhlcTAwODs4DQ7bDwMFLCwsf3pZqZRykrKT9SGdjZ2ZVoampyPEvnOAFisyWCQ0LW4V7GBGTGIzkl2bW1tXU2GTnphYSERpycnCIfd4+Ojk6voYFhHBkeYlBeNBrtBQHJ8CNEcDgryn2Axh2ZqTIeF0db1tfXp81pT9rEQVZt9vb2iY+7z9jEpFJDQzOdjMkMvPyIZHMk1PCNN9646enp+WVvXy9BmWanKctISxOpaWm7zn7zzftY62Ng8Dfy8/P1Kiorl5Bxltcoe5Rwd3cPepK+cHZ2DiMjVEoM2SV0Ot2vq7sb26CYgMxshISEbiDD2ABjS1NTM3O2g0PBk+6dP39+EFkEpKioaEl2drauAHQFe+Iii9zMSHkHOQyPCPcXJWEiA0+akaFRvIGBQdvj7pOSlCTmODsHk+F9h5XLtLS0tdU1NVM+vZKK9MCe3Xv2mZuZ/w4rIdONhEhLSxHXrl87cf36dU+s+TEw+Be0+PjVw0NDspzWQbBiLS8nX+/s5JT8pHvRPWFk1A2cvW1tbbPT09JccE9jAjJj0dDQIFdQWLCGjPCrf1OTOoQ+zYnTrnPnxklJSXVyOkQFlBciNjLxCQlrBMFWJv5dqSADYJxKzEQZz83NNaiurl5MhicNSLOLi0vg00yS8+fPCxMSEuI4wYRVnb6+Pv2U5GSOGNWqKipjZ06d3mJqYnJ7uu2ZoKB/SB8J/3Tp53Nl5eXyeArEwOA/IH1GREVHbSRj8znobD1dvTgtLa3uJ91ra2ObpaysXERGBAXMGUHBwRtxb2MCMmMRExOzuKenR4+MszjGB7CtbcjT3Kenp9ekoaGRREaICpCrhIT4jf39fXzdF2w2mwkXpz0+8DxEBuVaW1vlZqKMIyW/Znh4WJoMb76wsHC/ra1NzNPca2pimqugoJhDxmQG5ApN2AGc2kelqqo6fOrrUwFz5sw5Bysh0ymNLYSs9fT2mP7008VdeArEwOA/ZGRmODQ1NbmSERYOBGTePNegp5kPVFRUhm1sbCLIWrnOzctdU19fL4t7HBOQGQcwKiKjIgPIGORgZCkqKpbMcXbOfNrB6DJnThAZ8ZZgnFXX1Mylp6bO5uf+kJOTG1SQV+jntIE6kXtctLGp0WCmyXgfIp1oMttAxgofTGQ6OjppNtY21U9zv7KyMtvOzjacDBmHUMOCwsKlBQUF2px6ppqa2ui3Z795Z4Pfhq1MJrNXkA8qfBBSklKQYWdHTm6OGp4GMTD4C4GBgRsIEs40AZtHSkqq122+W/zTfmeeq2sQGU4jcPr29PbohYWH43BQTEBmHnJyc3XLKyqWkJVlws7WNlpdXf2pXQce7h4xyFAcJsPbioxwalRUtB+fExC2nJxsGxlpUIGEFBUVOcw0Gaen0J0bGxtdyCDZIOOINIfCeRNPiwUeHqTEFEP/ovIoREVHr+Lkc6HdPty79/LZM2dnm5qa/g0hWdMhixS0FyJUauHh4evxNIiBwT9oamoSR7bJejLsEnAaaWlpJRsYGNQ/7XesraxTyMjSCRARFoEkOQH4oFRMQGYcwsLCVjMYDDmyNqA7OMwOepbvmJqZlqiqqmaQ4W2AWFI6nb6uoaGBr/dBiEtI1JGhjGAVKCs7ewEZIW78jJDQ0A2kKXcKhe042zH8Wb5iY2OTJisrW0XGZAYTdmxs7EYy0ujOdXEp/+H7C2sO7N/vpa2tnQwx2oJORMY376enr8UHgmFg8A9S6PRFXV1dxmSc2QQ6y8nRMehZHFKIrPSamZrFk3UoYUVlxdLCokId3POYgMwYgAFBT6VvJCM0BYwrZGQ1Ojs7JT3L9yAswt7ePpgMgwCWO7t7uk3oqakL+blfDA0My8jIBgYEpKKiYm5BQcGMUXT19fXSuXm5a8mQcSDJaqqqOba2trnP8j1NDc1+czOzaDImM+jjxubGeTm5ObZktKeEhAThs94n7Lcrv87f/9HHy3W0dUIHBwdZ6CIEJM31fwGMkKamJsfqmmochoWBwScICgokJSwcHFFIRw7Nnzc/4lm/6+HuHkSG8248PJrJlA0OCVmDex4TkBmDxKREh9ra2nkiJKUmNTc3DzfQN+h69oHu8beQkBAp1gwotfCI8AB+7hddXZ18Mp47uRE9JjaWZ4cfXb12dU1sXKwVt5abI6OjFnZ0dBiSdfr5bAeHewoKCs+8lOHm5v4HWQb7GHtMODQ0zJfMdhUXF2f7+PiE/nL58vKzZ87arVix4igiJ0UDAwNj6Bof/4JwmvpEGJZiTU2NGZ4KMTB4j9LSUvXSsjIvMsKvQOeqqqrSTUxMip/1u3AquqSkZBsZcxecCZKcnOIPjhyMmQXhmVrx+Ph4PwpJSf7HPbGNjXrv7tx5DA36p3Y/U6lUNoPBEENgoP9Kc3ygi4nB4UZexSXFGmamZk382C8GBgaFqP1gCYTjzBA82CGhIds3bthwUUNDg8HNeiEjT+b7Cxe+QYa7lsucOVf9fP3OOjk5ZT3L/olnRVJiUgAZBHtSlgqLiuyRjB9HMi76DDI+2tvbK4/6AhiIEBnloqfS17e2th5Bky2psUXQd65z5xai60BbR9tnGWkZdtk52UsKCgsX1tXX2/b39alMeB3HyT+sQvLbuSJAlCorK03Rr3HcfjeSG+bIyAhYHTxpFEQUpdA1/Y69xxBYBAUHew8MDCg/Ter+ZwXoHzT/yB745JNjSC89tV0COguN1VERUZHRERbnV0FAP9bV185NSEhwWLp0aSaWAvLBYrF6ke7n2bI9kkMp9PrhGUlAEDmQSM/IWE9GaAoAPM6dnZ0Lm5ubFz6PkiDD+zGpSJhMpnJUVNQKREB+5se+MTY2rlRSUqro6ekx47TnHp7X3tFhefXa1Vf27N5znpv1unHzpi9qe12YWNLS019OpqdsNjQwDA3w9z/ttcwrBhnkHHWZFxQUaBYVF3mRJeNgUCP5XlNXV7fmefqBLGI0MfYsaDSau6+vbwQnntnf3y8SR4uzWeK5JAuNzYf2k4qSCsvLyysNLqTcv2hqblYoKMi3KC8vn4P6wrm2rs62u7tbHxm8YpPtB2WFi5ekBPRNW1u7Hrffy2AwiNWrVp9fv379PtiHx4u6IyOMIi4uPkRWCnYMjGc0yogUeoo/WTob5ByNNbu09DS757EdoFyk6aoxghodG+OHCQj5GBkZGd313i5vMzOzLDQfifOiDIj8UAwNDXtnJAGJo9EWtLW1mcjIyJA6sZNxiNBUAeQmOibWf+vWV3+W4MPyKcgrDBsYGCTT6XQzWLHgNOBE7rv37h1ctmxZkI21TQ036lRZVSUbFh62d3K1Y0IuhJDxvvLI0aMrr/9+I2XxwoWn1/usv6eqwhmvfXhkxKqBwQEFGWlyZBw8+5MGNL8ByhQWEeHPKQJy+84d7xNfnvhzy5Yt+9/bufMLcbHHjxsgV7o6Ol3oSkT/TYRVBkSoRVB/a5dXlJuXlpbaVVVXWyMdZNbe3q43MDCgAO0JOgOIyeRqCbcISH1DvSK3+wjCQTQ01ActzM1htbMdmwUYMx05ubmWNbW1bqIkOWcmiQQ/2iVAbnJycta3tbd9pqKswsTSQJrTBYz/MRMTky5rKytY/eXpCvCMJCCxcbGkhabwO6DeDQ317unpaZZu890K+LGMrnNdQ5OSkl4hSwGzR0fVjh0/fu7HCz+sQSSUTfaAP/vN2U/6+vrMHgy3AjIIF+oPlx9/unjz7r2/ilevWn1u9erVV3W0tXue952wDyE5Kdn/SYbydAW0aVlZ6cqqqioVfX39tqk8q6CwUOOXK1dOKysrE7du3TpaUVFh/Pnhz99WV1MbeBYjX0FBgYWuKhsbmyr0p2D4e39/P7WxsVGpobFBHz3Xoqys3KGmpsa+qbnJAvWh4uTETMaG1AcImxSemjEweIt/Av/xGWGxRMVIioDgZ4DTqLW11SQqKnqh/8aNIVgaSAXfxAHPuLXnvPw8tby8vOVkLXMKAkZGRkRDQkL5Nv8/MtJiJSQkWsnarA19X15e7v3Z4c9OkZ2C9MeLF/3j4+N3P26vB5BCWI1DRqfZpcuXzm3avKlw/4EDnxaXFGs+zzvpdLp1ZVXl/JlKssHgR8a9Wlh4+PKpPAfS+X5+5PMzTCZDDyZI6KOcnJwtL295OTEkNGTKh3pKS0uzTUxM2hYuWJj62quv/XLi+PF3r1696nb50mWTQwcPLVq7Zu2nSkpKGRCuRHLiAgqBgYHBM3R1dQlnZWX5zGS7BBwtcXFx/lgaZtBcPdMqnJiYuBIZncr8thmUm4Al2JzcHF+k9PjSQrW0sGhFJCSMTHIAhCA6Jmbnx/v3f0XWe27eurX08i+Xf37aDYVg5E7cqxkWHnZo2/bthW+9/fZ3sXFx5s+SVSk+IR4OnJyxCSYmSV0cLc6fNYXUkT9c/HF7aWnphvtDFiAssK+vz/azw4cT9n9y4CDsJ+PoJIxkwEBfv2OVt3fMgf37P7t+7brjq1tf9UUy2k1WWyFywyIwMDB4hqjoaLempiYbslc7+RlAvvLy85bnF+TjtOCYgEw/gCcRGXMBojNwifNBQ7e1tdUmjkabx69ldHdz/5WM80DuB3i0afG0D7bt2H6roqJCgVPPhfj2K1euBJw6feoumlAkn5Xswv1SUlLwUy4tPe3NPXv3ZO144/Wb4RERc+H8msehpbVFhJ6a6sOPcb7cJiDV1dUL09JSTZ7n+zExMeZ37tw5Dv3wsGcjHSIeGhr62UtbXk779bffVjGZ5IQtS6P379i+/Y8Af//DZKWpRPXpITAwMHgGNA9t5Mf9dNzE+JkgDIZKXBxtJZYITECmijF+q2xmZqYFMjTdZjoBmRzsERERfLvc6bVsWZSOjk4K2SQEVkKKi4v9tr++I/3q1aurp3qKNpIvmbffeefsN+e+vS4mJiY5lUkF+gg87qiMYnl5eRs++vijxJe3bIm6cePGSlTOh7rKEhMSPZpbWizwZEaBVIPiSMafOdSwrb1d+NTp09+hXxUetRkcng+rVYh4WH7z7Td/b37pxag7d/5YQBZJ0NOb1UDG2SJwuJi5mRneBI6BwSPU1NYoFhQUeM/k8KtJgG2WlJzkT8ahhxj8B9LW+8bYbAoIEZo0qZDukBuVERERGX1c9piIyEgf9AOPcuLf5c6CwoJVZWVlHxsbG3fyW/nk5eXH1q1dd/6bc9+4kL2XAYz8oaEhg9Nnz9z7488/I319fb+EEBhZWdmn1oLt7e0Sd/780//WrZsH+vr6DDidxx36C12U5pbmRV+cOL4oKib69x8vXHiBQvlveUd/9xcRFsYCPtFmmVlZG1B/nJSRkXnqnOffnvv2o8amxoVP04dA9GCVpKGhYdGxE8cW3bpzK3blipXfLFmyJFSTg2fNxMRELyUrPAONrype9M/IyOi48KKxx1O2DOcvoTYYwyMGgxcICwtf2tXdpUVWxkJBIyDl5eXudDrdYt68eYVYOsgBa3iYL3SvMFlCVFlV5er/QkAuwZ0NjsLDw8Ndx744ttrSwuKh3rzW1lZhxKx9sJfhP5Mu0dvbqxkZFbUUEZAb/FjGdWvX3rr7191dbW1t9mSTkMn9Fy2tLZ6nzpz2/P3G7wWOsx3vOTs7RRsZGhUrKyu3QiYj8EJPeNeJpqYm+aLiIovY2LiV2TnZAYiE6EtIjq9YkEmyx/cJODk6xT9IPopLSpRzcnJWYhmfUArIYG9sbLSPo8XN9V7pnfA037l9545bcHDwgYeFXj1J58FVV1+/4Ow3Zxf8dvW3Slsb27seHh7/2NvZZWhqavY/z6pUZ2en0Lnvvns/LT39VTLC6qBMZqZm5dzuG2jfv+79tS0yKnLl2NgYzyZBBoMhs3Dhwq92v//BOTxiMLgNmE+QXRIgKoKjMiYBhzfHxMauxwSE8wDbBdkQQp8fPfIHmq8Y3Foc4CoBmTDQpNHkb82NSkCGGPS+7uGhoUfWJz0jYz6azG2f1bCY7t6GFHrKC6/v2HGDHzfly8nJDa1ft/7YmbNnbnEroxO8B66uri7LoOAgy8CgwI+R4dcnLy/fgsrTjiaMftRWsLqn0NLSoj04OKgK3wOjn4zTax8ErCpqaWnl+vn6Xn7ws+TkpOXIoFLnRjkECdHRMRufhoDAeS0//PjDd6gvRZ93PEAOf7iYTKZBfEL8B4j8fCAjI9Ogra2daWxklGFubp6PZKnK0MCwFY2/fiRPg4iwshDBHUNEmwonxHZ3dytVVFaYpKWleSC9ta6jo8OcjDNxQG+iuraoqKhwnYBA+/b396v09PSo8FI2IGV1V2cX3vSKwROkpqUaFhUVLZ7pe/buB7RFQmKCL9KHx5FuwrFYJOje9vZ2I5IzK/KOgNzHtLhSicmGpFAfbTSEhoVupArhE28fJCBlZWWL09LTDZydnCr5sYy+Pj63Ud+FVFVVcTV18uSBcBPyJYMIiQwyBI3ul2/4nMzVjocB9hi8unXrFwoKCv+163loeJiIjIoKwPub/ncyy87JXlNZWbnfwMCg91H3wYrWqdOnjiGD2JoTfQorC5OkAZFGrfLyci1kaKz6+59/xv+GSOIAkqFeJFsDQEDU1dXZzc3NQqh/pdH9yohIisMqJcg8WTIGZFZbSyt71qxZPAnBhPrx+hRyGC9CwkI4/AqDJ6DRaGtHR0exV/QB3dnW3mabkpIyf9WqVbG4Rcixb/gBM8IiR8arYk5OjreYKA5NeZAkDg0NSUZFRa3j1zLCitVrW1/9CCnpAV4x9kmyMRlmAxeQa26vGkG6YGNj41BfX7+bD36Wm5NjgozcBZiA/K+R293drRNHi1v6uPt+vnRpfWJi4ptkGPtQBugXkOXJC4wORAA00E+j3t5e88KiIkv00wz9XxvdLw6rWFAWMpMJAGl1cnIOwyF7GBjcR19fHyWFTt+AdfbDdKYQEUejbcAtMc37eSZUEvY5DAwMaPPa28aPAA9xckryhq6uLr4t4+LFi3NWrFhxGDJUzdTzW4B8IeOU8erWrR/LyvzvZsWwiPB16HMJLNH/C5jgE5OSAh71eV5enta169fOcDN0bdL7DxeQDDj9GH7C/7kl48JCQkx3N7dALCEYGNxHQmKic21trfNMPTD2sXaJmBiRlp62uqKyQhG3BiYgAgsIrYiGzEB4kD/cCBEWhgw+zknJyXP4uZzvvv3OSUNDw2A4y2WmAQxSiFVfunTp8WVLl2U9+HlnVyc1KSnJD3uyH01AioqKPLOysvQe/Ky3t5c4dvz4WaQndGaSgwJW00xNTUMdHR3LsIRgYHAfcbS4jTP5QOTHGqZIF/f392slJCQuwa2BCYjAorCoyLCmpmYxJiCPH+zxCfEb+bmMSkpKYx/t27dDUkqqcqblCAdjUU1dPQmRsBMP+zwzI3NuW1vbbGGcfveRBI7JZMrGxsWuefCz7y9ceKOouGjGZccDmfJc7Hlupp8Xg4HBCyCbRJpOp6/Fm88fDXAchYWHBZB9FhgGJiCkISQkZM3w8LA09jQ8GqAE09PT19bW1vJ1+iQHe4f6Xe/u3MxgMPvIOJSNHwGhV+jq2bt795tqampDD7snODQEe9KeQsZpNNrGnp7/P/SbFh9vdfevu8dkZGZW/n04td3CwiLQ18cnCksGBgb3kZSctLi3t1cfh4U/GuA0rqqq8szLz9PHrYEJiMChr78fBvoGHJryBCFASrCzs1M/PiFhMb+XdfXq1cm73nsPkRAGayaQEAgReunFl95Z4LEg52Gf19fXy2ZlZa3GMv54wOpQXX29S3pGuiP8v6amRvLoF0cvItmXm0nkbYLQMl979bVD3M7ghoGBMX7OBREcEhIgIoqjMh4H0MvDw8NSNFr8WtwamIAIHBLi452bm5vn4NCUJwOWO6OiogJAOfI7Xty8+e+d7+58CZGQkelMQvr6+mAD/mevvfrqb4+6JzIqyhPdp4c9aU9FtKmhYWG+8HtrW5smIneGYJDPJAICe4lWeXsfW7hgQSaWCAwM7qOwsFCrsrJyGT588MmAleuo6Kj/WrnGmD6Y1pZ5RGTEBrJTtyKGPoLewRWrXVhEWFSIKkSKtTS+Ube4aGl+Qb62rY1tvQCQkBuQRfj02TNXxcXEJacbyQTysWjhwm+PHjn66aPSNAL5io6JDiC77kPDQyxijOAG06OIiIiIkkWmYJUoIyNjXX1Dw2EnR8fyr09+Ne+L48duNLc0O0hJTu9U/BT0j8FkENra2mFvvvHmUTz1YWDwBrFxcauZTKY8mVn3kF0yiuwSrmyWFBISEhERFqGOEZy3tWBua2lpmZORmeG8aOGiVCw9mIAIBJBRIZ1fULCOzNAUBpNJvPrKKxvd3d3TkEIhbTcZMsjGWCMjQ8ePHbvU2NzsKUKCwTmx3KmQkJCwChGQ7wWhjzdv2nwXGefLz50/f2NoaEgDvCX8cLonR8jHokXfHP38yM7HyW9WdpZuRUXFUjLzyLNYrP5d7+1aaW1lVYPamLQXoUmM3dPTQz36xRd/9vf3W5GxORqIDXqHSUpKykJfH58gV1fXsgvnv3c/9Nmn32RnZ2+F8zmm62oIIpGEjIxM4aeHDm1RUlIaJTAwMLgOSCUfFR3lT6pdwmAQvr6+769eteoO+p1Uzwqae/rPf//9kfT09K1kbqgPDAzciAkIJiACg8jIqEWdnZ0GYFSQAQhVkpOVLVmzek2Qurr6EDfqhAymu9euX/cUIclzAkoxOiZm48svvfw9N89EmAo2+G2gGRoazjt8+PDV+oYGV0Ep98MA5An2fCzx9Dx05PMjh580SYWHR4AnTZasOkP2EQ0NjbRVK71p3NovYGtrGxIdHW1F1vtgY2NYWJi/z/r1QUA2tLW1By58//2rl3/5JfrSpUtn2Gy28uQJ5tMBExnA4OT1ypNfnlxrb2fXjKc9DAzeIDMry7apqcmVLGMd5hCk47pXLl/xh5mpWSM36uS1bNltOp2+laznwzyYm5e3trGx8aCmpuYAlqLpg2kbOB4bFxtAZorJ4eFhwsbGJpJb5APg5uYeKSoqyiTLyw/GWW1t7bys7CxbQerr2Q6zq366+NOihQsXnuwfGGALYppeMPaHhob6t2/b9uLRI0efSD7Ay5WekU6qJw1k3NnJOZibm5U93NyDyHz+RKihV0lJicZ/5F5YhNj+2rZr354962BhYXEbVqBGRgU/1TOQj8HBQUJaSir3qy9PLkXkA5/5gYHBQwQG/uPLZrNJc/zC3KepoUk3MDBo4FadLC0sk6WkpOrI2o8JK9ddXV0G4RHhi7EEYQLC98jJydEsLi72ItM4gxUQlzkuwdysl5WlZamGhkYmyQa2cEhIiK+g9bmKisoQMrL2Hvrk4EJpaek8WOoWhHAsKCOUVU5OLhOVf96bb7x59WlCqhISEx3q6upcyTzfBhmw7LlzXSK42R4ODg5pqC2qyJrMJlYElEPDQlc++JmTk3PdxR9+3LBn956V0lLSeb19vYQgJGV4lFz19PYQ+vr6f505fWaBnZ1dBZ7uMDB4h9bWVrGc3FxSzxyCUEvnOc4h3FzFRWSnx9TUNA4cVmQB5jlafHwAliJMQPge0THRqwcZg/JkxXOzx9gEYvxNdra2ydysFygVK0ursGEWeQMdlGNaevr6ltYWgczrunrVKtqvv1xxXr9u3e7hYVYbeID5lYjAYXBM5hDDa5nXYVTmeW5ubrlP+92oqCg/VC/SNiyA4a2srJxnYW6Rx802UVdXHzQ2Mo4lczIDghefkLAR9nA9bKIL8PcPvvzzJcfNL2zejsZDBRBEQVpVg5Ar1H7969es2/nD9xfWW1lZdeGpDgODt0hMSvLo6OgwJysyYzz8SliEtcBjQQS36+Y+3y2ITGcN6OyysrKlxSXFWliSMAHhSwhRhcYtTTqd7i8mSmJoytAwgRg/zdjYmOsT+4IFHqGQ0Ya0NkTKsaury4JOT3UXVDlQU1Nj7v94/9c/XbxotWjRoqPIeGwHI5JfvNlAPFB5xgwNDP48e/r07KNHjhxSUVFhPu336+rrxTMyM9aT6klDZbS3tw9FJITreY7d3d2CyDT4gWTU19d75OTkWD7qHk1NzeEP3n//4m9XfrV5ISBgm5ysbD6ksIV24VdA2VAZx8zNzW+fP/ed/SeffPKNvLy84GdlwMCYBggJCfEnMyx8wmmUYWxsVMjtujk6Osaj+aiHLGffRDipYlBQ8CosSZiA8CXQAGAi8mFWVV09j8zMQGAczXN1DeRFHZFRmIUM7HwyDTRQkqAsBV0erCwtW08cO37gxws/WGzcsGG3nJxcIRiRsH+C2+eHwPtgNQa9n2VkZPTX54cPz7t86bLP3Llzi571WTQabWFHR4cJmZMZTCNu892CedFvLnNc4mVkZNrI7CP0bJHg4CCfJ92HiMjgB+9/8NON3284oJ8rTE1N/2IymQwgtLBvh9cAowPkCl3DiHjcPX7s2Lyffry4wcHBoRxPbxgY/IHSsjLVktKSFWTaJbBqbGdrGyovJ8/1+qE5rWGWnl4SmToRHG70VPpGJpOJBWqaQJifPXpPbSwh1g2CjyZjVmh42AtoMhYmK/xq4uCyHhsb21he1FVBXoGlr68fUVdXZ0VWnCfk887MzFxTUlp6wNTEpEngiYiVVRu6vt6+fcc3MTHR7jExsZsKCguWdXd3a8IGN/CIQ75xTssMGIcgl3ApKCjUz3Odd3vVKu+fZ8+eXSAu9vxZUKKjo18C45yssQvPlhAXL7e0sEjjRX/p6uq2amhoxBeXFK8ncyUzITHRv62t7QTsH3rSvbKysqwAf/8QX1+fkJycHP24uLi1KXT6WjQOHVH/SgIZBDmCn2Sm8gX9A86HyRA1VPaSBR4L7qxateqqvZ1dMacNnDE2m4rkjAJ1mo4pisGYGWGxpl6xkVFibIRBjDG4c7r12Ch6F6eNvdFRyhiLyb06MNC7kGxxdnygi8mkwLMJEh00/3kfc7wOT3Tk/v3332vaO9rVZKRlSCsLzDPu7h4hPDEk0fw5Z45LUE5u7nIyM1EWFRe7x8bGOnh5eWU+Qj9SkG4EnUVw0kEHz2OxRrjqsGeNjCemIYS4IMc8IyDW1tZJ06EeyNjrZI2MSAwPDWk5OjpmoAFBinWGJn8JNTW1JGSc8eywvlXe3ne6urrcEQEZJgiClDVPROIk62przacDAZmEvJwca93adVFwNTY1yaSkpLhkZ2d7lpSWzGtqarJEdR53HYGhNUlInvaQPyAbYLiDcQg/kTE6oqioWInIYrzLHJd7Hh4esTra2n1TrQMyeGWoQlQZJONpqHykuJuQApcyNTX9AxEBBi/6CRTu6lWrr6GfhkjGyUq7SEHGp1B9fb0RMuILnvZLkDHLcbZjFbpOMxiM0yUlJTq5eXmuubm57mXlZY5oXBpBqMD9dQGSC9ezTCSTRGNC50w6PghJSck2bW3tPHt7+zgHe4dwZyenTERuSdswg9p/EM0RyejdEuiadueHIBmQ1tXVq52yzKoqt0o4u+RSpaT6uVFudv+ApMgs3RaOjjtlpX5UhySqlCQYWqQvEY8NDUuLmplwdKWOIiY6KuHsmI6IQTMadKQvUY4ND4uLGhnmPMmh09vXa+Tk6JSD5gVS9Bl6h6i4uHi14+zZubwaS56enqFZ2VkJSM+BA5gU+UE6V6ahsdEK/Zr5CCI0bGFhkYLuk0U6d5SD75XS0dHh6qryLL1Z9ba2tnmoX/uIaQrKdDi47f76TFxsAoMToM6EtgQvQ1VVlVpFZYU5Miitenp6zCurqgxYLJYGIiZK6BY4TAaWm8A1OGlFQruA4cdAY6hfVVW1Q0pKqhYRjlINdfVcOzu7LBNjkzJkHHKUCKOJZtIbjWP7+Uxf9PX1URoaGlTqG+oNy8rKjNH/TWrr6vRgpQ1NYKqtra0wKUJOY1j+Ep2Qp0kPMPQnMI4hJE8MNJEOaGpqtqP/Q2hDpYaGRhEihQUGBgYl6mrqHdxMjYyBgTEFkoJsLHShoU/Fdgm2SzDuw/8JMADl5cyT9j6pfQAAAABJRU5ErkJggg== created_at: 2018-12-17 11:23:01.596760000 Z updated_at: 2018-12-17 11:23:01.596760000 Z + footprint: 8b3eaf474c43987b8e8378b8d3ff317307f021316804e9a6a1a6d43cfbcf129b value_history_10: id: 10 @@ -97,6 +105,7 @@ value_history_10: value: YYMMmmmX[/VL]R[/A] created_at: 2018-12-17 11:23:01.603733000 Z updated_at: 2018-12-17 11:23:01.603733000 Z + footprint: 5a958e4850121773796513b910aab6543b6218d31852cfa7dfc48c7799e4fc2e value_history_11: id: 11 @@ -105,6 +114,7 @@ value_history_11: value: 'true' created_at: 2018-12-17 11:23:01.607174000 Z updated_at: 2018-12-17 11:23:01.607174000 Z + footprint: 35778ae2b85918c3b59486d52af3be7ea82669544e4002090fcc3af2870b961c value_history_12: id: 12 @@ -113,6 +123,7 @@ value_history_12: value: INMEDFABLAB created_at: 2018-12-17 11:23:01.610103000 Z updated_at: 2018-12-17 11:23:01.610103000 Z + footprint: 164b8b8f2dd57c19425c8b30ddc5b7ee3078fd00525ad5066d4db21da0a9b8d7 value_history_13: id: 13 @@ -121,6 +132,7 @@ value_history_13: value: nnnnnn-MM-YY created_at: 2018-12-17 11:23:01.615671000 Z updated_at: 2018-12-17 11:23:01.615671000 Z + footprint: 24d0af3d73167d18f62508bf898c21c52ca31d85edacd93bdab7f453c9189aff value_history_14: id: 14 @@ -129,6 +141,7 @@ value_history_14: value: 'false' created_at: 2018-12-17 11:23:01.618886000 Z updated_at: 2018-12-17 11:23:01.618886000 Z + footprint: ef4487483ecced80c085204d221df5c423067efacc7caa6ba28e564ff3f2fb80 value_history_15: id: 15 @@ -137,6 +150,7 @@ value_history_15: value: '20.0' created_at: 2018-12-17 11:23:01.625154000 Z updated_at: 2018-12-17 11:23:01.625154000 Z + footprint: 2338ffd7424cb50551882332f5500bc24179ffbdc50853ac5c598aef759419d9 value_history_16: id: 16 @@ -145,6 +159,7 @@ value_history_16: value: Notre association n'est pas assujettie à la TVA created_at: 2018-12-17 11:23:01.632653000 Z updated_at: 2018-12-17 11:23:01.632653000 Z + footprint: 98e4128c298ff380598be8cc527440f800aaf5da457a003295bce4f14dafe5ec value_history_17: id: 17 @@ -155,6 +170,7 @@ value_history_17: - APE 913 E' created_at: 2018-12-17 11:23:01.569316000 Z updated_at: 2018-12-17 11:23:01.636940000 Z + footprint: cba817e7539f25905214b965476e5590feb0c5df8c4f7befe291b7330afc83e7 value_history_18: id: 18 @@ -163,6 +179,7 @@ value_history_18: value: '1970-01-01 08:00:00' created_at: 2018-12-17 11:23:01.639995000 Z updated_at: 2018-12-17 11:23:01.639995000 Z + footprint: f66d7752be611092946c0a6c8547530023002e8168e6d3792485e1acc2005d26 value_history_19: id: 19 @@ -171,6 +188,7 @@ value_history_19: value: '1970-01-01 23:59:59' created_at: 2018-12-17 11:23:01.643667000 Z updated_at: 2018-12-17 11:23:01.643667000 Z + footprint: b475caafe438ec6d562f00f9e09d97a1854fc26993b7bf429bdb9a65d8597cc8 value_history_20: id: 20 @@ -179,6 +197,7 @@ value_history_20: value: 'true' created_at: 2018-12-17 11:23:01.647950000 Z updated_at: 2018-12-17 11:23:01.647950000 Z + footprint: e9cb319169fcf7c5cec6ddda5510a54db662a752da70d65d3ab58bd22966189d value_history_21: id: 21 @@ -187,6 +206,7 @@ value_history_21: value: '24' created_at: 2018-12-17 11:23:01.650611000 Z updated_at: 2018-12-17 11:23:01.650611000 Z + footprint: dda0ad88a7bed25dfe69983ca55a248a1abc590e09e651e1848a84d2c4c39209 value_history_22: id: 22 @@ -195,6 +215,7 @@ value_history_22: value: 'false' created_at: 2018-12-17 11:23:01.653441000 Z updated_at: 2018-12-17 11:23:01.653441000 Z + footprint: 28d47621512b48b4d6044f0377f7a90e1a31a3480022706e63822b58a666b0ad value_history_23: id: 23 @@ -203,6 +224,7 @@ value_history_23: value: '24' created_at: 2018-12-17 11:23:01.655971000 Z updated_at: 2018-12-17 11:23:01.655971000 Z + footprint: 9a90482dd6a5c111e6432a1a1fed049eaf2cfbe77cca5180fa1c897a5d80df0c value_history_24: id: 24 @@ -211,6 +233,7 @@ value_history_24: value: "#cb1117" created_at: 2018-12-17 11:23:01.658780000 Z updated_at: 2018-12-17 11:23:01.658780000 Z + footprint: bd2b955f8497662df96136b5adbfc282e30143974e84fa62cb637356eeb56b0b value_history_25: id: 25 @@ -219,6 +242,7 @@ value_history_25: value: "#ffdd00" created_at: 2018-12-17 11:23:01.666698000 Z updated_at: 2018-12-17 11:23:01.666698000 Z + footprint: c5a4c80ae6b45db9a8ad263b85429fdd47b82be1d732284db7772b33c6511c6d value_history_26: id: 26 @@ -229,6 +253,7 @@ value_history_26: et les heures machines. created_at: 2018-12-17 11:23:01.569316000 Z updated_at: 2018-12-17 11:23:01.717053000 Z + footprint: 45b69c98b0c1f53b01778f911426adaf9c29df3714904024982cd31fe0ae7513 value_history_27: id: 27 @@ -237,6 +262,7 @@ value_history_27: value: Fab Lab de La Casemate created_at: 2018-12-17 11:23:01.724770000 Z updated_at: 2018-12-17 11:23:01.724770000 Z + footprint: f0ea447c7ef6ec3410ef00d9e9f298ea6288421676a59018aeccea8699069275 value_history_28: id: 28 @@ -245,6 +271,7 @@ value_history_28: value: male created_at: 2018-12-17 11:23:01.728465000 Z updated_at: 2018-12-17 11:23:01.728465000 Z + footprint: 6affb97bab0e401e1a5f0675b6cfcc1ba97733ee154296952af032303753d1ad value_history_29: id: 29 @@ -253,6 +280,7 @@ value_history_29: value: created_at: 2018-12-17 11:23:01.724770000 Z updated_at: 2018-12-17 11:23:01.724770000 Z + footprint: 3851b22f15888e02e49a4d8892292825a6f76bac00b4d9c01d9a5ece97cfbf85 value_history_30: id: 30 @@ -261,6 +289,7 @@ value_history_30: value: 3 created_at: 2018-12-17 11:23:01.724770000 Z updated_at: 2018-12-17 11:23:01.724770000 Z + footprint: 73a0a0978d5d6dedf56e19cf7e32e480e9006dd6d61209af13f64740cae8b7aa value_history_31: id: 31 @@ -269,6 +298,7 @@ value_history_31: value: 1 created_at: 2018-12-17 11:23:01.724770000 Z updated_at: 2018-12-17 11:23:01.724770000 Z + footprint: c929c99026087e1513a51ba7c8eab1a1e65d1b3abb496307f8833e7d9014e7f9 value_history_32: id: 32 @@ -277,6 +307,7 @@ value_history_32: value: false created_at: 2018-12-17 11:23:01.724770000 Z updated_at: 2018-12-17 11:23:01.724770000 Z + footprint: eff85b55c0c3923e3e7f8efdc1ef367bfb455d0f8ae0da28666fe47819412d67 value_history_33: id: 33 @@ -285,3 +316,4 @@ value_history_33: value: default created_at: 2018-12-17 11:23:01.724770000 Z updated_at: 2018-12-17 11:23:01.724770000 Z + footprint: 111f857e1e819c6d2d61c8c6e961772deb58c0926da27409c8596f735ffeb9f2 diff --git a/test/fixtures/invoice_items.yml b/test/fixtures/invoice_items.yml index 4e6e8f139..9b29ab471 100644 --- a/test/fixtures/invoice_items.yml +++ b/test/fixtures/invoice_items.yml @@ -9,7 +9,7 @@ invoice_item_1: description: Sleede - standard, association - month subscription_id: 1 invoice_item_id: - footprint: 772e9d8a55abb37b18b16385c4a46bd34c1fe0f795975c5da88316fc0fb5ffc4 + footprint: 931e8b6f5579f2e7eda625bdf5962da89892957abb25480603ae985e6e108059 invoice_item_2: id: 2 @@ -22,7 +22,7 @@ invoice_item_2: d'emploi - month subscription_id: 2 invoice_item_id: - footprint: 5326142307e047379b7cc2aff143bc12529ff4469fb3253171d04f16dd4ec601 + footprint: abbf95b8d506ed6207343edbf8ee63893cd02508de6d25347999d7dbc755c6fb invoice_item_3: id: 3 @@ -34,7 +34,7 @@ invoice_item_3: description: Mensuel - standard, association - month subscription_id: 3 invoice_item_id: - footprint: 32089152aa3cf6b6059598a570202b9b710d233602521d8167fe03cef79a05ea + footprint: caf3b7e54e0191d8f262e01acc4e66f29f9d27abe52e595fb3b71724a32cfe90 invoice_item_4: id: 4 @@ -46,7 +46,7 @@ invoice_item_4: description: Formation Laser / Vinyle April 11, 2012 08:00 - 12:00 PM subscription_id: invoice_item_id: - footprint: 2762d4ec03451e1da0abfe3622dabffd1c9183c5a58f5626a36c0a5936f99e95 + footprint: 71d095aa4d49ebac39f7efdc5c9de2354ea8efeec7c35a6708143fb66dd66b0b invoice_item_5: id: 5 @@ -58,5 +58,5 @@ invoice_item_5: description: Imprimante 3D June 15, 2015 12:00 - 01:00 PM subscription_id: invoice_item_id: - footprint: 640e915fb30da68f30976f93486d158b5ac01b4caf8f1335c4376d6b36c12ead + footprint: d4d856ee04f27b5e35f43056b46f139df73ffdfaad1716636f569164c52c5e5b diff --git a/test/fixtures/invoices.yml b/test/fixtures/invoices.yml index 535e22c76..6f90b2a03 100644 --- a/test/fixtures/invoices.yml +++ b/test/fixtures/invoices.yml @@ -15,7 +15,7 @@ invoice_1: type: subscription_to_expire: description: - footprint: b4bff0b6176a706f745710ef12014917ceefc557ce60e26ec638394ffaad8236 + footprint: 9b1d216a49a65f5428c92af10e284d6dfe4070f6e65e5eacd735ef770540a16a environment: test operator_id: @@ -35,7 +35,7 @@ invoice_2: type: subscription_to_expire: description: - footprint: dfe9a91a365155d4f6b4c614071d2c132472095a5a80f6c5d87e45c199bffd27 + footprint: 32c09fe7ba92501f9239c111abd6688cb7d4ea5fe16c201f56d8d28546031804 environment: test operator_id: @@ -55,7 +55,7 @@ invoice_3: type: subscription_to_expire: description: - footprint: 9913446ac3383d029e3742baa85ae72632f7dddd7fb7d291140775e8735795e3 + footprint: bbb731b181eafd9a78b0b610afeddd3c92f55fcc11b9d58a2d4956cb30b28ee0 environment: test operator_id: @@ -76,7 +76,7 @@ invoice_4: type: subscription_to_expire: description: - footprint: 776bb1ebd50a8cb5ed61af8c80c08185dd3d66baa77b46001245d57f421642f4 + footprint: 0b4afc997a22975102441c9dc3635a43bb098d31086f79189751d12e0fb0078c environment: test operator_id: @@ -96,6 +96,6 @@ invoice_5: type: subscription_to_expire: description: - footprint: cb23d5e724697e39da04f73b0e3d6e5e7830fd5db10d56f9998d399d435f2b74 + footprint: b580117a83436c91475f06ced6c043ce9677c86c2c04cd41ed10860fb214ec71 environment: test operator_id: From 5cdaa014eff7b0a0137fac707e68d77acda838ed Mon Sep 17 00:00:00 2001 From: Sylvain Date: Mon, 25 Mar 2019 14:57:48 +0100 Subject: [PATCH 55/75] [security] updated devise + updated rails --- CHANGELOG.md | 4 + Gemfile | 5 +- Gemfile.lock | 86 +++++++++---------- README.md | 2 +- .../controllers/application.js.erb | 2 +- app/assets/templates/shared/header.html.erb | 4 +- app/controllers/application_controller.rb | 15 ++-- app/controllers/sessions_controller.rb | 2 +- app/models/notification_type.rb | 1 - app/models/user.rb | 6 +- .../api/auth_providers/active.json.jbuilder | 4 +- .../notify_user_auth_migration.html.erb | 2 +- .../notify_user_account_created.html.erb | 2 +- config/initializers/devise_async.rb | 5 -- 14 files changed, 71 insertions(+), 69 deletions(-) delete mode 100644 config/initializers/devise_async.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b89ecc0d..3d71debc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog Fab Manager +- Fix a security issue: updated to devise 4.6.0 to fix [CVE-2019-5421](https://github.com/plataformatec/devise/issues/4981) +- Fix a security issue: updated Rails to 4.2.11.1 to fix [CVE-2019-5418](https://groups.google.com/forum/#!topic/rubyonrails-security/pFRKI96Sm8Q) and [CVE-2019-5419](https://groups.google.com/forum/#!topic/rubyonrails-security/GN7w9fFAQeI) +- [TODO DEPLOY] (dev) if applicable, you must first downgrade bundler to v1 `gem uninstall bundler --version=2.0.1 && gem install bundler --version=1.7.3 && bundle install`emi + ## v2.8.4 2019 March 18 - Limit members search to 50 results to speed up queries diff --git a/Gemfile b/Gemfile index 964fbddea..457d8da2b 100644 --- a/Gemfile +++ b/Gemfile @@ -2,7 +2,7 @@ source 'https://rubygems.org' gem 'compass-rails', '2.0.4' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' -gem 'rails', '4.2.11' +gem 'rails', '4.2.11.1' # Use SCSS for stylesheets gem 'sass-rails', '5.0.1' @@ -73,8 +73,7 @@ gem 'seed_dump' gem 'pg' -gem 'devise' -gem 'devise-async' +gem 'devise', ">= 4.6.0" gem 'omniauth', '~> 1.6.0' gem 'omniauth-oauth2' diff --git a/Gemfile.lock b/Gemfile.lock index 22fe64557..d7fbb1192 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -14,39 +14,39 @@ GEM specs: Ascii85 (1.0.2) aasm (4.1.0) - actionmailer (4.2.11) - actionpack (= 4.2.11) - actionview (= 4.2.11) - activejob (= 4.2.11) + actionmailer (4.2.11.1) + actionpack (= 4.2.11.1) + actionview (= 4.2.11.1) + activejob (= 4.2.11.1) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 1.0, >= 1.0.5) - actionpack (4.2.11) - actionview (= 4.2.11) - activesupport (= 4.2.11) + actionpack (4.2.11.1) + actionview (= 4.2.11.1) + activesupport (= 4.2.11.1) rack (~> 1.6) rack-test (~> 0.6.2) rails-dom-testing (~> 1.0, >= 1.0.5) rails-html-sanitizer (~> 1.0, >= 1.0.2) actionpack-page_caching (1.0.2) actionpack (>= 4.0.0, < 5) - actionview (4.2.11) - activesupport (= 4.2.11) + actionview (4.2.11.1) + activesupport (= 4.2.11.1) builder (~> 3.1) erubis (~> 2.7.0) rails-dom-testing (~> 1.0, >= 1.0.5) rails-html-sanitizer (~> 1.0, >= 1.0.3) active_record_query_trace (1.4) - activejob (4.2.11) - activesupport (= 4.2.11) + activejob (4.2.11.1) + activesupport (= 4.2.11.1) globalid (>= 0.3.0) - activemodel (4.2.11) - activesupport (= 4.2.11) + activemodel (4.2.11.1) + activesupport (= 4.2.11.1) builder (~> 3.1) - activerecord (4.2.11) - activemodel (= 4.2.11) - activesupport (= 4.2.11) + activerecord (4.2.11.1) + activemodel (= 4.2.11.1) + activesupport (= 4.2.11.1) arel (~> 6.0) - activesupport (4.2.11) + activesupport (4.2.11.1) i18n (~> 0.7) minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) @@ -70,7 +70,7 @@ GEM axlsx_rails (0.4.0) axlsx (>= 2.0.1) rails (>= 3.1) - bcrypt (3.1.10) + bcrypt (3.1.12) binding_of_caller (0.7.3) debug_inspector (>= 0.0.1) bootstrap-sass (3.4.1) @@ -119,7 +119,7 @@ GEM compass (~> 1.0.0) sass-rails (<= 5.0.1) sprockets (< 2.13) - concurrent-ruby (1.1.4) + concurrent-ruby (1.1.5) connection_pool (2.2.0) coveralls (0.8.16) json (>= 1.8, < 3) @@ -135,15 +135,12 @@ GEM debug_inspector (0.0.3) descendants_tracker (0.0.4) thread_safe (~> 0.3, >= 0.3.1) - devise (3.4.1) + devise (4.6.1) bcrypt (~> 3.0) orm_adapter (~> 0.1) - railties (>= 3.2.6, < 5) + railties (>= 4.1.0, < 6.0) responders - thread_safe (~> 0.1) warden (~> 1.2.3) - devise-async (0.9.0) - devise (~> 3.2) docile (1.1.5) domain_name (0.5.25) unf (>= 0.0.5, < 1.0.0) @@ -185,7 +182,7 @@ GEM forgery (0.6.0) friendly_id (5.1.0) activerecord (>= 4.0.0) - globalid (0.4.1) + globalid (0.4.2) activesupport (>= 4.2.0) has_secure_token (1.0.0) activerecord (>= 3.0) @@ -249,7 +246,7 @@ GEM mimemagic (0.3.2) mini_magick (4.2.0) mini_mime (1.0.1) - mini_portile2 (2.3.0) + mini_portile2 (2.4.0) minitest (5.11.3) minitest-reporters (1.1.8) ansi @@ -268,8 +265,8 @@ GEM net-ssh-gateway (1.2.0) net-ssh (>= 2.6.5) netrc (0.10.3) - nokogiri (1.8.5) - mini_portile2 (~> 2.3.0) + nokogiri (1.10.1) + mini_portile2 (~> 2.4.0) notify_with (0.0.2) jbuilder (~> 2.0) rails (>= 4.2.0) @@ -318,16 +315,16 @@ GEM rack-test (0.6.3) rack (>= 1.0) railroady (1.5.3) - rails (4.2.11) - actionmailer (= 4.2.11) - actionpack (= 4.2.11) - actionview (= 4.2.11) - activejob (= 4.2.11) - activemodel (= 4.2.11) - activerecord (= 4.2.11) - activesupport (= 4.2.11) + rails (4.2.11.1) + actionmailer (= 4.2.11.1) + actionpack (= 4.2.11.1) + actionview (= 4.2.11.1) + activejob (= 4.2.11.1) + activemodel (= 4.2.11.1) + activerecord (= 4.2.11.1) + activesupport (= 4.2.11.1) bundler (>= 1.3.0, < 2.0) - railties (= 4.2.11) + railties (= 4.2.11.1) sprockets-rails rails-deprecated_sanitizer (1.0.3) activesupport (>= 4.2.0.alpha) @@ -344,9 +341,9 @@ GEM rails_stdout_logging rails_serve_static_assets (0.0.4) rails_stdout_logging (0.0.3) - railties (4.2.11) - actionpack (= 4.2.11) - activesupport (= 4.2.11) + railties (4.2.11.1) + actionpack (= 4.2.11.1) + activesupport (= 4.2.11.1) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) rainbow (3.0.0) @@ -494,7 +491,7 @@ GEM coercible (~> 1.0) descendants_tracker (~> 0.0, >= 0.0.3) equalizer (~> 0.0, >= 0.0.9) - warden (1.2.3) + warden (1.2.7) rack (>= 1.0) web-console (2.1.3) activemodel (>= 4.0) @@ -528,8 +525,7 @@ DEPENDENCIES compass-rails (= 2.0.4) coveralls database_cleaner - devise - devise-async + devise (>= 4.6.0) elasticsearch-model (~> 5) elasticsearch-persistence (~> 5) elasticsearch-rails (~> 5) @@ -562,7 +558,7 @@ DEPENDENCIES pundit rack-protection (= 1.5.5) railroady - rails (= 4.2.11) + rails (= 4.2.11.1) rails-observers rails_12factor rb-readline @@ -591,4 +587,4 @@ DEPENDENCIES webmock BUNDLED WITH - 1.17.2 + 1.17.3 diff --git a/README.md b/README.md index b575911c0..b9f9fe3ba 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,7 @@ This procedure is not easy to follow so if you don't need to write some code for 10. Install bundler in the current RVM gemset ```bash - gem install bundler + gem install bundler --version=1.17.3 ``` 11. Install the required ruby gems and javascript plugins diff --git a/app/assets/javascripts/controllers/application.js.erb b/app/assets/javascripts/controllers/application.js.erb index 7bae15ddb..7e89ca850 100644 --- a/app/assets/javascripts/controllers/application.js.erb +++ b/app/assets/javascripts/controllers/application.js.erb @@ -340,7 +340,7 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco var openLoginModal = function (toState, toParams, callback) { <% active_provider = AuthProvider.active %> <% if active_provider.providable_type != DatabaseProvider.name %> - $window.location.href = '<%=user_omniauth_authorize_path(AuthProvider.active.strategy_name.to_sym)%>'; + $window.location.href = '<%="/users/auth/#{active_provider.strategy_name}"%>'; <% else %> return $uibModal.open({ templateUrl: '<%= asset_path "shared/deviseModal.html" %>', diff --git a/app/assets/templates/shared/header.html.erb b/app/assets/templates/shared/header.html.erb index 3a2c59502..9a3fe9cfc 100644 --- a/app/assets/templates/shared/header.html.erb +++ b/app/assets/templates/shared/header.html.erb @@ -53,9 +53,9 @@ {{ 'sign_in' | translate }} <% else %> -
  • {{ 'sign_up' | translate }}
  • +
  • " class="font-sbold label text-md"> {{ 'sign_up' | translate }}
  • - {{ 'sign_in' | translate }} + " class="font-sbold label text-md"> {{ 'sign_in' | translate }}
  • <% end %> diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 90a61671c..f59db1763 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -30,11 +30,16 @@ class ApplicationController < ActionController::Base end def configure_permitted_parameters - devise_parameter_sanitizer.for(:sign_up) << - { profile_attributes: [:phone, :last_name, :first_name, :gender, :birthday, :interest, :software_mastered, - organization_attributes: [:name, address_attributes: [:address]]] } - - devise_parameter_sanitizer.for(:sign_up).concat %i[username is_allow_contact is_allow_newsletter cgu group_id] + devise_parameter_sanitizer.permit(:sign_up, + keys: [ + { profile_attributes: [ + :phone, :last_name, :first_name, :gender, :birthday, + :interest, :software_mastered, organization_attributes: [ + :name, address_attributes: [:address] + ] + ] }, + :username, :is_allow_contact, :is_allow_newsletter, :cgu, :group_id + ]) end def default_url_options diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 5488450ad..e7ee47a0e 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -4,7 +4,7 @@ class SessionsController < Devise::SessionsController def new active_provider = AuthProvider.active if active_provider.providable_type != DatabaseProvider.name - redirect_to user_omniauth_authorize_path(active_provider.strategy_name.to_sym) + redirect_to "/users/auth/#{active_provider.strategy_name}" else super end diff --git a/app/models/notification_type.rb b/app/models/notification_type.rb index 54503aa56..11230db24 100644 --- a/app/models/notification_type.rb +++ b/app/models/notification_type.rb @@ -42,7 +42,6 @@ class NotificationType notify_admin_export_complete notify_member_about_coupon notify_member_reservation_reminder - notify_admin_free_disk_space ] # deprecated: # - notify_member_subscribed_plan_is_changed diff --git a/app/models/user.rb b/app/models/user.rb index ca5dd6963..11a842552 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -8,7 +8,7 @@ class User < ActiveRecord::Base # Include default devise modules. Others available are: # :lockable, :timeoutable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable, - :confirmable, :async + :confirmable rolify # enable OmniAuth authentication only if needed @@ -318,6 +318,10 @@ class User < ActiveRecord::Base create_wallet end + def send_devise_notification(notification, *args) + devise_mailer.send(notification, self, *args).deliver_later + end + def notify_admin_when_user_is_created if need_completion? && !provider.nil? NotificationCenter.call type: 'notify_admin_when_user_is_imported', diff --git a/app/views/api/auth_providers/active.json.jbuilder b/app/views/api/auth_providers/active.json.jbuilder index fc0a96351..1f35ece38 100644 --- a/app/views/api/auth_providers/active.json.jbuilder +++ b/app/views/api/auth_providers/active.json.jbuilder @@ -4,9 +4,9 @@ json.link_to_sso_profile @provider.link_to_sso_profile if @provider.providable_type == DatabaseProvider.name json.link_to_sso_connect '/#' else - json.link_to_sso_connect user_omniauth_authorize_path(@provider.strategy_name.to_sym) + json.link_to_sso_connect "/users/auth/#{@provider.strategy_name}" end if @provider.providable_type == OAuth2Provider.name json.domain @provider.providable.domain -end \ No newline at end of file +end diff --git a/app/views/notifications_mailer/notify_user_auth_migration.html.erb b/app/views/notifications_mailer/notify_user_auth_migration.html.erb index a366d776f..d72a262e5 100644 --- a/app/views/notifications_mailer/notify_user_auth_migration.html.erb +++ b/app/views/notifications_mailer/notify_user_auth_migration.html.erb @@ -15,7 +15,7 @@ <% active_provider = AuthProvider.active %> <%= render 'notifications_mailer/shared/hello', recipient: @recipient %> <% - url_path = user_omniauth_authorize_path(active_provider.strategy_name.to_sym) + url_path = "/users/auth/#{active_provider.strategy_name}" if url_path[0] == '/' and root_url[-1] == '/' url_path = root_url + url_path[1..-1] else diff --git a/app/views/users_mailer/notify_user_account_created.html.erb b/app/views/users_mailer/notify_user_account_created.html.erb index b0377fc14..1c3bfac6e 100644 --- a/app/views/users_mailer/notify_user_account_created.html.erb +++ b/app/views/users_mailer/notify_user_account_created.html.erb @@ -38,7 +38,7 @@

    <%= t('.body.thanks_to_') %> - + " target="_blank"> <%= t('body.logon_or_login', PROVIDER: active_provider.name )%>

    diff --git a/config/initializers/devise_async.rb b/config/initializers/devise_async.rb deleted file mode 100644 index 1ae610c44..000000000 --- a/config/initializers/devise_async.rb +++ /dev/null @@ -1,5 +0,0 @@ -Devise::Async.setup do |config| - config.enabled = true - config.backend = :sidekiq - config.queue = :devise_mailer -end \ No newline at end of file From ac57cba8ff3a40a9ece2d275e7474cda771f9864 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Mon, 25 Mar 2019 14:58:32 +0100 Subject: [PATCH 56/75] updated changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d71debc1..fb30972f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ - Fix a security issue: updated to devise 4.6.0 to fix [CVE-2019-5421](https://github.com/plataformatec/devise/issues/4981) - Fix a security issue: updated Rails to 4.2.11.1 to fix [CVE-2019-5418](https://groups.google.com/forum/#!topic/rubyonrails-security/pFRKI96Sm8Q) and [CVE-2019-5419](https://groups.google.com/forum/#!topic/rubyonrails-security/GN7w9fFAQeI) -- [TODO DEPLOY] (dev) if applicable, you must first downgrade bundler to v1 `gem uninstall bundler --version=2.0.1 && gem install bundler --version=1.7.3 && bundle install`emi +- [TODO DEPLOY] (dev) if applicable, you must first downgrade bundler to v1 `gem uninstall bundler --version=2.0.1 && gem install bundler --version=1.7.3 && bundle install` ## v2.8.4 2019 March 18 From 96c3e3173aa2ec89928e85b0f19212560806d9a1 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Mon, 25 Mar 2019 17:14:26 +0100 Subject: [PATCH 57/75] [ongoing] remove LaCasemate marks --- CONTRIBUTING.md | 6 ++--- README.md | 8 +++--- db/seeds.rb | 68 +++++++++++++++++++++++++------------------------ 3 files changed, 42 insertions(+), 40 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3b41e3d93..d7ee792fc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,7 +13,7 @@ patches and features. ## Using the issue tracker -The [issue tracker](https://github.com/LaCasemate/fab-manager/issues) is the preferred channel for [bug reports](#bugs) +The [issue tracker](https://github.com/sleede/fab-manager/issues) is the preferred channel for [bug reports](#bugs) and [submitting pull requests](#pull-requests), but please respect the following restrictions: * Please **do not** use the issue tracker for personal support requests (use [the forum](https://forum.fab-manager.com)). @@ -96,7 +96,7 @@ Adhering to the following process is the best way to get your work included in t # Navigate to the newly cloned directory cd fab-manager # Assign the original repo to a remote called "upstream" - git remote add upstream https://github.com/LaCasemate/fab-manager.git + git remote add upstream https://github.com/sleede/fab-manager.git ``` 2. If you cloned a while ago, get the latest changes from upstream: @@ -131,4 +131,4 @@ Adhering to the following process is the best way to get your work included in t 7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/) with a clear title and description. **IMPORTANT**: By submitting a patch, you agree to allow the project owners to license your work under the terms of -the [GNU Affero General Public License](LICENSE.md). \ No newline at end of file +the [GNU Affero General Public License](LICENSE.md). diff --git a/README.md b/README.md index b9f9fe3ba..873cd0959 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ FabManager is the FabLab management solution. It is web-based, open-source and totally free. -[![Coverage Status](https://coveralls.io/repos/github/LaCasemate/fab-manager/badge.svg)](https://coveralls.io/github/LaCasemate/fab-manager) +[![Coverage Status](https://coveralls.io/repos/github/sleede/fab-manager/badge.svg)](https://coveralls.io/github/sleede/fab-manager) [![Docker pulls](https://img.shields.io/docker/pulls/sleede/fab-manager.svg)](https://hub.docker.com/r/sleede/fab-manager/) [![Docker Build Status](https://img.shields.io/docker/build/sleede/fab-manager.svg)](https://hub.docker.com/r/sleede/fab-manager/builds) @@ -102,7 +102,7 @@ This procedure is not easy to follow so if you don't need to write some code for 7. Retrieve the project from Git ```bash - git clone https://github.com/LaCasemate/fab-manager.git + git clone https://github.com/sleede/fab-manager.git ``` 8. Install the software dependencies. @@ -205,7 +205,7 @@ environment. 2. Retrieve the project from Git ```bash - git clone https://github.com/LaCasemate/fab-manager + git clone https://github.com/sleede/fab-manager ``` 3. From the project directory, run: @@ -467,7 +467,7 @@ After modifying any values concerning the localisation, restart the application **This configuration is optional.** -You can configure your fab-manager to synchronize every project with the [Open Projects platform](https://github.com/LaCasemate/openlab-projects). +You can configure your fab-manager to synchronize every project with the [Open Projects platform](https://github.com/sleede/openlab-projects). It's very simple and straightforward and in return, your users will be able to search over projects from all fab-manager instances from within your platform. The deal is fair, you share your projects and as reward you benefits from projects of the whole community. diff --git a/db/seeds.rb b/db/seeds.rb index 476a878cf..e9e0c2757 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -168,46 +168,48 @@ end # end # end -if Category.count == 0 - Category.create!([ - {name: 'Stage'}, - {name: 'Atelier'} - ]) +if Category.count.zero? + Category.create!( + [ + { name: 'Stage' }, + { name: 'Atelier' } + ] + ) end unless Setting.find_by(name: 'about_body').try(:value) setting = Setting.find_or_initialize_by(name: 'about_body') - setting.value = "

    Le Fab Lab de La Casemate est un"+ - ' atelier de fabrication numérique où l’on peut utiliser des machines de découpe, des imprimantes 3D,… permettant'+ - ' de travailler sur des matériaux variés : plastique, bois, carton, vinyle, … afin de créer toute sorte d’objet grâce'+ - ' à la conception assistée par ordinateur ou à l’électronique. Mais le Fab Lab est aussi un lieu d’échange de'+ - ' compétences technique.

    '+ - "

    Le Fab Lab de La Casemate est un espace"+ - ' permanent : ouvert à tous, il offre la possibilité de réaliser des objets soi-même, de partager ses'+ - ' compétences et d’apprendre au contact des médiateurs du Fab Lab et des autres usagers.

    '+ - '

    La formation au Fab Lab s’appuie sur des projets et le partage de connaissances : vous devez prendre'+ + setting.value = '

    La Fabrique du Fab-manager est un' \ + ' atelier de fabrication numérique où l’on peut utiliser des machines de découpe, des imprimantes 3D,… permettant' \ + ' de travailler sur des matériaux variés : plastique, bois, carton, vinyle, … afin de créer toute sorte d’objet grâce' \ + ' à la conception assistée par ordinateur ou à l’électronique. Mais le Fab Lab est aussi un lieu d’échange de' \ + ' compétences technique.

    ' \ + '

    La Fabrique du Fab-manager est un espace' \ + ' permanent : ouvert à tous, il offre la possibilité de réaliser des objets soi-même, de partager ses' \ + ' compétences et d’apprendre au contact des médiateurs du Fab Lab et des autres usagers.

    ' \ + '

    La formation au Fab Lab s’appuie sur des projets et le partage de connaissances : vous devez prendre' \ ' part à la capitalisation des connaissances et à l’instruction des autres utilisateurs.

    ' setting.save end unless Setting.find_by(name: 'about_title').try(:value) setting = Setting.find_or_initialize_by(name: 'about_title') - setting.value = 'Imaginer, Fabriquer,
    Partager au Fab Lab
    de La Casemate' + setting.value = 'Imaginer, Fabriquer,
    Partager à la Fabrique
    du Fab-manager' setting.save end unless Setting.find_by(name: 'about_contacts').try(:value) setting = Setting.find_or_initialize_by(name: 'about_contacts') - setting.value = '
    '+ - '
    Manager Fab Lab :
    '+ - '
    jean-michel.molenaar@lacasemate.fr
    '+ - '
    Responsable médiation :
    '+ - '
    catherine.demarcq@lacasemate.fr
    '+ - '
    Animateur scientifique :
    '+ - '
    diego.scharager@lacasemate.fr
    '+ - '
    '+ - '

    '+ - "

    Visitez le site de La Casemate

    " + setting.value = '
    ' \ + '
    Manager Fab Lab :
    ' \ + '
    jean.dupont@fab-manager.com
    ' \ + '
    Responsable médiation :
    ' \ + '
    pierre.dupond@fab-manager.com
    ' \ + '
    Animateur scientifique :
    ' \ + '
    louise.durand@fab-manager.com
    ' \ + '
    ' \ + '

    ' \ + "

    Visitez le site de Fab-manager

    " setting.save end @@ -297,11 +299,11 @@ end unless Setting.find_by(name: 'invoice_legals').try(:value) setting = Setting.find_or_initialize_by(name: 'invoice_legals') - setting.value = 'La Casemate
    '+ - '2 Place St Laurent 38000 GRENOBLE France
    '+ - 'Tél. Administration : +33 4 76 44 88 80
    '+ - 'Fax. : +33 4 76 42 76 66
    '+ - 'SIRET : 317 270 593 00013 - APE 913 E' + setting.value = 'La fabrique
    ' \ + '68 rue Louise Michel 38100 GRENOBLE France
    ' \ + 'Tél. : +33 1 23 45 67 98
    ' \ + 'Fax. : +33 1 23 45 67 98
    ' \ + 'SIRET : 237 082 474 00006 - APE 913 E' setting.save end @@ -365,13 +367,13 @@ end unless Setting.find_by(name: 'fablab_name').try(:value) setting = Setting.find_or_initialize_by(name: 'fablab_name') - setting.value = 'Fab Lab de La Casemate' + setting.value = 'Fabrique' setting.save end unless Setting.find_by(name: 'name_genre').try(:value) setting = Setting.find_or_initialize_by(name: 'name_genre') - setting.value = 'male' + setting.value = 'female' setting.save end @@ -457,4 +459,4 @@ unless StatisticIndex.find_by(es_type_key: 'space') {statistic_index_id: index.id, key: 'booking', label:I18n.t('statistics.bookings'), graph: true, simple: true}, {statistic_index_id: index.id, key: 'hour', label:I18n.t('statistics.hours_number'), graph: true, simple: false} ]) -end \ No newline at end of file +end From cdf8aad34c583234b8a78e15aea2af9edd1d11c2 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Mon, 25 Mar 2019 17:15:19 +0100 Subject: [PATCH 58/75] updated twitter feed --- db/seeds.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/seeds.rb b/db/seeds.rb index e9e0c2757..1071018ae 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -215,7 +215,7 @@ end unless Setting.find_by(name: 'twitter_name').try(:value) setting = Setting.find_or_initialize_by(name: 'twitter_name') - setting.value = 'fablabgrenoble' + setting.value = 'fab_manager' setting.save end From 208e33a8905fa033870bb6674ed6b0dfc302f83a Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 26 Mar 2019 10:06:29 +0100 Subject: [PATCH 59/75] updated seeds file --- .rubocop.yml | 2 +- db/seeds.rb | 375 ++++++++++++++++++++++++++++++--------------------- 2 files changed, 223 insertions(+), 154 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index c4e6a5ab5..8b1d1390e 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,5 +1,5 @@ Metrics/LineLength: - Max: 130 + Max: 140 Metrics/MethodLength: Max: 30 Metrics/CyclomaticComplexity: diff --git a/db/seeds.rb b/db/seeds.rb index 1071018ae..39bf8bc32 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -1,36 +1,34 @@ -#encoding: utf-8 +# frozen_string_literal: true -if StatisticIndex.count == 0 +if StatisticIndex.count.zero? StatisticIndex.create!([ - {id:1, es_type_key:'subscription', label:I18n.t('statistics.subscriptions')}, - {id:2, es_type_key:'machine', label:I18n.t('statistics.machines_hours')}, - {id:3, es_type_key:'training', label:I18n.t('statistics.trainings')}, - {id:4, es_type_key:'event', label:I18n.t('statistics.events')}, - {id:5, es_type_key:'account', label:I18n.t('statistics.registrations'), ca: false}, - {id:6, es_type_key:'project', label:I18n.t('statistics.projects'), ca: false}, - {id:7, es_type_key:'user', label:I18n.t('statistics.users'), table: false, ca: false} - ]) + { id: 1, es_type_key: 'subscription', label: I18n.t('statistics.subscriptions') }, + { id: 2, es_type_key: 'machine', label: I18n.t('statistics.machines_hours') }, + { id: 3, es_type_key: 'training', label: I18n.t('statistics.trainings') }, + { id: 4, es_type_key: 'event', label: I18n.t('statistics.events') }, + { id: 5, es_type_key: 'account', label: I18n.t('statistics.registrations'), ca: false }, + { id: 6, es_type_key: 'project', label: I18n.t('statistics.projects'), ca: false }, + { id: 7, es_type_key: 'user', label: I18n.t('statistics.users'), table: false, ca: false } + ]) connection = ActiveRecord::Base.connection - if connection.instance_values['config'][:adapter] == 'postgresql' - connection.execute("SELECT setval('statistic_indices_id_seq', 7);") - end + connection.execute("SELECT setval('statistic_indices_id_seq', 7);") if connection.instance_values['config'][:adapter] == 'postgresql' end -if StatisticField.count == 0 +if StatisticField.count.zero? StatisticField.create!([ - # available data_types : index, number, date, text, list - {key:'trainingId', label:I18n.t('statistics.training_id'), statistic_index_id: 3, data_type: 'index'}, - {key:'trainingDate', label:I18n.t('statistics.training_date'), statistic_index_id: 3, data_type: 'date'}, - {key:'eventId', label:I18n.t('statistics.event_id'), statistic_index_id: 4, data_type: 'index'}, - {key:'eventDate', label:I18n.t('statistics.event_date'), statistic_index_id: 4, data_type: 'date'}, - {key:'themes', label:I18n.t('statistics.themes'), statistic_index_id: 6, data_type: 'list'}, - {key:'components', label:I18n.t('statistics.components'), statistic_index_id: 6, data_type: 'list'}, - {key:'machines', label:I18n.t('statistics.machines'), statistic_index_id: 6, data_type: 'list'}, - {key:'name', label:I18n.t('statistics.event_name'), statistic_index_id: 4, data_type: 'text'}, - {key:'userId', label:I18n.t('statistics.user_id'), statistic_index_id: 7, data_type: 'index'}, - {key:'eventTheme', label:I18n.t('statistics.event_theme'), statistic_index_id: 4, data_type: 'text'}, - {key:'ageRange', label:I18n.t('statistics.age_range'), statistic_index_id: 4, data_type: 'text'} - ]) + # available data_types : index, number, date, text, list + { key: 'trainingId', label: I18n.t('statistics.training_id'), statistic_index_id: 3, data_type: 'index' }, + { key: 'trainingDate', label: I18n.t('statistics.training_date'), statistic_index_id: 3, data_type: 'date' }, + { key: 'eventId', label: I18n.t('statistics.event_id'), statistic_index_id: 4, data_type: 'index' }, + { key: 'eventDate', label: I18n.t('statistics.event_date'), statistic_index_id: 4, data_type: 'date' }, + { key: 'themes', label: I18n.t('statistics.themes'), statistic_index_id: 6, data_type: 'list' }, + { key: 'components', label: I18n.t('statistics.components'), statistic_index_id: 6, data_type: 'list' }, + { key: 'machines', label: I18n.t('statistics.machines'), statistic_index_id: 6, data_type: 'list' }, + { key: 'name', label: I18n.t('statistics.event_name'), statistic_index_id: 4, data_type: 'text' }, + { key: 'userId', label: I18n.t('statistics.user_id'), statistic_index_id: 7, data_type: 'index' }, + { key: 'eventTheme', label: I18n.t('statistics.event_theme'), statistic_index_id: 4, data_type: 'text' }, + { key: 'ageRange', label: I18n.t('statistics.age_range'), statistic_index_id: 4, data_type: 'text' } + ]) end unless StatisticField.find_by(key:'groupName').try(:label) @@ -41,132 +39,188 @@ unless StatisticField.find_by(key:'groupName').try(:label) field.save! end -if StatisticType.count == 0 +if StatisticType.count.zero? StatisticType.create!([ - {statistic_index_id: 2, key: 'booking', label:I18n.t('statistics.bookings'), graph: true, simple: true}, - {statistic_index_id: 2, key: 'hour', label:I18n.t('statistics.hours_number'), graph: true, simple: false}, - {statistic_index_id: 3, key: 'booking', label:I18n.t('statistics.bookings'), graph: false, simple: true}, - {statistic_index_id: 3, key: 'hour', label:I18n.t('statistics.hours_number'), graph: false, simple: false}, - {statistic_index_id: 4, key: 'booking', label:I18n.t('statistics.tickets_number'), graph: false, simple: false}, - {statistic_index_id: 4, key: 'hour', label:I18n.t('statistics.hours_number'), graph: false, simple: false}, - {statistic_index_id: 5, key: 'member', label:I18n.t('statistics.users'), graph: true, simple: true}, - {statistic_index_id: 6, key: 'project', label:I18n.t('statistics.projects'), graph: false, simple: true}, - {statistic_index_id: 7, key: 'revenue', label:I18n.t('statistics.revenue'), graph: false, simple: false} - ]) + { statistic_index_id: 2, key: 'booking', label: I18n.t('statistics.bookings'), graph: true, simple: true }, + { statistic_index_id: 2, key: 'hour', label: I18n.t('statistics.hours_number'), graph: true, simple: false }, + { statistic_index_id: 3, key: 'booking', label: I18n.t('statistics.bookings'), graph: false, simple: true }, + { statistic_index_id: 3, key: 'hour', label: I18n.t('statistics.hours_number'), graph: false, simple: false }, + { statistic_index_id: 4, key: 'booking', label: I18n.t('statistics.tickets_number'), graph: false, + simple: false }, + { statistic_index_id: 4, key: 'hour', label: I18n.t('statistics.hours_number'), graph: false, simple: false }, + { statistic_index_id: 5, key: 'member', label: I18n.t('statistics.users'), graph: true, simple: true }, + { statistic_index_id: 6, key: 'project', label: I18n.t('statistics.projects'), graph: false, simple: true }, + { statistic_index_id: 7, key: 'revenue', label: I18n.t('statistics.revenue'), graph: false, simple: false } + ]) end -if StatisticSubType.count == 0 +if StatisticSubType.count.zero? StatisticSubType.create!([ - {key: 'created', label:I18n.t('statistics.account_creation'), statistic_types: StatisticIndex.find_by(es_type_key: 'account').statistic_types}, - {key: 'published', label:I18n.t('statistics.project_publication'), statistic_types: StatisticIndex.find_by(es_type_key: 'project').statistic_types} - ]) + { key: 'created', label: I18n.t('statistics.account_creation'), + statistic_types: StatisticIndex.find_by(es_type_key: 'account').statistic_types }, + { key: 'published', label:I18n.t('statistics.project_publication'), + statistic_types: StatisticIndex.find_by(es_type_key: 'project').statistic_types } + ]) end -if StatisticGraph.count == 0 +if StatisticGraph.count.zero? StatisticGraph.create!([ - {statistic_index_id:1, chart_type:'stackedAreaChart', limit:0}, - {statistic_index_id:2, chart_type:'stackedAreaChart', limit:0}, - {statistic_index_id:3, chart_type:'discreteBarChart', limit:10}, - {statistic_index_id:4, chart_type:'discreteBarChart', limit:10}, - {statistic_index_id:5, chart_type:'lineChart', limit:0}, - {statistic_index_id:7, chart_type:'discreteBarChart', limit:10} - ]) + { statistic_index_id: 1, chart_type: 'stackedAreaChart', limit: 0 }, + { statistic_index_id: 2, chart_type: 'stackedAreaChart', limit: 0 }, + { statistic_index_id: 3, chart_type: 'discreteBarChart', limit: 10 }, + { statistic_index_id: 4, chart_type: 'discreteBarChart', limit: 10 }, + { statistic_index_id: 5, chart_type: 'lineChart', limit: 0 }, + { statistic_index_id: 7, chart_type: 'discreteBarChart', limit: 10 } + ]) end -if Group.count == 0 +if Group.count.zero? Group.create!([ - {name: 'standard, association', slug: 'standard'}, - {name: "étudiant, - de 25 ans, enseignant, demandeur d'emploi", slug: 'student'}, - {name: 'artisan, commerçant, chercheur, auto-entrepreneur', slug: 'merchant'}, - {name: 'PME, PMI, SARL, SA', slug: 'business'} - ]) + { name: 'standard, association', slug: 'standard' }, + { name: "étudiant, - de 25 ans, enseignant, demandeur d'emploi", slug: 'student' }, + { name: 'artisan, commerçant, chercheur, auto-entrepreneur', slug: 'merchant' }, + { name: 'PME, PMI, SARL, SA', slug: 'business' } + ]) end -unless Group.find_by(slug: 'admins') - Group.create! name: I18n.t('group.admins'), slug: 'admins' -end +Group.create! name: I18n.t('group.admins'), slug: 'admins' unless Group.find_by(slug: 'admins') # Create the default admin if none exists yet -if Role.where(name: 'admin').joins(:users).count === 0 - admin = User.new(username: 'admin', email: ENV["ADMIN_EMAIL"], password: ENV["ADMIN_PASSWORD"], password_confirmation: Rails.application.secrets.admin_password, group_id: Group.find_by(slug: 'admins').id, profile_attributes: {first_name: 'admin', last_name: 'admin', gender: true, phone: '0123456789', birthday: Time.now}) - admin.add_role 'admin' - admin.save! +if Role.where(name: 'admin').joins(:users).count.zero? + admin = User.new(username: 'admin', email: ENV['ADMIN_EMAIL'], password: ENV['ADMIN_PASSWORD'], + password_confirmation: Rails.application.secrets.admin_password, group_id: Group.find_by(slug: 'admins').id, + profile_attributes: { first_name: 'admin', last_name: 'admin', gender: true, phone: '0123456789', birthday: Time.now }) + admin.add_role 'admin' + admin.save! end -if Component.count == 0 +if Component.count.zero? Component.create!([ - {name: 'Silicone'}, - {name: 'Vinyle'}, - {name: 'Bois Contre plaqué'}, - {name: 'Bois Medium'}, - {name: 'Plexi / PMMA'}, - {name: 'Flex'}, - {name: 'Vinyle'}, - {name: 'Parafine'}, - {name: 'Fibre de verre'}, - {name: 'Résine'} - ]) + { name: 'Silicone' }, + { name: 'Vinyle' }, + { name: 'Bois Contre plaqué' }, + { name: 'Bois Medium' }, + { name: 'Plexi / PMMA' }, + { name: 'Flex' }, + { name: 'Vinyle' }, + { name: 'Parafine' }, + { name: 'Fibre de verre' }, + { name: 'Résine' } + ]) end -if Licence.count == 0 +if Licence.count.zero? Licence.create!([ - {name: 'Attribution (BY)', description: 'Le titulaire des droits autorise toute exploitation de l’œuvre, y compris à des fins commerciales, ainsi que la création d’œuvres dérivées, dont la distribution est également autorisé sans restriction, à condition de l’attribuer à son l’auteur en citant son nom. Cette licence est recommandée pour la diffusion et l’utilisation maximale des œuvres.'}, - {name: 'Attribution + Pas de modification (BY ND)', description: 'Le titulaire des droits autorise toute utilisation de l’œuvre originale (y compris à des fins commerciales), mais n’autorise pas la création d’œuvres dérivées.'}, - {name: "Attribution + Pas d'Utilisation Commerciale + Pas de Modification (BY NC ND)", description: 'Le titulaire des droits autorise l’utilisation de l’œuvre originale à des fins non commerciales, mais n’autorise pas la création d’œuvres dérivés.'}, - {name: "Attribution + Pas d'Utilisation Commerciale (BY NC)", description: 'Le titulaire des droits autorise l’exploitation de l’œuvre, ainsi que la création d’œuvres dérivées, à condition qu’il ne s’agisse pas d’une utilisation commerciale (les utilisations commerciales restant soumises à son autorisation).'}, - {name: "Attribution + Pas d'Utilisation Commerciale + Partage dans les mêmes conditions (BY NC SA)", description: 'Le titulaire des droits autorise l’exploitation de l’œuvre originale à des fins non commerciales, ainsi que la création d’œuvres dérivées, à condition qu’elles soient distribuées sous une licence identique à celle qui régit l’œuvre originale.'}, - {name: 'Attribution + Partage dans les mêmes conditions (BY SA)', description: 'Le titulaire des droits autorise toute utilisation de l’œuvre originale (y compris à des fins commerciales) ainsi que la création d’œuvres dérivées, à condition qu’elles soient distribuées sous une licence identique à celle qui régit l’œuvre originale. Cette licence est souvent comparée aux licences « copyleft » des logiciels libres. C’est la licence utilisée par Wikipedia.'} - ]) + { name: 'Attribution (BY)', description: 'Le titulaire des droits autorise toute exploitation de l’œuvre, y compris à' \ + ' des fins commerciales, ainsi que la création d’œuvres dérivées, dont la distribution est également autorisé sans ' \ + 'restriction, à condition de l’attribuer à son l’auteur en citant son nom. Cette licence est recommandée pour la ' \ + 'diffusion et l’utilisation maximale des œuvres.' }, + { name: 'Attribution + Pas de modification (BY ND)', description: 'Le titulaire des droits autorise toute utilisation' \ + ' de l’œuvre originale (y compris à des fins commerciales), mais n’autorise pas la création d’œuvres dérivées.' }, + { name: "Attribution + Pas d'Utilisation Commerciale + Pas de Modification (BY NC ND)", description: 'Le titulaire ' \ + 'des droits autorise l’utilisation de l’œuvre originale à des fins non commerciales, mais n’autorise pas la ' \ + 'création d’œuvres dérivés.' }, + { name: "Attribution + Pas d'Utilisation Commerciale (BY NC)", description: 'Le titulaire des droits autorise ' \ + 'l’exploitation de l’œuvre, ainsi que la création d’œuvres dérivées, à condition qu’il ne s’agisse pas d’une ' \ + 'utilisation commerciale (les utilisations commerciales restant soumises à son autorisation).' }, + { name: "Attribution + Pas d'Utilisation Commerciale + Partage dans les mêmes conditions (BY NC SA)", description: + 'Le titulaire des droits autorise l’exploitation de l’œuvre originale à des fins non commerciales, ainsi que la ' \ + 'création d’œuvres dérivées, à condition qu’elles soient distribuées sous une licence identique à celle qui régit ' \ + 'l’œuvre originale.' }, + { name: 'Attribution + Partage dans les mêmes conditions (BY SA)', description: 'Le titulaire des droits autorise ' \ + 'toute utilisation de l’œuvre originale (y compris à des fins commerciales) ainsi que la création d’œuvres dérivées' \ + ', à condition qu’elles soient distribuées sous une licence identique à celle qui régit l’œuvre originale. Cette' \ + 'licence est souvent comparée aux licences « copyleft » des logiciels libres. C’est la licence utilisée par ' \ + 'Wikipedia.' } + ]) end -if Theme.count == 0 +if Theme.count.zero? Theme.create!([ - {name: 'Vie quotidienne'}, - {name: 'Robotique'}, - {name: 'Arduine'}, - {name: 'Capteurs'}, - {name: 'Musique'}, - {name: 'Sport'}, - {name: 'Autre'} - ]) + { name: 'Vie quotidienne' }, + { name: 'Robotique' }, + { name: 'Arduine' }, + { name: 'Capteurs' }, + { name: 'Musique' }, + { name: 'Sport' }, + { name: 'Autre' } + ]) end -if Training.count == 0 +if Training.count.zero? Training.create!([ - {name: 'Formation Imprimante 3D', description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'}, - {name: 'Formation Laser / Vinyle', description: 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.'}, - {name: 'Formation Petite fraiseuse numerique', description: 'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.'}, - {name: 'Formation Shopbot Grande Fraiseuse', description: 'Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'}, - {name: 'Formation logiciel 2D', description: 'Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.'} - ]) + { name: 'Formation Imprimante 3D', description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do ' \ + 'eiusmod tempor incididunt ut labore et dolore magna aliqua.' }, + { name: 'Formation Laser / Vinyle', description: 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris' \ + ' nisi ut aliquip ex ea commodo consequat.' }, + { name: 'Formation Petite fraiseuse numerique', description: 'Duis aute irure dolor in reprehenderit in voluptate ' \ + 'velit esse cillum dolore eu fugiat nulla pariatur.' }, + { name: 'Formation Shopbot Grande Fraiseuse', description: 'Excepteur sint occaecat cupidatat non proident, sunt in ' \ + 'culpa qui officia deserunt mollit anim id est laborum.' }, + { name: 'Formation logiciel 2D', description: 'Sed ut perspiciatis unde omnis iste natus error sit voluptatem ' \ + 'accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi ' \ + 'architecto beatae vitae dicta sunt explicabo.' } + ]) TrainingsPricing.all.each do |p| p.update_columns(amount: (rand * 50 + 5).floor * 100) end end -if Machine.count == 0 +if Machine.count.zero? Machine.create!([ - {name: 'Découpeuse laser', description: "Préparation à l'utilisation de l'EPILOG Legend 36EXT\r\nInformations générales \r\n Pour la découpe, il suffit d'apporter votre fichier vectorisé type illustrator, svg ou dxf avec des \"lignes de coupe\" d'une épaisseur inférieur à 0,01 mm et la machine s'occupera du reste!\r\n La gravure est basée sur le spectre noir et blanc. Les nuances sont obtenues par différentes profondeurs de gravure correspondant aux niveaux de gris de votre image. Il suffit pour cela d'apporter une image scannée ou un fichier photo en noir et blanc pour pouvoir reproduire celle-ci sur votre support! \r\nQuels types de matériaux pouvons nous graver/découper?\r\n Du bois au tissu, du plexiglass au cuir, cette machine permet de découper et graver la plupart des matériaux sauf les métaux. La gravure est néanmoins possible sur les métaux recouverts d'une couche de peinture ou les aluminiums anodisés. \r\n Concernant l'épaisseur des matériaux découpés, il est préférable de ne pas dépasser 5 mm pour le bois et 6 mm pour le plexiglass.\r\n", spec: "Puissance: 40W\r\nSurface de travail: 914x609 mm \r\nEpaisseur maximale de la matière: 305mm\r\nSource laser: tube laser type CO2\r\nContrôles de vitesse et de puissance: ces deux paramètres sont ajustables en fonction du matériau (de 1% à 100%) .\r\n", slug: 'decoupeuse-laser'}, - {name: 'Découpeuse vinyle', description: "Préparation à l'utilisation de la Roland CAMM-1 GX24\r\nInformations générales \r\n Envie de réaliser un tee shirt personnalisé ? Un sticker à l'effigie votre groupe préféré? Un masque pour la réalisation d'un circuit imprimé? Pour cela, il suffit simplement de venir avec votre fichier vectorisé (ne pas oublier de vectoriser les textes) type illustrator svg ou dxf.\r\n \r\nMatériaux utilisés:\r\n Cette machine permet de découper principalement du vinyle,vinyle réfléchissant, flex.\r\n", spec: "Largeurs de support acceptées: de 50 mm à 700 mm\r\nVitesse de découpe: 50 cm/sec\r\nRésolution mécanique: 0,0125 mm/pas\r\n", slug: 'decoupeuse-vinyle'}, - {name: 'Shopbot / Grande fraiseuse', description: "La fraiseuse numérique ShopBot PRS standard\r\nInformations générales\r\nCette machine est un fraiseuse 3 axes idéale pour l'usinage de pièces de grandes dimensions. De la réalisation d'une chaise ou d'un meuble jusqu'à la construction d'une maison ou d'un assemblage immense, le ShopBot ouvre de nombreuses portes à votre imagination! \r\nMatériaux usinables\r\nLes principaux matériaux usinables sont le bois, le plastique, le laiton et bien d'autres.\r\nCette machine n'usine pas les métaux.\r\n", spec: "Surface maximale de travail: 2440x1220x150 (Z) mm\r\nLogiciel utilisé: Partworks 2D & 3D\r\nRésolution mécanique: 0,015 mm\r\nPrécision de la position: +/- 0,127mm\r\nFormats acceptés: DXF, STL \r\n", slug: 'shopbot-grande-fraiseuse'}, - {name: 'Imprimante 3D', description: "L'utimaker est une imprimante 3D low cost utilisant une technologie FFF (Fused Filament Fabrication) avec extrusion thermoplastique.\r\nC'est une machine idéale pour réaliser rapidement des prototypes 3D dans des couleurs différentes.\r\n", spec: "Surface maximale de travail: 210x210x220mm \r\nRésolution méchanique: 0,02 mm \r\nPrécision de position: +/- 0,05 \r\nLogiciel utilisé: Cura\r\nFormats de fichier acceptés: STL \r\nMatériaux utilisés: PLA (en stock).", slug: 'imprimante-3d'}, - {name: 'Petite Fraiseuse', description: "La fraiseuse numérique Roland Modela MDX-20\r\nInformations générales\r\nCette machine est utilisée pour l'usinage et le scannage 3D de précision. Elle permet principalement d'usiner des circuits imprimés et des moules de petite taille. Le faible diamètre des fraises utilisées (Ø 0,3 mm à Ø 6mm) induit que certains temps d'usinages peuvent êtres long (> 12h), c'est pourquoi cette fraiseuse peut être laissée en autonomie toute une nuit afin d'obtenir le plus précis des usinages au FabLab.\r\nMatériaux usinables:\r\nLes principaux matériaux usinables sont le bois, plâtre, résine, cire usinable, cuivre.\r\n", spec: "Taille du plateau X/Y : 220 mm x 160 mm\r\nVolume maximal de travail: 203,2 mm (X), 152,4 mm (Y), 60,5 mm (Z)\r\nPrécision usinage: 0,00625 mm\r\nPrécision scannage: réglable de 0,05 à 5 mm (axes X,Y) et 0,025 mm (axe Z)\r\nVitesse d'analyse (scannage): 4-15 mm/sec\r\n \r\n \r\nLogiciel utilisé pour le fraisage: Roland Modela player 4 \r\nLogiciel utilisé pour l'usinage de circuits imprimés: Cad.py (linux)\r\nFormats acceptés: STL,PNG 3D\r\nFormat d'exportation des données scannées: DXF, VRML, STL, 3DMF, IGES, Grayscale, Point Group et BMP\r\n", slug: 'petite-fraiseuse'}, - {name: 'FORM1+ imprimante 3D', description: "Form 1+, imprimante 3D stéréolithographie.\n\nLa photopolymérisation est le premier procédé de prototypage rapide à avoir été développé dans les années 1980. Le nom de SLA (pour StereoLithography Apparatus) lui a été donné. Il repose sur les propriétés qu'ont certaines résines à se polymériser sous l'effet de la lumière et de la chaleur. (Source : wikipédia)\n\nPossibilité d'utiliser 3 résines de couleurs différentes au Lab : noir, blanc et translucide.\n\nPlus d'infos sur le site web de Formlab", spec: "Imprimante :\n- Dimensions : 30 × 28 × 45 cm\n- Poids : 8 kg\n- Température d'utilisation : 18–28° C\n- Alimentation : 100–240 V ; 1.5 A 50/60 Hz ; 60 W.\n\nCaractéristiques du laser :\n- EN 60825-1:2007 certifié\n- Class 1 Laser Product\n- 405nm violet laser\n\nPropriétés d'impression :\n- Technologie stéréolithographie (SLA)\n- Volume d'impression : 125 × 125 × 165 mm\n- Dimension minimale : 300 microns\n- Épaisseur des couches (Résolution verticale) : 25, 50, 100 microns\n- Ressources Générées automatiquement\n- Facilement amovible", slug: 'form1-imprimante-3d'} - ]) + { name: 'Découpeuse laser', description: "Préparation à l'utilisation de l'EPILOG Legend 36EXT\r\nInformations" \ + " générales \r\n Pour la découpe, il suffit d'apporter votre fichier vectorisé type illustrator, svg ou dxf" \ + " avec des \"lignes de coupe\" d'une épaisseur inférieur à 0,01 mm et la machine s'occupera du reste!\r\n La " \ + 'gravure est basée sur le spectre noir et blanc. Les nuances sont obtenues par différentes profondeurs de gravure ' \ + "correspondant aux niveaux de gris de votre image. Il suffit pour cela d'apporter une image scannée ou un fichier " \ + "photo en noir et blanc pour pouvoir reproduire celle-ci sur votre support! \r\nQuels types de matériaux pouvons " \ + "nous graver/découper?\r\n Du bois au tissu, du plexiglass au cuir, cette machine permet de découper et graver " \ + "la plupart des matériaux sauf les métaux. La gravure est néanmoins possible sur les métaux recouverts d'une couche" \ + " de peinture ou les aluminiums anodisés. \r\n Concernant l'épaisseur des matériaux découpés, il est " \ + "préférable de ne pas dépasser 5 mm pour le bois et 6 mm pour le plexiglass.\r\n", spec: "Puissance: 40W\r\nSurface" \ + " de travail: 914x609 mm \r\nEpaisseur maximale de la matière: 305mm\r\nSource laser: tube laser type CO2\r\n" \ + 'Contrôles de vitesse et de puissance: ces deux paramètres sont ajustables en fonction du matériau (de 1% à 100%) .' \ + "\r\n", slug: 'decoupeuse-laser' }, + { name: 'Découpeuse vinyle', description: "Préparation à l'utilisation de la Roland CAMM-1 GX24\r\nInformations " \ + "générales \r\n Envie de réaliser un tee shirt personnalisé ? Un sticker à l'effigie votre groupe " \ + "préféré ? Un masque pour la réalisation d'un circuit imprimé? Pour cela, il suffit simplement de venir avec votre" \ + " fichier vectorisé (ne pas oublier de vectoriser les textes) type illustrator svg ou dxf.\r\n \r\nMatériaux " \ + "utilisés:\r\n Cette machine permet de découper principalement du vinyle,vinyle réfléchissant, flex.\r\n", + spec: "Largeurs de support acceptées: de 50 mm à 700 mm\r\nVitesse de découpe: 50 cm/sec\r\nRésolution mécanique: " \ + "0,0125 mm/pas\r\n", slug: 'decoupeuse-vinyle' }, + { name: 'Shopbot / Grande fraiseuse', description: "La fraiseuse numérique ShopBot PRS standard\r\nInformations " \ + "générales\r\nCette machine est un fraiseuse 3 axes idéale pour l'usinage de pièces de grandes dimensions. De la " \ + "réalisation d'une chaise ou d'un meuble jusqu'à la construction d'une maison ou d'un assemblage immense, le " \ + "ShopBot ouvre de nombreuses portes à votre imagination! \r\nMatériaux usinables\r\nLes principaux matériaux " \ + "usinables sont le bois, le plastique, le laiton et bien d'autres.\r\nCette machine n'usine pas les métaux.\r\n", + spec: "Surface maximale de travail: 2440x1220x150 (Z) mm\r\nLogiciel utilisé: Partworks 2D & 3D\r\nRésolution " \ + "mécanique: 0,015 mm\r\nPrécision de la position: +/- 0,127mm\r\nFormats acceptés: DXF, STL \r\n", + slug: 'shopbot-grande-fraiseuse' }, + { name: 'Imprimante 3D', description: "L'utimaker est une imprimante 3D low cost utilisant une technologie FFF " \ + "(Fused Filament Fabrication) avec extrusion thermoplastique.\r\nC'est une machine idéale pour réaliser rapidement " \ + "des prototypes 3D dans des couleurs différentes.\r\n", spec: "Surface maximale de travail: 210x210x220mm \r\n" \ + "Résolution méchanique: 0,02 mm \r\nPrécision de position: +/- 0,05 \r\nLogiciel utilisé: Cura\r\nFormats de " \ + "fichier acceptés: STL \r\nMatériaux utilisés: PLA (en stock).", slug: 'imprimante-3d' }, + { name: 'Petite Fraiseuse', description: "La fraiseuse numérique Roland Modela MDX-20\r\nInformations générales" \ + "\r\nCette machine est utilisée pour l'usinage et le scannage 3D de précision. Elle permet principalement d'usiner" \ + ' des circuits imprimés et des moules de petite taille. Le faible diamètre des fraises utilisées (Ø 0,3 mm à Ø 6mm' \ + ") induit que certains temps d'usinages peuvent êtres long (> 12h), c'est pourquoi cette fraiseuse peut être " \ + "laissée en autonomie toute une nuit afin d'obtenir le plus précis des usinages au FabLab.\r\nMatériaux usinables:" \ + "\r\nLes principaux matériaux usinables sont le bois, plâtre, résine, cire usinable, cuivre.\r\n", + spec: "Taille du plateau X/Y : 220 mm x 160 mm\r\nVolume maximal de travail: 203,2 mm (X), 152,4 mm (Y), 60,5 mm " \ + "(Z)\r\nPrécision usinage: 0,00625 mm\r\nPrécision scannage: réglable de 0,05 à 5 mm (axes X,Y) et 0,025 mm (axe Z)" \ + "\r\nVitesse d'analyse (scannage): 4-15 mm/sec\r\n \r\n \r\nLogiciel utilisé pour le fraisage: Roland Modela player" \ + " 4 \r\nLogiciel utilisé pour l'usinage de circuits imprimés: Cad.py (linux)\r\nFormats acceptés: STL,PNG 3D\r\n" \ + "Format d'exportation des données scannées: DXF, VRML, STL, 3DMF, IGES, Grayscale, Point Group et BMP\r\n", + slug: 'petite-fraiseuse' }, + ]) Price.all.each do |p| - p.update_columns(amount: (rand()*50+5).floor*100) + p.update_columns(amount: (rand * 50 + 5).floor * 100) end end -# if Plan.count == 0 -# Group.all.each do |group| -# %w(month year).each do |interval| -# Plan.create!(base_name: "plan #{SecureRandom.hex(4)}", amount: (rand()*200+50).floor*100, interval: interval, group: group) -# end -# end -# end if Category.count.zero? Category.create!( @@ -202,12 +256,12 @@ unless Setting.find_by(name: 'about_contacts').try(:value) setting = Setting.find_or_initialize_by(name: 'about_contacts') setting.value = '
    ' \ '
    Manager Fab Lab :
    ' \ - '
    jean.dupont@fab-manager.com
    ' \ - '
    Responsable médiation :
    ' \ - '
    pierre.dupond@fab-manager.com
    ' \ - '
    Animateur scientifique :
    ' \ - '
    louise.durand@fab-manager.com
    ' \ - '
    ' \ + '
    contact@fab-manager.com
    ' \ + '
    Responsable médiation :
    ' \ + '
    contact@fab-manager.com
    ' \ + '
    Animateur scientifique :
    ' \ + '
    lcontact@fab-manager.com
    ' \ + '' \ '

    ' \ "

    Visitez le site de Fab-manager

    " setting.save @@ -221,37 +275,48 @@ end unless Setting.find_by(name: 'machine_explications_alert').try(:value) setting = Setting.find_or_initialize_by(name: 'machine_explications_alert') - setting.value = "Tout achat d'heure machine est définitif. Aucune"+ - ' annulation ne pourra être effectuée, néanmoins au plus tard 24h avant le créneau fixé, vous pouvez en'+ - " modifier la date et l'horaire à votre convenance et en fonction du calendrier proposé. Passé ce délais,"+ + setting.value = "Tout achat d'heure machine est définitif. Aucune" \ + ' annulation ne pourra être effectuée, néanmoins au plus tard 24h avant le créneau fixé, vous pouvez en' \ + " modifier la date et l'horaire à votre convenance et en fonction du calendrier proposé. Passé ce délais," \ ' aucun changement ne pourra être effectué.' setting.save end unless Setting.find_by(name: 'training_explications_alert').try(:value) setting = Setting.find_or_initialize_by(name: 'training_explications_alert') - setting.value = 'Toute réservation de formation est définitive.'+ - ' Aucune annulation ne pourra être effectuée, néanmoins au plus tard 24h avant le créneau fixé, vous pouvez'+ - " en modifier la date et l'horaire à votre convenance et en fonction du calendrier proposé. Passé ce délais,"+ + setting.value = 'Toute réservation de formation est définitive.' \ + ' Aucune annulation ne pourra être effectuée, néanmoins au plus tard 24h avant le créneau fixé, vous pouvez' \ + " en modifier la date et l'horaire à votre convenance et en fonction du calendrier proposé. Passé ce délais," \ ' aucun changement ne pourra être effectué.' setting.save end unless Setting.find_by(name: 'subscription_explications_alert').try(:value) setting = Setting.find_or_initialize_by(name: 'subscription_explications_alert') - setting.value = '

    Règle sur la date de début des abonnements

    • '+ - " Si vous êtes un nouvel utilisateur - i.e aucune "+ - " formation d'enregistrée sur le site - votre abonnement débutera à la date de réservation de votre première "+ - " formation.
    • Si vous avez déjà une "+ - " formation ou plus de validée, votre abonnement débutera à la date de votre achat d'abonnement.
    • "+ - "

    Merci de bien prendre ses informations en compte, et merci de votre compréhension. L'équipe du Fab Lab.
    "+ + setting.value = '

    Règle sur la date de début des abonnements

    • ' \ + ' Si vous êtes un nouvel utilisateur - i.e aucune ' \ + " formation d'enregistrée sur le site - votre abonnement débutera à la date de réservation de votre première " \ + ' formation.
    • Si vous avez déjà une ' \ + " formation ou plus de validée, votre abonnement débutera à la date de votre achat d'abonnement.
    • " \ + "

    Merci de bien prendre ses informations en compte, et merci de votre compréhension. L'équipe du Fab Lab.
    " \ '

    ' setting.save end unless Setting.find_by(name: 'invoice_logo').try(:value) setting = Setting.find_or_initialize_by(name: 'invoice_logo') - setting.value = 'iVBORw0KGgoAAAANSUhEUgAAAyAAAABNCAYAAABe8gBxAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA/RpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ1dWlkOjVEMjA4OTI0OTNCRkRCMTE5MTRBODU5MEQzMTUwOEM4IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjAzODFDRjYwMEE1RTExRTQ5NzJDRkFDOTI4MTJEOEM1IiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjAzODFDRjVGMEE1RTExRTQ5NzJDRkFDOTI4MTJEOEM1IiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIElsbHVzdHJhdG9yIENTNCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ1dWlkOjI2NzQ5N2UwLTgyODEtNDg4Ny1iOGZlLTExMzA0ODhhZjRhOCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3RDc4OUVFODZFRjBFMzEx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' + setting.value = 'iVBORw0KGgoAAAANSUhEUgAAAG0AAABZCAYAAAA0E6rtAAAACXBIWXMAAAsTAAALEwEAmpwYAAA57WlUWHRYTUw6Y29tLmFkb2JlLnhtc' \ + 'AAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PS' \ + 'JhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxMzggNzkuMTU5ODI0LCAyMDE2LzA5LzE0LTAxOjA5OjA' \ + 'xICAgICAgICAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMi' \ + 'PgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb' \ + '20veGFwLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgICAgICAgIC' \ + 'AgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIgogICAgICAgICAgICB4bWxuczp4bXBNTT0' \ + 'iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIKICAgICAgICAgICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20v' \ + 'eGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmL' \ + 'zEuMC8iCiAgICAgICAgICAgIHhtbG5zOmV4aWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIj4KICAgICAgICAgPHhtcDpDcm' \ + 'VhdG9yVG9vbD5BZG9iZSBQaG90b3Nob3AgQ0MgMjAxNyAoV2luZG93cyk8L3htcDpDcmVhdG9yVG9vbD4KICAgICAgICAgPHhtcDpDcmV' \ + 'hdGVEYXRlPjIwMTctMDEtMDNUMTE6MTg6MTgrMDE6MDA8L3htcDpDcmVhdGVEYXRlPgogICAgICAgICA8eG1wOk1vZGlmeURhdGU' setting.save end @@ -427,36 +492,40 @@ unless Setting.find_by(name: 'machines_sort_by').try(:value) setting.save end -if StatisticCustomAggregation.count == 0 +if StatisticCustomAggregation.count.zero? # available reservations hours for machines machine_hours = StatisticType.find_by(key: 'hour', statistic_index_id: 2) - available_hours = StatisticCustomAggregation.new({ - statistic_type_id: machine_hours.id, - es_index: 'fablab', - es_type: 'availabilities', - field: 'available_hours', - query: '{"size":0, "aggregations":{"%{aggs_name}":{"sum":{"field":"bookable_hours"}}}, "query":{"bool":{"must":[{"range":{"start_at":{"gte":"%{start_date}", "lte":"%{end_date}"}}}, {"match":{"available_type":"machines"}}]}}}' - }) + available_hours = StatisticCustomAggregation.new( + statistic_type_id: machine_hours.id, + es_index: 'fablab', + es_type: 'availabilities', + field: 'available_hours', + query: '{"size":0, "aggregations":{"%{aggs_name}":{"sum":{"field":"bookable_hours"}}}, "query":{"bool":{"must":[{"range":' \ + '{"start_at":{"gte":"%{start_date}", "lte":"%{end_date}"}}}, {"match":{"available_type":"machines"}}]}}}' + ) available_hours.save! # available training tickets training_bookings = StatisticType.find_by(key: 'booking', statistic_index_id: 3) - available_tickets = StatisticCustomAggregation.new({ - statistic_type_id: training_bookings.id, - es_index: 'fablab', - es_type: 'availabilities', - field: 'available_tickets', - query: '{"size":0, "aggregations":{"%{aggs_name}":{"sum":{"field":"nb_total_places"}}}, "query":{"bool":{"must":[{"range":{"start_at":{"gte":"%{start_date}", "lte":"%{end_date}"}}}, {"match":{"available_type":"training"}}]}}}' - }) + available_tickets = StatisticCustomAggregation.new( + statistic_type_id: training_bookings.id, + es_index: 'fablab', + es_type: 'availabilities', + field: 'available_tickets', + query: '{"size":0, "aggregations":{"%{aggs_name}":{"sum":{"field":"nb_total_places"}}}, "query":{"bool":{"must":[{"range":' \ + '{"start_at":{"gte":"%{start_date}", "lte":"%{end_date}"}}}, {"match":{"available_type":"training"}}]}}}' + ) available_tickets.save! end unless StatisticIndex.find_by(es_type_key: 'space') - index = StatisticIndex.create!({es_type_key:'space', label:I18n.t('statistics.spaces')}) + index = StatisticIndex.create!(es_type_key: 'space', label: I18n.t('statistics.spaces')) StatisticType.create!([ - {statistic_index_id: index.id, key: 'booking', label:I18n.t('statistics.bookings'), graph: true, simple: true}, - {statistic_index_id: index.id, key: 'hour', label:I18n.t('statistics.hours_number'), graph: true, simple: false} - ]) + { statistic_index_id: index.id, key: 'booking', label: I18n.t('statistics.bookings'), + graph: true, simple: true }, + { statistic_index_id: index.id, key: 'hour', label: I18n.t('statistics.hours_number'), + graph: true, simple: false } + ]) end From 01ac73a1a1c447326b29b0e1ecf4c6b542403010 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 26 Mar 2019 10:40:58 +0100 Subject: [PATCH 60/75] removed capistrano & unicorn + cleaned lacasemate brading --- Gemfile | 7 +- app/views/application/index.html.erb | 11 +-- config/deploy.rb | 121 --------------------------- config/deploy/production.rb | 16 ---- config/deploy/staging.rb | 66 --------------- config/nginx.conf | 64 -------------- config/nginx_puma.conf | 44 ---------- config/nginx_staging.conf | 54 ------------ config/unicorn.rb | 18 ---- config/unicorn_init.sh | 84 ------------------- config/unicorn_init_staging.sh | 84 ------------------- config/unicorn_staging.rb | 18 ---- 12 files changed, 4 insertions(+), 583 deletions(-) delete mode 100644 config/deploy.rb delete mode 100644 config/deploy/production.rb delete mode 100644 config/deploy/staging.rb delete mode 100644 config/nginx.conf delete mode 100644 config/nginx_puma.conf delete mode 100644 config/nginx_staging.conf delete mode 100644 config/unicorn.rb delete mode 100755 config/unicorn_init.sh delete mode 100755 config/unicorn_init_staging.sh delete mode 100644 config/unicorn_staging.rb diff --git a/Gemfile b/Gemfile index 457d8da2b..4345a8f84 100644 --- a/Gemfile +++ b/Gemfile @@ -17,7 +17,7 @@ gem 'jquery-rails' gem 'jbuilder', '~> 2.5' gem 'jbuilder_cache_multi' # bundle exec rake doc:rails generates the API under doc/api. -gem 'sdoc', '~> 0.4.0', group: :doc #TODO remove unused ? +gem 'sdoc', '~> 0.4.0', group: :doc # TODO, remove unused ? gem 'forgery' gem 'responders', '~> 2.0' @@ -41,16 +41,12 @@ end group :development do gem 'active_record_query_trace' gem 'awesome_print' - gem 'capistrano' - gem 'capistrano-maintenance', '0.0.5', require: false - gem 'capistrano-sidekiq', require: false gem 'coveralls', require: false gem 'foreman' # Preview mail in the browser gem 'mailcatcher' gem 'puma' gem 'rb-readline' - gem 'rvm-capistrano', require: false end group :test do @@ -66,7 +62,6 @@ end group :production do gem 'rails_12factor' - gem 'unicorn' end gem 'seed_dump' diff --git a/app/views/application/index.html.erb b/app/views/application/index.html.erb index 84703a1ca..e1ffd033d 100644 --- a/app/views/application/index.html.erb +++ b/app/views/application/index.html.erb @@ -9,14 +9,9 @@ <%=Setting.find_by(name: 'fablab_name').value%> - <% if ENV['DEFAULT_HOST'] == 'fablab.lacasemate.fr' %> - - - <% else %> - - - - <% end %> + + +