mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-01-29 18:52:22 +01:00
(api) availabilities
This commit is contained in:
parent
7945895c1e
commit
3811e7a6d5
@ -23,6 +23,8 @@
|
||||
- Fill the holes in the logical sequence of invoices references with nil invoices
|
||||
- Use a cached configuration file to read the authentification provider settings
|
||||
- Order numbers are now saved in database instead of generated on-the-fly
|
||||
- OpenAPI availabilities endpoint
|
||||
- Ability to filter OpenAPI reservations endpoint by availability_id
|
||||
- Support for ARM64 CPU architecture
|
||||
- Fix a bug: broken display after a plan category was deleted
|
||||
- Fix a security issue: updated json5 to 2.2.2 to fix [CVE-2022-46175](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-46175)
|
||||
|
35
app/controllers/open_api/v1/availabilities_controller.rb
Normal file
35
app/controllers/open_api/v1/availabilities_controller.rb
Normal file
@ -0,0 +1,35 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'concerns/reservations_filters_concern'
|
||||
|
||||
# public API controller for resources of type Reservation
|
||||
class OpenAPI::V1::AvailabilitiesController < OpenAPI::V1::BaseController
|
||||
extend OpenAPI::APIDoc
|
||||
include Rails::Pagination
|
||||
include OpenAPI::V1::Concerns::AvailabilitiesFiltersConcern
|
||||
expose_doc
|
||||
|
||||
def index
|
||||
@availabilities = Availability.order(start_at: :desc)
|
||||
.includes(:slots)
|
||||
|
||||
@availabilities = filter_by_after(@availabilities, params)
|
||||
@availabilities = filter_by_before(@availabilities, params)
|
||||
@availabilities = filter_by_id(@availabilities, params)
|
||||
@availabilities = filter_by_available_type(@availabilities, params)
|
||||
@availabilities = filter_by_available_id(@availabilities, params)
|
||||
|
||||
@availabilities = @availabilities.page(page).per(per_page)
|
||||
paginate @availabilities, per_page: per_page
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def page
|
||||
params[:page] || 1
|
||||
end
|
||||
|
||||
def per_page
|
||||
params[:per_page] || 20
|
||||
end
|
||||
end
|
@ -0,0 +1,85 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Filter the list of availabilities by the given parameters
|
||||
module OpenAPI::V1::Concerns::AvailabilitiesFiltersConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
# @param availabilities [ActiveRecord::Relation<Availability>]
|
||||
# @param filters [ActionController::Parameters]
|
||||
def filter_by_id(availabilities, filters)
|
||||
return availabilities if filters[:id].blank?
|
||||
|
||||
availabilities.where(id: may_array(filters[:id]))
|
||||
end
|
||||
|
||||
# @param availabilities [ActiveRecord::Relation<Availability>]
|
||||
# @param filters [ActionController::Parameters]
|
||||
def filter_by_after(availabilities, filters)
|
||||
return availabilities if filters[:after].blank?
|
||||
|
||||
availabilities.where('availabilities.start_at >= ?', Time.zone.parse(filters[:after]))
|
||||
end
|
||||
|
||||
# @param availabilities [ActiveRecord::Relation<Availability>]
|
||||
# @param filters [ActionController::Parameters]
|
||||
def filter_by_before(availabilities, filters)
|
||||
return availabilities if filters[:before].blank?
|
||||
|
||||
availabilities.where('availabilities.end_at <= ?', Time.zone.parse(filters[:before]))
|
||||
end
|
||||
|
||||
# @param availabilities [ActiveRecord::Relation<Availability>]
|
||||
# @param filters [ActionController::Parameters]
|
||||
def filter_by_available_type(availabilities, filters)
|
||||
return availabilities if filters[:available_type].blank?
|
||||
|
||||
availabilities.where(available_type: format_type(filters[:available_type]))
|
||||
end
|
||||
|
||||
# @param availabilities [ActiveRecord::Relation<Availability>]
|
||||
# @param filters [ActionController::Parameters]
|
||||
def filter_by_available_id(availabilities, filters)
|
||||
return availabilities if filters[:available_id].blank? || filters[:available_type].blank?
|
||||
|
||||
join_table = join_table(filters)
|
||||
availabilities.joins(join_table).where(join_table => { where_clause(filters) => may_array(filters[:available_id]) })
|
||||
end
|
||||
|
||||
# @param type [ActionController::Parameters]
|
||||
# @return [String]
|
||||
def format_type(type)
|
||||
types = {
|
||||
'Machine' => 'machines',
|
||||
'Space' => 'space',
|
||||
'Training' => 'training',
|
||||
'Event' => 'event'
|
||||
}
|
||||
types[type]
|
||||
end
|
||||
|
||||
# @param filters [ActionController::Parameters]
|
||||
# @return [Symbol]
|
||||
def join_table(filters)
|
||||
tables = {
|
||||
'Machine' => :machines_availabilities,
|
||||
'Space' => :spaces_availabilities,
|
||||
'Training' => :trainings_availabilities,
|
||||
'Event' => :event
|
||||
}
|
||||
tables[filters[:available_type]]
|
||||
end
|
||||
|
||||
# @param filters [ActionController::Parameters]
|
||||
# @return [Symbol]
|
||||
def where_clause(filters)
|
||||
clauses = {
|
||||
'Machine' => :machine_id,
|
||||
'Space' => :space_id,
|
||||
'Training' => :training_id,
|
||||
'Event' => :id
|
||||
}
|
||||
clauses[filters[:available_type]]
|
||||
end
|
||||
end
|
||||
end
|
@ -45,6 +45,15 @@ module OpenAPI::V1::Concerns::ReservationsFiltersConcern
|
||||
reservations.where(reservable_id: may_array(filters[:reservable_id]))
|
||||
end
|
||||
|
||||
# @param reservations [ActiveRecord::Relation<Reservation>]
|
||||
# @param filters [ActionController::Parameters]
|
||||
def filter_by_availability_id(reservations, filters)
|
||||
return reservations if filters[:availability_id].blank?
|
||||
|
||||
reservations.joins(:slots_reservations, :slots)
|
||||
.where(slots_reservations: { slots: { availability_id: may_array(filters[:availability_id]) } })
|
||||
end
|
||||
|
||||
# @param type [String]
|
||||
def format_type(type)
|
||||
type.singularize.classify
|
||||
|
@ -19,6 +19,7 @@ class OpenAPI::V1::ReservationsController < OpenAPI::V1::BaseController
|
||||
@reservations = filter_by_user(@reservations, params)
|
||||
@reservations = filter_by_reservable_type(@reservations, params)
|
||||
@reservations = filter_by_reservable_id(@reservations, params)
|
||||
@reservations = filter_by_availability_id(@reservations, params)
|
||||
|
||||
@reservations = @reservations.page(page).per(per_page)
|
||||
paginate @reservations, per_page: per_page
|
||||
|
163
app/doc/open_api/v1/availabilities_doc.rb
Normal file
163
app/doc/open_api/v1/availabilities_doc.rb
Normal file
@ -0,0 +1,163 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# openAPI documentation for reservations endpoint
|
||||
class OpenAPI::V1::AvailabilitiesDoc < OpenAPI::V1::BaseDoc
|
||||
resource_description do
|
||||
short 'Availabilities'
|
||||
desc 'Slots availables for reservation'
|
||||
formats FORMATS
|
||||
api_version API_VERSION
|
||||
end
|
||||
|
||||
include OpenAPI::V1::Concerns::ParamGroups
|
||||
|
||||
doc_for :index do
|
||||
api :GET, "/#{API_VERSION}/availabilities", 'Availabilities index'
|
||||
description 'Index of reservable availabilities and their slots, paginated. Ordered by *start_at* descendant.'
|
||||
param_group :pagination
|
||||
param :after, DateTime, optional: true, desc: 'Filter availabilities to those starting after the given date.'
|
||||
param :before, DateTime, optional: true, desc: 'Filter availabilities to those ending before the given date.'
|
||||
param :user_id, [Integer, Array], optional: true, desc: 'Scope the request to one or various users.'
|
||||
param :available_type, %w[Event Machine Space Training], optional: true, desc: 'Scope the request to a specific type of reservable.'
|
||||
param :available_id, [Integer, Array], optional: true, desc: 'Scope the request to one or various reservables. <br>' \
|
||||
'<b>WARNING</b>: filtering by <i>available_id</i> is only available if ' \
|
||||
'filter <i>available_type</i> is provided'
|
||||
|
||||
example <<-AVAILABILITIES
|
||||
# /open_api/v1/availabilities?available_type=Machine&page=1&per_page=3
|
||||
{
|
||||
"availabilities": [
|
||||
{
|
||||
"id": 5115,
|
||||
"start_at": "2023-07-13T14:00:00.000+02:00",
|
||||
"end_at": "2023-07-13T18:00:00.000+02:00",
|
||||
"created_at": "2023-01-24T12:28:25.905+01:00",
|
||||
"available_type": "Machine",
|
||||
"available_ids": [
|
||||
5,
|
||||
9,
|
||||
10,
|
||||
15,
|
||||
8,
|
||||
12,
|
||||
17,
|
||||
16,
|
||||
3,
|
||||
2,
|
||||
14,
|
||||
18
|
||||
],
|
||||
"slots": [
|
||||
{
|
||||
"id": 17792,
|
||||
"start_at": "2023-07-13T14:00:00.000+02:00",
|
||||
"end_at": "2023-07-13T15:00:00.000+02:00"
|
||||
},
|
||||
{
|
||||
"id": 17793,
|
||||
"start_at": "2023-07-13T15:00:00.000+02:00",
|
||||
"end_at": "2023-07-13T16:00:00.000+02:00"
|
||||
},
|
||||
{
|
||||
"id": 17794,
|
||||
"start_at": "2023-07-13T16:00:00.000+02:00",
|
||||
"end_at": "2023-07-13T17:00:00.000+02:00"
|
||||
},
|
||||
{
|
||||
"id": 17795,
|
||||
"start_at": "2023-07-13T17:00:00.000+02:00",
|
||||
"end_at": "2023-07-13T18:00:00.000+02:00"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5112,
|
||||
"start_at": "2023-07-07T14:00:00.000+02:00",
|
||||
"end_at": "2023-07-07T18:00:00.000+02:00",
|
||||
"created_at": "2023-01-24T12:26:45.997+01:00",
|
||||
"available_type": "Machine",
|
||||
"available_ids": [
|
||||
5,
|
||||
9,
|
||||
10,
|
||||
15,
|
||||
8,
|
||||
12,
|
||||
17,
|
||||
16,
|
||||
3,
|
||||
2,
|
||||
14,
|
||||
18
|
||||
],
|
||||
"slots": [
|
||||
{
|
||||
"id": 17786,
|
||||
"start_at": "2023-07-07T14:00:00.000+02:00",
|
||||
"end_at": "2023-07-07T15:00:00.000+02:00"
|
||||
},
|
||||
{
|
||||
"id": 17787,
|
||||
"start_at": "2023-07-07T15:00:00.000+02:00",
|
||||
"end_at": "2023-07-07T16:00:00.000+02:00"
|
||||
},
|
||||
{
|
||||
"id": 17788,
|
||||
"start_at": "2023-07-07T16:00:00.000+02:00",
|
||||
"end_at": "2023-07-07T17:00:00.000+02:00"
|
||||
},
|
||||
{
|
||||
"id": 17789,
|
||||
"start_at": "2023-07-07T17:00:00.000+02:00",
|
||||
"end_at": "2023-07-07T18:00:00.000+02:00"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5111,
|
||||
"start_at": "2023-07-06T14:00:00.000+02:00",
|
||||
"end_at": "2023-07-06T18:00:00.000+02:00",
|
||||
"created_at": "2023-01-24T12:26:37.189+01:00",
|
||||
"available_type": "Machine",
|
||||
"available_ids": [
|
||||
5,
|
||||
9,
|
||||
10,
|
||||
15,
|
||||
8,
|
||||
12,
|
||||
17,
|
||||
16,
|
||||
3,
|
||||
2,
|
||||
14,
|
||||
18
|
||||
],
|
||||
"slots": [
|
||||
{
|
||||
"id": 17782,
|
||||
"start_at": "2023-07-06T14:00:00.000+02:00",
|
||||
"end_at": "2023-07-06T15:00:00.000+02:00"
|
||||
},
|
||||
{
|
||||
"id": 17783,
|
||||
"start_at": "2023-07-06T15:00:00.000+02:00",
|
||||
"end_at": "2023-07-06T16:00:00.000+02:00"
|
||||
},
|
||||
{
|
||||
"id": 17784,
|
||||
"start_at": "2023-07-06T16:00:00.000+02:00",
|
||||
"end_at": "2023-07-06T17:00:00.000+02:00"
|
||||
},
|
||||
{
|
||||
"id": 17785,
|
||||
"start_at": "2023-07-06T17:00:00.000+02:00",
|
||||
"end_at": "2023-07-06T18:00:00.000+02:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
AVAILABILITIES
|
||||
end
|
||||
end
|
@ -20,6 +20,7 @@ class OpenAPI::V1::ReservationsDoc < OpenAPI::V1::BaseDoc
|
||||
param :user_id, [Integer, Array], optional: true, desc: 'Scope the request to one or various users.'
|
||||
param :reservable_type, %w[Event Machine Space Training], optional: true, desc: 'Scope the request to a specific type of reservable.'
|
||||
param :reservable_id, [Integer, Array], optional: true, desc: 'Scope the request to one or various reservables.'
|
||||
param :availability_id, [Integer, Array], optional: true, desc: 'Scope the request to one or various availabilities.'
|
||||
|
||||
example <<-RESERVATIONS
|
||||
# /open_api/v1/reservations?reservable_type=Event&page=1&per_page=3
|
||||
@ -48,6 +49,7 @@ class OpenAPI::V1::ReservationsDoc < OpenAPI::V1::BaseDoc
|
||||
"reserved_slots": [
|
||||
{
|
||||
"canceled_at": "2016-05-20T09:40:12.201+01:00",
|
||||
"availability_id": 5200,
|
||||
"start_at": "2016-06-03T14:00:00.000+01:00",
|
||||
"end_at": "2016-06-03T15:00:00.000+01:00"
|
||||
}
|
||||
@ -76,6 +78,7 @@ class OpenAPI::V1::ReservationsDoc < OpenAPI::V1::BaseDoc
|
||||
"reserved_slots": [
|
||||
{
|
||||
"canceled_at": null,
|
||||
"availability_id": 5199,
|
||||
"start_at": "2016-06-02T16:00:00.000+01:00",
|
||||
"end_at": "2016-06-02T17:00:00.000+01:00"
|
||||
}
|
||||
@ -104,6 +107,7 @@ class OpenAPI::V1::ReservationsDoc < OpenAPI::V1::BaseDoc
|
||||
"reserved_slots": [
|
||||
{
|
||||
"canceled_at": null,
|
||||
"availability_id": 5066,
|
||||
"start_at": "2016-06-03T14:00:00.000+01:00",
|
||||
"end_at": "2016-06-03T15:00:00.000+01:00"
|
||||
}
|
||||
|
@ -113,6 +113,22 @@ class Availability < ApplicationRecord
|
||||
end
|
||||
end
|
||||
|
||||
# @return [Array<Integer>]
|
||||
def available_ids
|
||||
case available_type
|
||||
when 'training'
|
||||
training_ids
|
||||
when 'machines'
|
||||
machine_ids
|
||||
when 'event'
|
||||
[event&.id]
|
||||
when 'space'
|
||||
space_ids
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
# check if the reservations are complete?
|
||||
# if a nb_total_places hasn't been defined, then places are unlimited
|
||||
# @return [Boolean]
|
||||
|
11
app/views/open_api/v1/availabilities/index.json.jbuilder
Normal file
11
app/views/open_api/v1/availabilities/index.json.jbuilder
Normal file
@ -0,0 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
json.availabilities @availabilities do |availability|
|
||||
json.extract! availability, :id, :start_at, :end_at, :created_at
|
||||
json.available_type availability.available_type.classify
|
||||
json.available_ids availability.available_ids
|
||||
|
||||
json.slots availability.slots do |slot|
|
||||
json.extract! slot, :id, :start_at, :end_at
|
||||
end
|
||||
end
|
@ -25,6 +25,6 @@ json.reservations @reservations do |reservation|
|
||||
|
||||
json.reserved_slots reservation.slots_reservations do |slot_reservation|
|
||||
json.extract! slot_reservation, :canceled_at
|
||||
json.extract! slot_reservation.slot, :start_at, :end_at
|
||||
json.extract! slot_reservation.slot, :availability_id, :start_at, :end_at
|
||||
end
|
||||
end
|
||||
|
87
test/integration/open_api/availabilities_test.rb
Normal file
87
test/integration/open_api/availabilities_test.rb
Normal file
@ -0,0 +1,87 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'test_helper'
|
||||
|
||||
module OpenApi; end
|
||||
|
||||
class OpenApi::AvailabilitiesTest < ActionDispatch::IntegrationTest
|
||||
def setup
|
||||
@token = OpenAPI::Client.find_by(name: 'minitest').token
|
||||
end
|
||||
|
||||
test 'list availabilities' do
|
||||
get '/open_api/v1/availabilities', headers: open_api_headers(@token)
|
||||
assert_response :success
|
||||
availabilities = json_response(response.body)
|
||||
assert_not_empty availabilities[:availabilities]
|
||||
|
||||
assert(availabilities[:availabilities].none? { |a| a[:id].blank? })
|
||||
assert(availabilities[:availabilities].none? { |a| a[:start_at].blank? })
|
||||
assert(availabilities[:availabilities].none? { |a| a[:end_at].blank? })
|
||||
assert(availabilities[:availabilities].none? { |a| a[:available_type].blank? })
|
||||
assert(availabilities[:availabilities].none? { |a| a[:available_ids].empty? })
|
||||
assert(availabilities[:availabilities].none? { |a| a[:created_at].blank? })
|
||||
assert(availabilities[:availabilities].none? { |a| a[:slots].empty? })
|
||||
assert(availabilities[:availabilities].pluck(:slots).flatten.none? { |s| s[:id].blank? })
|
||||
assert(availabilities[:availabilities].pluck(:slots).flatten.none? { |s| s[:start_at].blank? })
|
||||
assert(availabilities[:availabilities].pluck(:slots).flatten.none? { |s| s[:end_at].blank? })
|
||||
end
|
||||
|
||||
test 'list availabilities with pagination details' do
|
||||
get '/open_api/v1/availabilities?page=1&per_page=5', headers: open_api_headers(@token)
|
||||
assert_response :success
|
||||
availabilities = json_response(response.body)
|
||||
|
||||
assert_equal 5, availabilities[:availabilities].count
|
||||
end
|
||||
|
||||
test 'list availabilities for given IDs' do
|
||||
get '/open_api/v1/availabilities?id=[3,4]', headers: open_api_headers(@token)
|
||||
assert_response :success
|
||||
availabilities = json_response(response.body)
|
||||
assert_not_empty availabilities[:availabilities]
|
||||
|
||||
assert(availabilities[:availabilities].all? { |a| [3, 4].include?(a[:id]) })
|
||||
end
|
||||
|
||||
test 'list availabilities for given type' do
|
||||
get '/open_api/v1/availabilities?available_type=Machine', headers: open_api_headers(@token)
|
||||
assert_response :success
|
||||
availabilities = json_response(response.body)
|
||||
assert_not_empty availabilities[:availabilities]
|
||||
|
||||
assert(availabilities[:availabilities].all? { |a| a[:available_type] == 'Machine' })
|
||||
end
|
||||
|
||||
test 'list availabilities for given type and IDs' do
|
||||
get '/open_api/v1/availabilities?available_type=Machine&available_id[]=1&available_id[]=2', headers: open_api_headers(@token)
|
||||
assert_response :success
|
||||
availabilities = json_response(response.body)
|
||||
assert_not_empty availabilities[:availabilities]
|
||||
|
||||
assert(availabilities[:availabilities].all? { |a| a[:available_type] == 'Machine' })
|
||||
assert(availabilities[:availabilities].all? { |a| a[:available_ids].any? { |id| [1, 2].include?(id) } })
|
||||
end
|
||||
|
||||
test 'list availabilities with given available_id but no available_type does not filter by id' do
|
||||
get '/open_api/v1/availabilities?&available_id=1', headers: open_api_headers(@token)
|
||||
assert_response :success
|
||||
availabilities = json_response(response.body)
|
||||
assert_not_empty availabilities[:availabilities]
|
||||
|
||||
assert(availabilities[:availabilities].any? { |a| a[:available_ids] != 1 })
|
||||
end
|
||||
|
||||
test 'list availabilities with date filtering' do
|
||||
get '/open_api/v1/availabilities?after=2016-04-01T00:00:00+01:00&before=2016-05-31T23:59:59+02:00', headers: open_api_headers(@token)
|
||||
assert_response :success
|
||||
availabilities = json_response(response.body)
|
||||
assert_not_empty availabilities[:availabilities]
|
||||
|
||||
assert(availabilities[:availabilities].all? do |a|
|
||||
start = Time.zone.parse(a[:start_at])
|
||||
ending = Time.zone.parse(a[:end_at])
|
||||
start >= '2016-04-01'.to_date && ending <= '2016-05-31'.to_date
|
||||
end)
|
||||
end
|
||||
end
|
@ -79,4 +79,14 @@ class OpenApi::ReservationsTest < ActionDispatch::IntegrationTest
|
||||
assert_not_empty reservations[:reservations]
|
||||
assert_equal [2], reservations[:reservations].pluck(:reservable_id).uniq
|
||||
end
|
||||
|
||||
test 'list reservations filtered by availability' do
|
||||
get '/open_api/v1/reservations?availability_id=13', headers: open_api_headers(@token)
|
||||
assert_response :success
|
||||
assert_match Mime[:json].to_s, response.content_type
|
||||
|
||||
reservations = json_response(response.body)
|
||||
assert_not_empty reservations[:reservations]
|
||||
assert_equal [13], reservations[:reservations].pluck(:reserved_slots).flatten.pluck(:availability_id).uniq
|
||||
end
|
||||
end
|
||||
|
Loading…
x
Reference in New Issue
Block a user