From 30e9f6febfcadfe756e70df5e1b44ed0b3ca5375 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Mon, 1 Feb 2021 17:43:15 +0100 Subject: [PATCH] OpenAPI endpoints to create/update/show/delete machines --- CHANGELOG.md | 9 +- .../open_api/v1/base_controller.rb | 58 ++++++----- .../open_api/v1/invoices_controller.rb | 20 ++-- .../open_api/v1/machines_controller.rb | 44 +++++++++ app/doc/open_api/v1/machines_doc.rb | 96 +++++++++++++++++++ .../v1/machines/_machine.json.jbuilder | 2 + .../open_api/v1/machines/create.json.jbuilder | 3 + .../open_api/v1/machines/index.json.jbuilder | 2 + .../open_api/v1/machines/show.json.jbuilder | 5 + .../open_api/v1/machines/update.json.jbuilder | 3 + config/locales/en.yml | 1 + config/locales/fr.yml | 1 + config/routes.rb | 2 +- 13 files changed, 210 insertions(+), 36 deletions(-) create mode 100644 app/views/open_api/v1/machines/create.json.jbuilder create mode 100644 app/views/open_api/v1/machines/show.json.jbuilder create mode 100644 app/views/open_api/v1/machines/update.json.jbuilder diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d7123364..97328045a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,11 @@ # Changelog Fab-manager ## Next release +- Full German translation (thanks to [@korrupt](https://crowdin.com/profile/korrupt)) +- OpenAPI endpoints to create/update/show/delete machines - Updated environment documentation - Removed useless locales' configuration files +- OpenAPI's endpoints will now return more detailed error messages when something wrong occurs - Fix a security issue: updated ini to 1.3.8 to fix [CVE-2020-7788](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-7788) − Fix a security issue: updated nokogiri to 1.11.1 to fix [CVE-2020-26247](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-26247) - Updated caxlsx to 3.0.4, and the dependencies of caxlsx_rail @@ -27,7 +30,7 @@ - Removed fab-manager email address from the seeds - Initialize new plans with default prices for machines & spaces - Display a message when no plans are available -- Fix a bug: in the settings area, boolean switches are always shown as false +- Fix a bug: in the settings' area, boolean switches are always shown as false - Fix a bug: public cards presenting the plans in the public area, have bogus style - Fix a bug: theme primary color is ignored on links - [TODO DEPLOY] `rails fablab:maintenance:rebuild_stylesheet` @@ -36,7 +39,7 @@ - Add intermediate step version for upgrades: v4.4.6. This will prevent issues with FootprintDebug if a regeneration is needed - Check postgreSQL status before compiling assets -- Improved documentation about the upgrade process +- Improved the documentation about the upgrade process - Fix a bug: unable to set libraries locales to their default values (en-us) - Fix a bug: unable to display details about a closed period - Fix a bug: members cannot view available trainings slots @@ -44,7 +47,7 @@ ## v4.6.1 2020 October 21 -- Reduced down time during upgrades +- Reduced downtime during upgrades - Architecture changes to allow including React.js components into the application - Allow running upgrade scripts from dev ranch - Fix a bug: script mount-webpack.sh was not updating the docker-compose.yml file diff --git a/app/controllers/open_api/v1/base_controller.rb b/app/controllers/open_api/v1/base_controller.rb index 0fc111893..20f3b5fc7 100644 --- a/app/controllers/open_api/v1/base_controller.rb +++ b/app/controllers/open_api/v1/base_controller.rb @@ -1,5 +1,9 @@ +# frozen_string_literal: true + +# Parameters for OpenAPI endpoints class OpenAPI::V1::BaseController < ActionController::Base protect_from_forgery with: :null_session + skip_before_action :verify_authenticity_token before_action :authenticate before_action :increment_calls_count @@ -7,37 +11,47 @@ class OpenAPI::V1::BaseController < ActionController::Base rescue_from OpenAPI::ParameterError, with: :bad_request rescue_from ActionController::ParameterMissing, with: :bad_request + rescue_from TypeError, with: :server_error + rescue_from NoMethodError, with: :server_error + rescue_from ArgumentError, with: :server_error + helper_method :current_api_client protected - def not_found - render json: { errors: ["Not found"] }, status: :not_found - end - def bad_request - render json: { errors: ["Bad request"] }, status: :bad_request - end + def not_found(exception) + render json: { errors: exception }, status: :not_found + end - def authenticate - authenticate_token || render_unauthorized - end + def bad_request(exception) + render json: { errors: exception }, status: :bad_request + end - def authenticate_token - authenticate_with_http_token do |token, options| - @open_api_client = OpenAPI::Client.find_by(token: token) - end - end + def server_error(exception) + render json: { error: exception }, status: :internal_server_error + end - def current_api_client - @open_api_client - end + def authenticate + authenticate_token || render_unauthorized + end - def render_unauthorized - render json: { errors: ['Bad credentials'] }, status: 401 + def authenticate_token + authenticate_with_http_token do |token, _options| + @open_api_client = OpenAPI::Client.find_by(token: token) end + end + + def current_api_client + @open_api_client + end + + def render_unauthorized + render json: { errors: ['Bad credentials'] }, status: 401 + end private - def increment_calls_count - @open_api_client.increment_calls_count - end + + def increment_calls_count + @open_api_client.increment_calls_count + end end diff --git a/app/controllers/open_api/v1/invoices_controller.rb b/app/controllers/open_api/v1/invoices_controller.rb index 3dd1e9b15..985f01ba4 100644 --- a/app/controllers/open_api/v1/invoices_controller.rb +++ b/app/controllers/open_api/v1/invoices_controller.rb @@ -1,3 +1,4 @@ +# OpenAPI controller for the invoices class OpenAPI::V1::InvoicesController < OpenAPI::V1::BaseController extend OpenAPI::ApiDoc expose_doc @@ -5,14 +6,12 @@ class OpenAPI::V1::InvoicesController < OpenAPI::V1::BaseController def index @invoices = Invoice.order(created_at: :desc) - if params[:user_id].present? - @invoices = @invoices.where(user_id: params[:user_id]) - end + @invoices = @invoices.where(user_id: params[:user_id]) if params[:user_id].present? - if params[:page].present? - @invoices = @invoices.page(params[:page]).per(per_page) - paginate @invoices, per_page: per_page - end + return unless params[:page].present? + + @invoices = @invoices.page(params[:page]).per(per_page) + paginate @invoices, per_page: per_page end def download @@ -21,7 +20,8 @@ class OpenAPI::V1::InvoicesController < OpenAPI::V1::BaseController end private - def per_page - params[:per_page] || 20 - end + + def per_page + params[:per_page] || 20 + end end diff --git a/app/controllers/open_api/v1/machines_controller.rb b/app/controllers/open_api/v1/machines_controller.rb index c998afffe..88462a071 100644 --- a/app/controllers/open_api/v1/machines_controller.rb +++ b/app/controllers/open_api/v1/machines_controller.rb @@ -1,8 +1,52 @@ +# frozen_string_literal: true + +# authorized 3rd party softwares can manage the machines through the OpenAPI class OpenAPI::V1::MachinesController < OpenAPI::V1::BaseController extend OpenAPI::ApiDoc expose_doc + before_action :set_machine, only: %i[show update destroy] + def index @machines = Machine.order(:created_at) end + + def create + @machine = Machine.new(machine_params) + if @machine.save + render :show, status: :created, location: @machine + else + render json: @machine.errors, status: :unprocessable_entity + end + end + + def update + if @machine.update(machine_params) + render :show, status: :ok, location: @machine + else + render json: @machine.errors, status: :unprocessable_entity + end + end + + def show; end + + def destroy + if @machine.destroyable? + @machine.destroy + head :no_content + else + render json: { error: 'has existing reservations' }, status: :unprocessable_entity + end + end + + private + + def machine_params + params.require(:machine).permit(:name, :description, :spec, :disabled, + machine_image_attributes: [:attachment]) + end + + def set_machine + @machine = Machine.friendly.find(params[:id]) + end end diff --git a/app/doc/open_api/v1/machines_doc.rb b/app/doc/open_api/v1/machines_doc.rb index 42edb050a..d613bb0cf 100644 --- a/app/doc/open_api/v1/machines_doc.rb +++ b/app/doc/open_api/v1/machines_doc.rb @@ -83,4 +83,100 @@ class OpenAPI::V1::MachinesDoc < OpenAPI::V1::BaseDoc } MACHINES end + + doc_for :create do + api :POST, "/#{API_VERSION}/machines", 'Create a machine' + formats %w[json multipart/form-data] + description 'Create a new machine.' + returns code: 201, desc: 'The machine was successfully created' + param :machine, Hash, required: true do + param :name, String, desc: 'The name of the machine.', required: true + param :description, String, desc: 'A long textual description of the machine. HTML is supported.', required: true + param :spec, String, desc: 'A long textual description of the technical specifications of the machine. HTML is supported.' + param :disabled, [TrueClass, FalseClass], desc: "Should the machine be disabled? If yes, the machine won't be reservable and will be shown apart." + param :machine_image_attributes, Hash do + param :attachment, ActionDispatch::Http::UploadedFile, required: true, desc: 'Upload a picture for the machine.' + end + end + example <<-MACHINE + curl -X POST + -H "Authorization:Token token=xxx" + -H "Content-Type:multipart/form-data" + -H "Accept: application/json" + -F machine[name]="Epilog laser" + -F machine[description]="La découpeuse laser vous permet de découper ou graver des matériaux." + -F machine[machine_image_attributes[attachment]]=@epilog.jpeg + /open_api/v1/machines + + curl -X POST + -H "Authorization:Token token=xxx" + -H "Content-Type:application/json" + -H "Accept: application/json" + -d '{"machine": { "name": "DMP Flex 100", "description": "Cette imprimante 3D peut imprimer des métaux." }}' + /open_api/v1/machines + MACHINE + end + + doc_for :update do + api :PATCH, "/#{API_VERSION}/machines/:id", 'Update a machine' + formats %w[json multipart/form-data] + description 'Update an existing machine.' + returns code: 200, desc: 'The machine was successfully updated' + param :machine, Hash, required: true do + param :name, String, desc: 'The name of the machine.', required: true + param :description, String, desc: 'A long textual description of the machine. HTML is supported.', required: true + param :spec, String, desc: 'A long textual description of the technical specifications of the machine. HTML is supported.' + param :disabled, [TrueClass, FalseClass], desc: "Should the machine be disabled? If yes, the machine won't be reservable and will be shown apart." + param :machine_image_attributes, Hash do + param :attachment, ActionDispatch::Http::UploadedFile, required: true, desc: 'Upload a picture for the machine.' + end + end + example <<-MACHINE + curl -X PATCH + -H "Authorization:Token token=xxx" + -H "Content-Type:multipart/form-data" + -H "Accept: application/json" + -F machine[spec]="Laser CO2 de 60W
Surface de travail de 812 x 508 mm" + -F machine[machine_image_attributes[attachment]]=@epilog2.jpg + /open_api/v1/machines/10 + + curl -X PATCH + -H "Authorization:Token token=xxx" + -H "Content-Type:application/json" + -H "Accept: application/json" + -d '{"machine": { "disabled": true }}' + /open_api/v1/machines/10 + MACHINE + end + + doc_for :show do + api :GET, "/#{API_VERSION}/machines/:id", 'Show a machine' + description 'Show all the details of single machine.' + example <<-MACHINES + # /open_api/v1/machines/1 + { + "id": 1, + "name": "Epilog EXT36 Laser", + "slug": "decoupeuse-laser", + "disabled": false, + "updated_at": "2015-02-17T11:06:00.495+01:00", + "created_at": "2014-06-30T03:32:31.972+02:00", + "description": "La découpeuse Laser, EPILOG Legend 36EXT\r\n\r\nInformations générales :\r\nLa découpeuse laser vous permet de découper ou graver des matériaux. \r\n\r\nPour la découpe, il suffit d'apporter votre fichier vectorisé type illustrator, svg ou dxf avec des \"lignes de coupe\" d'une épaisseur inférieure à 0,01 mm et la machine s'occupera du reste!\r\n\r\nLa 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\n\r\nTypes de matériaux gravables/découpeables ?\r\nDu 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\nConcernant 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", + "image": "/uploads/machine_image/2514/machine_image.jpg" + } + MACHINES + end + + doc_for :destroy do + api :DELETE, "/#{API_VERSION}/machines/:id", 'Delete a machine' + description 'Delete an existing machine that does not have any existing reservations.' + returns code: 204, desc: 'The machine was successfully deleted' + example <<-MACHINE + curl -X DELETE + -H "Authorization:Token token=xxx" + -H "Accept: application/json" + /open_api/v1/machines/10 + MACHINE + end end diff --git a/app/views/open_api/v1/machines/_machine.json.jbuilder b/app/views/open_api/v1/machines/_machine.json.jbuilder index 7d287fb59..0d42b869b 100644 --- a/app/views/open_api/v1/machines/_machine.json.jbuilder +++ b/app/views/open_api/v1/machines/_machine.json.jbuilder @@ -1 +1,3 @@ +# frozen_string_literal: true + json.extract! machine, :id, :name, :slug, :disabled, :updated_at, :created_at diff --git a/app/views/open_api/v1/machines/create.json.jbuilder b/app/views/open_api/v1/machines/create.json.jbuilder new file mode 100644 index 000000000..f1a35b3bd --- /dev/null +++ b/app/views/open_api/v1/machines/create.json.jbuilder @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +json.partial! 'open_api/v1/machines/machine', machine: @machine diff --git a/app/views/open_api/v1/machines/index.json.jbuilder b/app/views/open_api/v1/machines/index.json.jbuilder index d6f67d1ee..3f9a07ca2 100644 --- a/app/views/open_api/v1/machines/index.json.jbuilder +++ b/app/views/open_api/v1/machines/index.json.jbuilder @@ -1,3 +1,5 @@ +# frozen_string_literal: true + json.machines @machines do |machine| json.partial! 'open_api/v1/machines/machine', machine: machine json.extract! machine, :description, :spec diff --git a/app/views/open_api/v1/machines/show.json.jbuilder b/app/views/open_api/v1/machines/show.json.jbuilder new file mode 100644 index 000000000..5aab5c924 --- /dev/null +++ b/app/views/open_api/v1/machines/show.json.jbuilder @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +json.partial! 'open_api/v1/machines/machine', machine: @machine +json.extract! @machine, :description, :spec +json.image URI.join(root_url, @machine.machine_image.attachment.url) if @machine.machine_image diff --git a/app/views/open_api/v1/machines/update.json.jbuilder b/app/views/open_api/v1/machines/update.json.jbuilder new file mode 100644 index 000000000..f1a35b3bd --- /dev/null +++ b/app/views/open_api/v1/machines/update.json.jbuilder @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +json.partial! 'open_api/v1/machines/machine', machine: @machine diff --git a/config/locales/en.yml b/config/locales/en.yml index 498cb5f90..d7f759b77 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -39,6 +39,7 @@ en: must_be_in_the_past: "The period must be strictly prior to today's date." apipie: api_documentation: "API Documentation" + code: "HTTP code" #error messages when importing an account from a SSO omniauth: email_already_linked_to_another_account_please_input_your_authentication_code: "E-mail address \"%{OLD_MAIL}\" is already linked to another account, please input your authentication code." diff --git a/config/locales/fr.yml b/config/locales/fr.yml index cdb54732a..f705d4e8e 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -39,6 +39,7 @@ fr: must_be_in_the_past: "La période doit être strictement antérieure à la date du jour." apipie: api_documentation: "Documentation de l'API" + code: "Code HTTP " #error messages when importing an account from a SSO omniauth: email_already_linked_to_another_account_please_input_your_authentication_code: "L'adresse de courriel \"%{OLD_MAIL}\" est déjà associée à un compte utilisateur, merci de saisir votre code d'authentification." diff --git a/config/routes.rb b/config/routes.rb index d98ab79dd..36a47d475 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -189,7 +189,7 @@ Rails.application.routes.draw do resources :trainings resources :user_trainings resources :reservations - resources :machines + resources :machines, only: %i[index create update show destroy] resources :bookable_machines resources :invoices do get :download, on: :member