mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-01-29 18:52:22 +01:00
Merge branch 'dev' for release 2.3.0
This commit is contained in:
commit
59efdcf880
1
.gitignore
vendored
1
.gitignore
vendored
@ -18,6 +18,7 @@
|
||||
|
||||
/public/uploads
|
||||
/public/assets
|
||||
/public/api
|
||||
|
||||
# Ignore application configurations
|
||||
/config/application.yml
|
||||
|
14
CHANGELOG.md
14
CHANGELOG.md
@ -1,5 +1,17 @@
|
||||
# Changelog Fab Manager
|
||||
|
||||
## v2.3.0 2016 June 28
|
||||
|
||||
- Public API with access management and online documentation
|
||||
- Add json cache for machines, events, trainings
|
||||
- Optimise sql query, avoid to N+1
|
||||
- Projects URL are always composed with slug instead of ID
|
||||
- Confirmation on project deletion
|
||||
- Fix a bug: unable to deploy 2.2.0+ when PostgreSQL 'unaccent' extension was already active
|
||||
- Fix a bug: some reservations was referencing reservables not present in database (#patch)
|
||||
- [TODO DEPLOY] `bundle exec rake fablab:fix:reservations_not_existing_reservable` to apply #patch
|
||||
- [TODO DEPLOY] `bundle install` and `rake db:migrate`
|
||||
|
||||
## v2.2.2 2016 June 23
|
||||
- Fix some bugs: users with uncompleted account (sso imported) won't appear in statistics, in listings and in searches. Moreover, they won't block statistics generation
|
||||
- Fix a bug: unable to display next results in statistics tables
|
||||
@ -41,4 +53,4 @@
|
||||
- Fix a bug: custom asset favicon-file favicon file is not set
|
||||
- Fix a security issue: stripe card token is now checked on server side on new/renew subscription
|
||||
- Translated notification e-mails into english language
|
||||
- Subscription extension logic has been extracted into a microservice
|
||||
- Subscription extension logic has been extracted into a microservice
|
||||
|
7
Gemfile
7
Gemfile
@ -16,7 +16,8 @@ gem 'therubyracer', '= 0.12.0', platforms: :ruby
|
||||
# Use jquery as the JavaScript library
|
||||
gem 'jquery-rails'
|
||||
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
|
||||
gem 'jbuilder', '~> 2.0'
|
||||
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 ?
|
||||
|
||||
@ -139,3 +140,7 @@ gem 'protected_attributes'
|
||||
gem 'message_format'
|
||||
|
||||
gem 'openlab_ruby'
|
||||
|
||||
gem 'api-pagination'
|
||||
gem 'has_secure_token'
|
||||
gem 'apipie-rails'
|
||||
|
23
Gemfile.lock
23
Gemfile.lock
@ -42,6 +42,9 @@ GEM
|
||||
tzinfo (~> 1.1)
|
||||
addressable (2.3.8)
|
||||
ansi (1.5.0)
|
||||
api-pagination (4.3.0)
|
||||
apipie-rails (0.3.6)
|
||||
json
|
||||
arel (6.0.3)
|
||||
autoprefixer-rails (5.1.8)
|
||||
execjs
|
||||
@ -164,6 +167,8 @@ GEM
|
||||
activerecord (>= 4.0.0)
|
||||
globalid (0.3.6)
|
||||
activesupport (>= 4.1.0)
|
||||
has_secure_token (1.0.0)
|
||||
activerecord (>= 3.0)
|
||||
hashdiff (0.3.0)
|
||||
hashie (3.4.2)
|
||||
highline (1.7.1)
|
||||
@ -179,9 +184,11 @@ GEM
|
||||
multi_xml (>= 0.5.2)
|
||||
i18n (0.7.0)
|
||||
ice_nine (0.11.1)
|
||||
jbuilder (2.2.12)
|
||||
activesupport (>= 3.0.0, < 5)
|
||||
jbuilder (2.5.0)
|
||||
activesupport (>= 3.0.0, < 5.1)
|
||||
multi_json (~> 1.2)
|
||||
jbuilder_cache_multi (0.0.3)
|
||||
jbuilder (>= 1.5.0, < 3)
|
||||
jquery-rails (4.0.3)
|
||||
rails-dom-testing (~> 1.0)
|
||||
railties (>= 4.2.0)
|
||||
@ -208,13 +215,13 @@ GEM
|
||||
mime-types (2.99)
|
||||
mini_magick (4.2.0)
|
||||
mini_portile2 (2.0.0)
|
||||
minitest (5.8.4)
|
||||
minitest (5.9.0)
|
||||
minitest-reporters (1.1.8)
|
||||
ansi
|
||||
builder
|
||||
minitest (>= 5.0)
|
||||
ruby-progressbar
|
||||
multi_json (1.11.2)
|
||||
multi_json (1.12.1)
|
||||
multi_xml (0.5.5)
|
||||
multipart-post (2.0.0)
|
||||
naught (1.0.0)
|
||||
@ -430,6 +437,8 @@ DEPENDENCIES
|
||||
aasm
|
||||
actionpack-page_caching
|
||||
active_record_query_trace
|
||||
api-pagination
|
||||
apipie-rails
|
||||
awesome_print
|
||||
bootstrap-sass
|
||||
byebug
|
||||
@ -452,7 +461,9 @@ DEPENDENCIES
|
||||
foreman
|
||||
forgery
|
||||
friendly_id (~> 5.1.0)
|
||||
jbuilder (~> 2.0)
|
||||
has_secure_token
|
||||
jbuilder (~> 2.5)
|
||||
jbuilder_cache_multi
|
||||
jquery-rails
|
||||
kaminari
|
||||
letter_opener
|
||||
@ -497,4 +508,4 @@ DEPENDENCIES
|
||||
webmock
|
||||
|
||||
BUNDLED WITH
|
||||
1.11.2
|
||||
1.12.5
|
||||
|
@ -0,0 +1,69 @@
|
||||
Application.Controllers.controller "OpenAPIClientsController", ["$scope", 'clientsPromise', 'growl', 'OpenAPIClient', 'dialogs', '_t'
|
||||
, ($scope, clientsPromise, growl, OpenAPIClient, dialogs, _t) ->
|
||||
|
||||
|
||||
|
||||
### PUBLIC SCOPE ###
|
||||
|
||||
## clients list
|
||||
$scope.clients = clientsPromise
|
||||
$scope.order = null
|
||||
$scope.clientFormVisible = false
|
||||
$scope.client = {}
|
||||
|
||||
$scope.toggleForm = ->
|
||||
$scope.clientFormVisible = !$scope.clientFormVisible
|
||||
|
||||
|
||||
# Change the order criterion to the one provided
|
||||
# @param orderBy {string} ordering criterion
|
||||
##
|
||||
$scope.setOrder = (orderBy)->
|
||||
if $scope.order == orderBy
|
||||
$scope.order = '-'+orderBy
|
||||
else
|
||||
$scope.order = orderBy
|
||||
|
||||
$scope.saveClient = (client)->
|
||||
if client.id?
|
||||
OpenAPIClient.update { id: client.id }, open_api_client: client, (clientResp)->
|
||||
client = clientResp
|
||||
growl.success(_t('client_successfully_updated'))
|
||||
else
|
||||
OpenAPIClient.save open_api_client: client, (client)->
|
||||
$scope.clients.push client
|
||||
growl.success(_t('client_successfully_created'))
|
||||
|
||||
|
||||
$scope.clientFormVisible = false
|
||||
$scope.clientForm.$setPristine()
|
||||
$scope.client = {}
|
||||
|
||||
$scope.editClient = (client)->
|
||||
$scope.clientFormVisible = true
|
||||
$scope.client = client
|
||||
|
||||
$scope.deleteClient = (index)->
|
||||
dialogs.confirm
|
||||
resolve:
|
||||
object: ->
|
||||
title: _t('confirmation_required')
|
||||
msg: _t('do_you_really_want_to_delete_this_open_api_client')
|
||||
, ->
|
||||
OpenAPIClient.delete { id: $scope.clients[index].id }, ->
|
||||
$scope.clients.splice(index, 1)
|
||||
growl.success(_t('client_successfully_deleted'))
|
||||
|
||||
$scope.resetToken = (client)->
|
||||
dialogs.confirm
|
||||
resolve:
|
||||
object: ->
|
||||
title: _t('confirmation_required')
|
||||
msg: _t('do_you_really_want_to_revoke_this_open_api_access')
|
||||
, ->
|
||||
OpenAPIClient.resetToken { id: client.id }, {}, (clientResp)->
|
||||
client.token = clientResp.token
|
||||
growl.success(_t('access_successfully_revoked'))
|
||||
|
||||
|
||||
]
|
@ -175,8 +175,8 @@ Application.Controllers.controller 'NewPlanController', ['$scope', '$uibModal',
|
||||
##
|
||||
# Controller used in the plan edition form
|
||||
##
|
||||
Application.Controllers.controller 'EditPlanController', ['$scope', 'groups', 'plans', 'planPromise', 'machines', 'prices', 'partners', 'CSRF', '$state', '$stateParams', 'growl', '$filter', '_t', '$locale'
|
||||
, ($scope, groups, plans, planPromise, machines, prices, partners, CSRF, $state, $stateParams, growl, $filter, _t, $locale) ->
|
||||
Application.Controllers.controller 'EditPlanController', ['$scope', 'groups', 'plans', 'planPromise', 'machines', 'prices', 'partners', 'CSRF', '$state', '$stateParams', 'growl', '$filter', '_t', '$locale', 'Plan'
|
||||
, ($scope, groups, plans, planPromise, machines, prices, partners, CSRF, $state, $stateParams, growl, $filter, _t, $locale, Plan) ->
|
||||
|
||||
|
||||
|
||||
@ -207,12 +207,13 @@ Application.Controllers.controller 'EditPlanController', ['$scope', 'groups', 'p
|
||||
##
|
||||
$scope.copyPricesFromPlan = ->
|
||||
if $scope.plan.parent
|
||||
parentPlan = $scope.getPlanFromId($scope.plan.parent)
|
||||
for parentPrice in parentPlan.prices
|
||||
for childKey, childPrice of $scope.plan.prices
|
||||
if childPrice.priceable_type == parentPrice.priceable_type and childPrice.priceable_id == parentPrice.priceable_id
|
||||
$scope.plan.prices[childKey].amount = parentPrice.amount
|
||||
break
|
||||
Plan.get {id: $scope.plan.parent}, (parentPlan) ->
|
||||
for parentPrice in parentPlan.prices
|
||||
for childKey, childPrice of $scope.plan.prices
|
||||
if childPrice.priceable_type == parentPrice.priceable_type and childPrice.priceable_id == parentPrice.priceable_id
|
||||
$scope.plan.prices[childKey].amount = parentPrice.amount
|
||||
break
|
||||
|
||||
# if no plan were selected, unset every prices
|
||||
else
|
||||
for key, price of $scope.plan.prices
|
||||
@ -257,4 +258,4 @@ Application.Controllers.controller 'EditPlanController', ['$scope', 'groups', 'p
|
||||
|
||||
## !!! MUST BE CALLED AT THE END of the controller
|
||||
initialize()
|
||||
]
|
||||
]
|
||||
|
@ -8,7 +8,7 @@ Application.Controllers.controller "EditPricingController", ["$scope", "$state",
|
||||
|
||||
### PUBLIC SCOPE ###
|
||||
## List of machines prices (not considering any plan)
|
||||
$scope.machinesPrices = machinesPricesPromise.prices
|
||||
$scope.machinesPrices = machinesPricesPromise
|
||||
|
||||
## List of trainings pricing
|
||||
$scope.trainingsPricings = trainingsPricingsPromise
|
||||
|
@ -96,6 +96,11 @@ Application.Controllers.controller "MainNavController", ["$scope", "$location",
|
||||
linkText: 'customization'
|
||||
linkIcon: 'gear'
|
||||
}
|
||||
{
|
||||
state: 'app.admin.open_api_clients'
|
||||
linkText: 'open_api_clients'
|
||||
linkIcon: 'cloud'
|
||||
}
|
||||
].concat(Fablab.adminNavLinks)
|
||||
|
||||
$scope.adminNavLinks = Fablab.adminNavLinks
|
||||
|
@ -70,7 +70,7 @@ class ProjectsController
|
||||
$('section[ui-view=main]').scrollTop(0, 200)
|
||||
return
|
||||
else
|
||||
$state.go('app.public.projects_show', {id: content.id})
|
||||
$state.go('app.public.projects_show', {id: content.slug})
|
||||
|
||||
|
||||
|
||||
@ -338,8 +338,8 @@ Application.Controllers.controller "EditProjectController", ["$scope", "$state",
|
||||
##
|
||||
# Controller used in the public project's details page
|
||||
##
|
||||
Application.Controllers.controller "ShowProjectController", ["$scope", "$state", "projectPromise", '$location', '$uibModal', '_t'
|
||||
, ($scope, $state, projectPromise, $location, $uibModal, _t) ->
|
||||
Application.Controllers.controller "ShowProjectController", ["$scope", "$state", "projectPromise", '$location', '$uibModal', 'dialogs', '_t'
|
||||
, ($scope, $state, projectPromise, $location, $uibModal, dialogs, _t) ->
|
||||
|
||||
### PUBLIC SCOPE ###
|
||||
|
||||
@ -383,8 +383,14 @@ Application.Controllers.controller "ShowProjectController", ["$scope", "$state",
|
||||
# check the permissions
|
||||
if $scope.currentUser.role is 'admin' or $scope.projectDeletableBy($scope.currentUser)
|
||||
# delete the project then refresh the projects list
|
||||
$scope.project.$delete ->
|
||||
$state.go('app.public.projects_list', {}, {reload: true})
|
||||
dialogs.confirm
|
||||
resolve:
|
||||
object: ->
|
||||
title: _t('confirmation_required')
|
||||
msg: _t('do_you_really_want_to_delete_this_project')
|
||||
, -> # cancel confirmed
|
||||
$scope.project.$delete ->
|
||||
$state.go('app.public.projects_list', {}, {reload: true})
|
||||
else
|
||||
console.error _t('unauthorized_operation')
|
||||
|
||||
|
@ -330,7 +330,7 @@ angular.module('application.router', ['ui.router']).
|
||||
controller: 'ReserveMachineController'
|
||||
resolve:
|
||||
plansPromise: ['Plan', (Plan)->
|
||||
Plan.query(attributes_requested: "['machines_credits']").$promise
|
||||
Plan.query().$promise
|
||||
]
|
||||
groupsPromise: ['Group', (Group)->
|
||||
Group.query().$promise
|
||||
@ -362,7 +362,6 @@ angular.module('application.router', ['ui.router']).
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query(['app.admin.machines_edit', 'app.shared.machine']).$promise
|
||||
]
|
||||
|
||||
# trainings
|
||||
.state 'app.logged.trainings_reserve',
|
||||
url: '/trainings/reserve'
|
||||
@ -375,7 +374,7 @@ angular.module('application.router', ['ui.router']).
|
||||
Setting.get(name: 'training_explications_alert').$promise
|
||||
]
|
||||
plansPromise: ['Plan', (Plan)->
|
||||
Plan.query(attributes_requested: "['trainings_credits']").$promise
|
||||
Plan.query().$promise
|
||||
]
|
||||
groupsPromise: ['Group', (Group)->
|
||||
Group.query().$promise
|
||||
@ -423,7 +422,7 @@ angular.module('application.router', ['ui.router']).
|
||||
Setting.get(name: 'subscription_explications_alert').$promise
|
||||
]
|
||||
plansPromise: ['Plan', (Plan)->
|
||||
Plan.query(shallow: true).$promise
|
||||
Plan.query().$promise
|
||||
]
|
||||
groupsPromise: ['Group', (Group)->
|
||||
Group.query().$promise
|
||||
@ -876,5 +875,19 @@ angular.module('application.router', ['ui.router']).
|
||||
Translations.query('app.admin.settings').$promise
|
||||
]
|
||||
|
||||
# OpenAPI Clients
|
||||
.state 'app.admin.open_api_clients',
|
||||
url: '/open_api_clients'
|
||||
views:
|
||||
'main@':
|
||||
templateUrl: '<%= asset_path "admin/open_api_clients/index.html" %>'
|
||||
controller: 'OpenAPIClientsController'
|
||||
resolve:
|
||||
clientsPromise: ['OpenAPIClient', (OpenAPIClient)->
|
||||
OpenAPIClient.query().$promise
|
||||
]
|
||||
translations: [ 'Translations', (Translations) ->
|
||||
Translations.query('app.admin.open_api_clients').$promise
|
||||
]
|
||||
|
||||
]
|
||||
|
11
app/assets/javascripts/services/open_api_client.coffee
Normal file
11
app/assets/javascripts/services/open_api_client.coffee
Normal file
@ -0,0 +1,11 @@
|
||||
'use strict'
|
||||
|
||||
Application.Services.factory 'OpenAPIClient', ["$resource", ($resource)->
|
||||
$resource "/api/open_api_clients/:id",
|
||||
{id: "@id"},
|
||||
resetToken:
|
||||
method: 'PATCH'
|
||||
url: "/api/open_api_clients/:id/reset_token"
|
||||
update:
|
||||
method: 'PUT'
|
||||
]
|
@ -4,7 +4,7 @@ Application.Services.factory 'Price', ["$resource", ($resource)->
|
||||
$resource "/api/prices/:id",
|
||||
{},
|
||||
query:
|
||||
isArray: false
|
||||
isArray: true
|
||||
update:
|
||||
method: 'PUT'
|
||||
compute:
|
||||
|
@ -335,6 +335,11 @@ p, .widget p {
|
||||
}
|
||||
}
|
||||
|
||||
.exponent {
|
||||
font-size: 0.7em;
|
||||
vertical-align: super
|
||||
}
|
||||
|
||||
@media screen and (min-width: $screen-lg-min) {
|
||||
.b-r-lg {border-right: 1px solid $border-color; }
|
||||
}
|
||||
|
83
app/assets/templates/admin/open_api_clients/index.html.erb
Normal file
83
app/assets/templates/admin/open_api_clients/index.html.erb
Normal file
@ -0,0 +1,83 @@
|
||||
<section class="heading b-b">
|
||||
<div class="row no-gutter">
|
||||
<div class="col-xs-2 col-sm-2 col-md-1">
|
||||
<section class="heading-btn">
|
||||
<a href="#" ng-click="backPrevLocation($event)"><i class="fa fa-long-arrow-left "></i></a>
|
||||
</section>
|
||||
</div>
|
||||
<div class="col-xs-10 col-sm-10 col-md-7 b-l b-r-md">
|
||||
<section class="heading-title">
|
||||
<h1 translate>{{ 'open_api_clients' }}</h1>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-12 col-sm-12 col-md-4 b-t hide-b-md">
|
||||
<section class="heading-actions wrapper">
|
||||
<a href="<%= apipie_apipie_path({version: 'v1'}) %>" target="_blank" class="btn btn-info b-2x rounded m-t-sm">
|
||||
<i class="fa fa-book" aria-hidden="true"></i>
|
||||
<span translate>{{ 'api_documentation' }}</span>
|
||||
<span class="exponent"><i class="fa fa-external-link" aria-hidden="true"></i></span>
|
||||
</a>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="m-lg">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="col-md-12">
|
||||
|
||||
<button type="button" class="btn btn-warning m-t m-b" ng-click="toggleForm()" ng-show="!clientFormVisible" translate>{{ 'add_new_client' | translate }}</button>
|
||||
|
||||
<form role="form" id="clientForm" ng-show="clientFormVisible" name="clientForm" class="form-inline m-b m-t" novalidate>
|
||||
<div class="form-group" ng-class="{'has-error': clientForm['client[name]'].$dirty && clientForm['client[name]'].$invalid}">
|
||||
<input class="form-control" type="text" name="client[name]" ng-model="client.name" value="" placeholder="{{ 'client_name' | translate }}" required>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-default" ng-click="toggleForm()" name="button">{{ 'cancel' | translate }}</button>
|
||||
<input type="submit" class="btn btn-warning" ng-disabled="!client.name || client.name.length == 0" ng-click="saveClient(client)" value="{{ 'save' | translate }}">
|
||||
</form>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:20%"><a href="" ng-click="setOrder('name')">{{ 'name' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': order == 'name', 'fa fa-sort-alpha-desc': order == '-name', 'fa fa-arrows-v': order }"></i></a></th>
|
||||
|
||||
<th style="width:10%"><a href="" ng-click="setOrder('calls_count')">{{ 'calls_count' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': order == 'calls_count', 'fa fa-sort-numeric-desc': order == '-calls_count', 'fa fa-arrows-v': order }"></i></a></th>
|
||||
|
||||
<th style="width:20%"><a href="">{{ 'token' | translate }}</a></th>
|
||||
|
||||
<th style="width:20%"><a href="" ng-click="setOrder('created_at')">{{ 'created_at' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': order == 'created_at', 'fa fa-sort-numeric-desc': order == '-created_at', 'fa fa-arrows-v': order }"></i></a></th>
|
||||
|
||||
<th style="width:30%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="client in clients | orderBy: order">
|
||||
<td>{{ client.name }}</td>
|
||||
<td>{{ client.calls_count }}</td>
|
||||
<td>{{ client.token }}</td>
|
||||
<td>{{ client.created_at | amDateFormat: 'LL' }}</td>
|
||||
<td>
|
||||
<div class="buttons">
|
||||
<button class="btn btn-default" ng-click="editClient(client)">
|
||||
<i class="fa fa-pencil"></i> {{ 'edit' | translate }}
|
||||
</button>
|
||||
|
||||
<button class="btn btn-default" ng-click="resetToken(client)">
|
||||
<i class="fa fa-times"></i> {{ 'reset_token' | translate }}
|
||||
</button>
|
||||
|
||||
<button class="btn btn-danger" ng-click="deleteClient($index)" ng-show="client.calls_count == 0">
|
||||
<i class="fa fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
@ -510,5 +510,3 @@
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
@ -3,7 +3,7 @@ class API::AdminsController < API::ApiController
|
||||
|
||||
def index
|
||||
authorize :admin
|
||||
@admins = User.admins
|
||||
@admins = User.includes(profile: [:user_avatar]).admins
|
||||
end
|
||||
|
||||
def create
|
||||
|
@ -49,21 +49,22 @@ class API::AvailabilitiesController < API::ApiController
|
||||
else
|
||||
@user = current_user
|
||||
end
|
||||
@current_user_role = current_user.is_admin? ? 'admin' : 'user'
|
||||
@machine = Machine.find(params[:machine_id])
|
||||
@slots = []
|
||||
@reservations = Reservation.where('reservable_type = ? and reservable_id = ?', @machine.class.to_s, @machine.id).joins(:slots).where('slots.start_at > ?', Time.now)
|
||||
@reservations = Reservation.where('reservable_type = ? and reservable_id = ?', @machine.class.to_s, @machine.id).includes(:slots, user: [:profile]).references(:slots, :user).where('slots.start_at > ?', Time.now)
|
||||
if @user.is_admin?
|
||||
@availabilities = @machine.availabilities.where("end_at > ? AND available_type = 'machines'", Time.now)
|
||||
@availabilities = @machine.availabilities.includes(:tags).where("end_at > ? AND available_type = 'machines'", Time.now)
|
||||
else
|
||||
end_at = 1.month.since
|
||||
end_at = 3.months.since if is_subscription_year(@user)
|
||||
@availabilities = @machine.availabilities.includes(:availability_tags).where("end_at > ? AND end_at < ? AND available_type = 'machines'", Time.now, end_at).where('availability_tags.tag_id' => @user.tag_ids.concat([nil]))
|
||||
@availabilities = @machine.availabilities.includes(:tags).where("end_at > ? AND end_at < ? AND available_type = 'machines'", Time.now, end_at).where('availability_tags.tag_id' => @user.tag_ids.concat([nil]))
|
||||
end
|
||||
@availabilities.each do |a|
|
||||
((a.end_at - a.start_at)/SLOT_DURATION.minutes).to_i.times do |i|
|
||||
if (a.start_at + (i * SLOT_DURATION).minutes) > Time.now
|
||||
slot = Slot.new(start_at: a.start_at + (i*SLOT_DURATION).minutes, end_at: a.start_at + (i*SLOT_DURATION).minutes + SLOT_DURATION.minutes, availability_id: a.id, machine: @machine, title: '')
|
||||
slot = verify_machine_is_reserved(slot, @reservations)
|
||||
slot = Slot.new(start_at: a.start_at + (i*SLOT_DURATION).minutes, end_at: a.start_at + (i*SLOT_DURATION).minutes + SLOT_DURATION.minutes, availability_id: a.id, availability: a, machine: @machine, title: '')
|
||||
slot = verify_machine_is_reserved(slot, @reservations, current_user, @current_user_role)
|
||||
@slots << slot
|
||||
end
|
||||
end
|
||||
@ -77,13 +78,13 @@ class API::AvailabilitiesController < API::ApiController
|
||||
@user = current_user
|
||||
end
|
||||
@slots = []
|
||||
@reservations = @user.reservations.where("reservable_type = 'Training'").joins(:slots).where('slots.start_at > ?', Time.now)
|
||||
@reservations = @user.reservations.includes(:slots).references(:slots).where("reservable_type = 'Training' AND slots.start_at > ?", Time.now)
|
||||
if @user.is_admin?
|
||||
@availabilities = Availability.trainings.where('start_at > ?', Time.now)
|
||||
@availabilities = Availability.includes(:tags, :slots, trainings: [:machines]).trainings.where('availabilities.start_at > ?', Time.now)
|
||||
else
|
||||
end_at = 1.month.since
|
||||
end_at = 3.months.since if can_show_slot_plus_three_months(@user)
|
||||
@availabilities = Availability.trainings.includes(:availability_tags).where('start_at > ? AND start_at < ?', Time.now, end_at).where('availability_tags.tag_id' => @user.tag_ids.concat([nil]))
|
||||
@availabilities = Availability.includes(:tags, :slots, trainings: [:machines]).trainings.where('availabilities.start_at > ? AND availabilities.start_at < ?', Time.now, end_at).where('availability_tags.tag_id' => @user.tag_ids.concat([nil]))
|
||||
end
|
||||
@availabilities.each do |a|
|
||||
a = verify_training_is_reserved(a, @reservations)
|
||||
@ -115,15 +116,14 @@ class API::AvailabilitiesController < API::ApiController
|
||||
is_reserved
|
||||
end
|
||||
|
||||
def verify_machine_is_reserved(slot, reservations)
|
||||
user = current_user
|
||||
def verify_machine_is_reserved(slot, reservations, user, user_role)
|
||||
reservations.each do |r|
|
||||
r.slots.each do |s|
|
||||
if s.start_at == slot.start_at and s.canceled_at == nil
|
||||
slot.id = s.id
|
||||
slot.is_reserved = true
|
||||
slot.title = t('availabilities.not_available')
|
||||
slot.can_modify = true if user.is_admin?
|
||||
slot.can_modify = true if user_role === 'admin'
|
||||
slot.reservation = r
|
||||
end
|
||||
if s.start_at == slot.start_at and r.user == user and s.canceled_at == nil
|
||||
|
@ -4,13 +4,14 @@ class API::EventsController < API::ApiController
|
||||
def index
|
||||
@events = policy_scope(Event)
|
||||
@total = @events.count
|
||||
@events = @events.page(params[:page]).per(12)
|
||||
@page = params[:page]
|
||||
@events = @events.page(@page).per(12)
|
||||
end
|
||||
|
||||
# GET /events/upcoming/:limit
|
||||
def upcoming
|
||||
limit = params[:limit]
|
||||
@events = Event.includes(:event_image, :event_files, :availability)
|
||||
@events = Event.includes(:event_image, :event_files, :availability, :categories)
|
||||
.where('availabilities.start_at >= ?', Time.now)
|
||||
.order('availabilities.start_at ASC').references(:availabilities).limit(limit)
|
||||
end
|
||||
|
@ -17,7 +17,7 @@ class API::MembersController < API::ApiController
|
||||
end
|
||||
|
||||
def last_subscribed
|
||||
@query = User.active.with_role(:member).includes(:profile).where('is_allow_contact = true AND confirmed_at IS NOT NULL').order('created_at desc').limit(params[:last])
|
||||
@query = User.active.with_role(:member).includes(profile: [:user_avatar]).where('is_allow_contact = true AND confirmed_at IS NOT NULL').order('created_at desc').limit(params[:last])
|
||||
|
||||
# remove unmerged profiles from list
|
||||
@members = @query.to_a
|
||||
@ -178,7 +178,7 @@ class API::MembersController < API::ApiController
|
||||
order_key = 'users.id'
|
||||
end
|
||||
|
||||
@query = User.includes(:profile, :group)
|
||||
@query = User.includes(:profile, :group, :subscriptions)
|
||||
.joins(:profile, :group, :roles, 'LEFT JOIN "subscriptions" ON "subscriptions"."user_id" = "users"."id" LEFT JOIN "plans" ON "plans"."id" = "subscriptions"."plan_id"')
|
||||
.where("users.is_active = 'true' AND roles.name = 'member'")
|
||||
.order("#{order_key} #{direction}")
|
||||
|
46
app/controllers/api/open_api_clients_controller.rb
Normal file
46
app/controllers/api/open_api_clients_controller.rb
Normal file
@ -0,0 +1,46 @@
|
||||
class API::OpenAPIClientsController < API::ApiController
|
||||
before_action :authenticate_user!
|
||||
|
||||
def index
|
||||
authorize OpenAPI::Client
|
||||
@clients = OpenAPI::Client.order(:created_at)
|
||||
end
|
||||
# add authorization
|
||||
def create
|
||||
@client = OpenAPI::Client.new(client_params)
|
||||
authorize @client
|
||||
if @client.save
|
||||
render status: :created
|
||||
else
|
||||
render json: @client.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
@client = OpenAPI::Client.find(params[:id])
|
||||
authorize @client
|
||||
if @client.update(client_params)
|
||||
render status: :ok
|
||||
else
|
||||
render json: @client.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def reset_token
|
||||
@client = OpenAPI::Client.find(params[:id])
|
||||
authorize @client
|
||||
@client.regenerate_token
|
||||
end
|
||||
|
||||
def destroy
|
||||
@client = OpenAPI::Client.find(params[:id])
|
||||
authorize @client
|
||||
@client.destroy
|
||||
head 204
|
||||
end
|
||||
|
||||
private
|
||||
def client_params
|
||||
params.require(:open_api_client).permit(:name)
|
||||
end
|
||||
end
|
@ -2,14 +2,9 @@
|
||||
before_action :authenticate_user!, except: [:index]
|
||||
|
||||
def index
|
||||
@attributes_requested = params[:attributes_requested]
|
||||
@plans = Plan.all
|
||||
@plans = Plan.includes(:plan_file)
|
||||
@plans = @plans.where(group_id: params[:group_id]) if params[:group_id]
|
||||
if params[:shallow]
|
||||
render :shallow_index
|
||||
else
|
||||
render :index
|
||||
end
|
||||
render :index
|
||||
end
|
||||
|
||||
def show
|
||||
|
@ -2,7 +2,7 @@ class API::TrainingsPricingsController < API::ApiController
|
||||
before_action :authenticate_user!
|
||||
|
||||
def index
|
||||
@trainings_pricings = TrainingsPricing.includes(:training)
|
||||
@trainings_pricings = TrainingsPricing.all
|
||||
end
|
||||
|
||||
def update
|
||||
|
43
app/controllers/open_api/v1/base_controller.rb
Normal file
43
app/controllers/open_api/v1/base_controller.rb
Normal file
@ -0,0 +1,43 @@
|
||||
class OpenAPI::V1::BaseController < ActionController::Base
|
||||
protect_from_forgery with: :null_session
|
||||
before_action :authenticate
|
||||
before_action :increment_calls_count
|
||||
|
||||
rescue_from ActiveRecord::RecordNotFound, with: :not_found
|
||||
rescue_from OpenAPI::ParameterError, with: :bad_request
|
||||
rescue_from ActionController::ParameterMissing, with: :bad_request
|
||||
|
||||
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 authenticate
|
||||
authenticate_token || render_unauthorized
|
||||
end
|
||||
|
||||
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
|
||||
end
|
40
app/controllers/open_api/v1/bookable_machines_controller.rb
Normal file
40
app/controllers/open_api/v1/bookable_machines_controller.rb
Normal file
@ -0,0 +1,40 @@
|
||||
class OpenAPI::V1::BookableMachinesController < OpenAPI::V1::BaseController
|
||||
extend OpenAPI::ApiDoc
|
||||
expose_doc
|
||||
|
||||
def index
|
||||
raise ActionController::ParameterMissing if params[:user_id].blank?
|
||||
|
||||
@machines = Machine.all
|
||||
|
||||
@machines = @machines.where(id: params[:machine_id]) if params[:machine_id].present?
|
||||
|
||||
@machines = @machines.to_a
|
||||
|
||||
user = User.find(params[:user_id])
|
||||
|
||||
@machines.delete_if do |machine|
|
||||
(machine.trainings.count != 0) and !user.is_training_machine?(machine)
|
||||
end
|
||||
|
||||
|
||||
@hours_remaining = Hash[@machines.map { |m| [m.id, 0] }]
|
||||
|
||||
|
||||
|
||||
if user.subscription
|
||||
plan_id = user.subscription.plan_id
|
||||
|
||||
@machines.each do |machine|
|
||||
credit = Credit.find_by(plan_id: plan_id, creditable: machine)
|
||||
users_credit = user.users_credits.find_by(credit: credit) if credit
|
||||
|
||||
if credit
|
||||
@hours_remaining[machine.id] = credit.hours - (users_credit.try(:hours_used) || 0)
|
||||
else
|
||||
@hours_remaining[machine.id] = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
18
app/controllers/open_api/v1/events_controller.rb
Normal file
18
app/controllers/open_api/v1/events_controller.rb
Normal file
@ -0,0 +1,18 @@
|
||||
class OpenAPI::V1::EventsController < OpenAPI::V1::BaseController
|
||||
extend OpenAPI::ApiDoc
|
||||
expose_doc
|
||||
|
||||
def index
|
||||
@events = Event.order(created_at: :desc)
|
||||
|
||||
if params[:page].present?
|
||||
@events = @events.page(params[:page]).per(per_page)
|
||||
paginate @events, per_page: per_page
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def per_page
|
||||
params[:per_page] || 20
|
||||
end
|
||||
end
|
27
app/controllers/open_api/v1/invoices_controller.rb
Normal file
27
app/controllers/open_api/v1/invoices_controller.rb
Normal file
@ -0,0 +1,27 @@
|
||||
class OpenAPI::V1::InvoicesController < OpenAPI::V1::BaseController
|
||||
extend OpenAPI::ApiDoc
|
||||
expose_doc
|
||||
|
||||
def index
|
||||
@invoices = Invoice.order(created_at: :desc)
|
||||
|
||||
if params[:user_id].present?
|
||||
@invoices = @invoices.where(user_id: params[:user_id])
|
||||
end
|
||||
|
||||
if params[:page].present?
|
||||
@invoices = @invoices.page(params[:page]).per(per_page)
|
||||
paginate @invoices, per_page: per_page
|
||||
end
|
||||
end
|
||||
|
||||
def download
|
||||
@invoice = Invoice.find(params[:id])
|
||||
send_file File.join(Rails.root, @invoice.file), type: 'application/pdf', disposition: 'inline', filename: @invoice.filename
|
||||
end
|
||||
|
||||
private
|
||||
def per_page
|
||||
params[:per_page] || 20
|
||||
end
|
||||
end
|
8
app/controllers/open_api/v1/machines_controller.rb
Normal file
8
app/controllers/open_api/v1/machines_controller.rb
Normal file
@ -0,0 +1,8 @@
|
||||
class OpenAPI::V1::MachinesController < OpenAPI::V1::BaseController
|
||||
extend OpenAPI::ApiDoc
|
||||
expose_doc
|
||||
|
||||
def index
|
||||
@machines = Machine.order(:created_at)
|
||||
end
|
||||
end
|
36
app/controllers/open_api/v1/reservations_controller.rb
Normal file
36
app/controllers/open_api/v1/reservations_controller.rb
Normal file
@ -0,0 +1,36 @@
|
||||
class OpenAPI::V1::ReservationsController < OpenAPI::V1::BaseController
|
||||
extend OpenAPI::ApiDoc
|
||||
expose_doc
|
||||
|
||||
def index
|
||||
@reservations = Reservation.order(created_at: :desc)
|
||||
|
||||
if params[:user_id].present?
|
||||
@reservations = @reservations.where(user_id: params[:user_id])
|
||||
else
|
||||
@reservations = @reservations.includes(user: :profile)
|
||||
end
|
||||
|
||||
if params[:reservable_type].present?
|
||||
@reservations = @reservations.where(reservable_type: format_type(params[:reservable_type]))
|
||||
end
|
||||
|
||||
if params[:reservable_id].present?
|
||||
@reservations = @reservations.where(reservable_id: params[:reservable_id])
|
||||
end
|
||||
|
||||
if params[:page].present?
|
||||
@reservations = @reservations.page(params[:page]).per(per_page)
|
||||
paginate @reservations, per_page: per_page
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def format_type(type)
|
||||
type.singularize.classify
|
||||
end
|
||||
|
||||
def per_page
|
||||
params[:per_page] || 20
|
||||
end
|
||||
end
|
8
app/controllers/open_api/v1/trainings_controller.rb
Normal file
8
app/controllers/open_api/v1/trainings_controller.rb
Normal file
@ -0,0 +1,8 @@
|
||||
class OpenAPI::V1::TrainingsController < OpenAPI::V1::BaseController
|
||||
extend OpenAPI::ApiDoc
|
||||
expose_doc
|
||||
|
||||
def index
|
||||
@trainings = Training.order(:created_at)
|
||||
end
|
||||
end
|
30
app/controllers/open_api/v1/user_trainings_controller.rb
Normal file
30
app/controllers/open_api/v1/user_trainings_controller.rb
Normal file
@ -0,0 +1,30 @@
|
||||
class OpenAPI::V1::UserTrainingsController < OpenAPI::V1::BaseController
|
||||
extend OpenAPI::ApiDoc
|
||||
expose_doc
|
||||
|
||||
def index
|
||||
@user_trainings = UserTraining.order(created_at: :desc)
|
||||
|
||||
if params[:user_id].present?
|
||||
@user_trainings = @user_trainings.where(user_id: params[:user_id])
|
||||
else
|
||||
@user_trainings = @user_trainings.includes(user: :profile)
|
||||
end
|
||||
|
||||
if params[:training_id].present?
|
||||
@user_trainings = @user_trainings.where(training_id: params[:training_id])
|
||||
else
|
||||
@user_trainings = @user_trainings.includes(:training)
|
||||
end
|
||||
|
||||
if params[:page].present?
|
||||
@user_trainings = @user_trainings.page(params[:page]).per(per_page)
|
||||
paginate @user_trainings, per_page: per_page
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def per_page
|
||||
params[:per_page] || 20
|
||||
end
|
||||
end
|
27
app/controllers/open_api/v1/users_controller.rb
Normal file
27
app/controllers/open_api/v1/users_controller.rb
Normal file
@ -0,0 +1,27 @@
|
||||
class OpenAPI::V1::UsersController < OpenAPI::V1::BaseController
|
||||
extend OpenAPI::ApiDoc
|
||||
expose_doc
|
||||
|
||||
def index
|
||||
@users = User.order(created_at: :desc).includes(:group, :profile)
|
||||
|
||||
if params[:email].present?
|
||||
email_param = params[:email].is_a?(String) ? params[:email].downcase : params[:email].map(&:downcase)
|
||||
@users = @users.where(email: email_param)
|
||||
end
|
||||
|
||||
if params[:user_id].present?
|
||||
@users = @users.where(id: params[:user_id])
|
||||
end
|
||||
|
||||
if params[:page].present?
|
||||
@users = @users.page(params[:page]).per(per_page)
|
||||
paginate @users, per_page: per_page
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def per_page
|
||||
params[:per_page] || 20
|
||||
end
|
||||
end
|
60
app/doc/open_api/api_doc.rb
Normal file
60
app/doc/open_api/api_doc.rb
Normal file
@ -0,0 +1,60 @@
|
||||
# app/concerns/controllers/api_doc.rb
|
||||
#
|
||||
# Controller extension with common API documentation shortcuts
|
||||
#
|
||||
|
||||
module OpenAPI::ApiDoc
|
||||
# Apipie doesn't allow to append anything to esisting
|
||||
# description. It raises an error on double definition.
|
||||
#
|
||||
def append_desc(desc = "")
|
||||
_apipie_dsl_data[:description] << desc << "\n"
|
||||
end
|
||||
|
||||
# Converts passed +code+ to the markdown
|
||||
# by prepending 4 spaces to each line
|
||||
#
|
||||
# @param code [String]
|
||||
# @return [String]
|
||||
#
|
||||
def to_markdown_code(code)
|
||||
code.split("\n").map do |line|
|
||||
(" " * 4) + line
|
||||
end.join("\n")
|
||||
end
|
||||
|
||||
# Includes passed list of json schemas
|
||||
# to method description
|
||||
#
|
||||
# @example
|
||||
# include_response_schema 'users.json', '_user.json'
|
||||
#
|
||||
# @param schemas [Array<String>]
|
||||
#
|
||||
def include_response_schema(*schemas)
|
||||
root = Rails.root.join('app/doc/responses')
|
||||
_apipie_dsl_data[:description] = _apipie_dsl_data[:description].strip_heredoc
|
||||
append_desc("## Response schema")
|
||||
|
||||
schemas.each do |relative_path|
|
||||
append_desc MarkdownJsonSchema.read(relative_path)
|
||||
end
|
||||
end
|
||||
|
||||
# Exports all documentation from provided class
|
||||
#
|
||||
# @example
|
||||
# class ProfilesController < ApplicationController
|
||||
# extend Controllers::ApiDoc
|
||||
# expose_doc
|
||||
# # exports all docs from ProfilesDoc class
|
||||
# # that must be inherired from ApplicationDoc
|
||||
# end
|
||||
#
|
||||
# @see ApplicationDoc
|
||||
#
|
||||
def expose_doc(doc_name = "#{controller_path}_doc")
|
||||
doc_klass = doc_name.classify.constantize
|
||||
doc_klass.apply(self)
|
||||
end
|
||||
end
|
92
app/doc/open_api/application_doc.rb
Normal file
92
app/doc/open_api/application_doc.rb
Normal file
@ -0,0 +1,92 @@
|
||||
# app/docs/application_doc.rb
|
||||
#
|
||||
# A common class for defining API docs
|
||||
#
|
||||
# This class is abstract, to define your own doc
|
||||
# for controller Api::ResourcesController, create a class
|
||||
#
|
||||
# class Api::ResourcesDoc < ApplicationDoc
|
||||
# resource_description do
|
||||
# # any method from Apipie
|
||||
# end
|
||||
#
|
||||
# doc_for :action_name do
|
||||
# # documentation for Api::ResourcesController#action_name
|
||||
# # using Apipie methods
|
||||
# end
|
||||
# end
|
||||
#
|
||||
|
||||
class OpenAPI::ApplicationDoc
|
||||
extend OpenAPI::ApiDoc
|
||||
|
||||
class << self
|
||||
# Stores provided resource description
|
||||
# to include it later to the controller class
|
||||
#
|
||||
# @param block [Proc]
|
||||
#
|
||||
def resource_description(&block)
|
||||
@_resource_description_block = block
|
||||
end
|
||||
|
||||
# Returns stored resource description (or empty proc)
|
||||
#
|
||||
# @return [Proc]
|
||||
#
|
||||
def resource_description_block
|
||||
@_resource_description_block || proc {}
|
||||
end
|
||||
|
||||
# Defines documentation for provided +action_name+
|
||||
#
|
||||
# @param action_name [#to_s] should match controller action name
|
||||
# @param block [Proc] documentation for +action_name+ action
|
||||
#
|
||||
def doc_for(action_name, &block)
|
||||
docs[action_name] = block
|
||||
end
|
||||
|
||||
# Returns mappign action_name => documentation
|
||||
#
|
||||
# @return [Hash]
|
||||
#
|
||||
def docs
|
||||
@_docs ||= {}
|
||||
end
|
||||
|
||||
def define_param_group(param_group_name, &block)
|
||||
param_groups[param_group_name] = block
|
||||
end
|
||||
|
||||
def param_groups
|
||||
@_param_groups ||= {}
|
||||
end
|
||||
|
||||
# Applies all defined DSL to provided controller class
|
||||
#
|
||||
# @param controller [ActionController::Base]
|
||||
#
|
||||
def apply(controller)
|
||||
resource_description_block = self.resource_description_block
|
||||
docs = self.docs
|
||||
param_groups = self.param_groups
|
||||
|
||||
controller.class_eval do
|
||||
resource_description(&resource_description_block)
|
||||
|
||||
param_groups.each do |param_group_name, block|
|
||||
instance_eval do
|
||||
def_param_group param_group_name, &block
|
||||
end
|
||||
end
|
||||
|
||||
docs.each do |action_name, block|
|
||||
instance_eval(&block)
|
||||
|
||||
define_method(action_name) {}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
5
app/doc/open_api/v1/base_doc.rb
Normal file
5
app/doc/open_api/v1/base_doc.rb
Normal file
@ -0,0 +1,5 @@
|
||||
class OpenAPI::V1::BaseDoc < OpenAPI::ApplicationDoc
|
||||
API_VERSION = "v1"
|
||||
FORMATS = ['json']
|
||||
PER_PAGE_DEFAULT = 20
|
||||
end
|
72
app/doc/open_api/v1/bookable_machines_doc.rb
Normal file
72
app/doc/open_api/v1/bookable_machines_doc.rb
Normal file
@ -0,0 +1,72 @@
|
||||
class OpenAPI::V1::BookableMachinesDoc < OpenAPI::V1::BaseDoc
|
||||
resource_description do
|
||||
short 'Bookable machines'
|
||||
desc 'Machines that a given user is allowed to book (allowed to make a reservation)'
|
||||
formats FORMATS
|
||||
api_version API_VERSION
|
||||
end
|
||||
|
||||
doc_for :index do
|
||||
api :GET, "/#{API_VERSION}/bookable_machines", "Bookable machines index"
|
||||
description "Machines that a given user is allowed to book."
|
||||
param :user_id, Integer, required: true, desc: "Id of the given user."
|
||||
example <<-EOS
|
||||
# /open_api/v1/bookable_machines?user_id=522
|
||||
{
|
||||
"machines": [
|
||||
{
|
||||
"id": 3,
|
||||
"name": "Shopbot / Grande fraiseuse",
|
||||
"slug": "shopbot-grande-fraiseuse",
|
||||
"updated_at": "2014-08-19T11:01:12.919+02:00",
|
||||
"created_at": "2014-06-30T03:32:31.982+02:00",
|
||||
"description": "La fraiseuse numériq ... ",
|
||||
"spec": "Surface maximale de travail: 244 ... "
|
||||
"hours_remaining": 0
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"name": "Petite Fraiseuse",
|
||||
"slug": "petite-fraiseuse",
|
||||
"updated_at": "2014-06-30T14:33:37.638+02:00",
|
||||
"created_at": "2014-06-30T03:32:31.989+02:00",
|
||||
"description": "La fraiseuse numérique Roland Modela MDX-20 ... ",
|
||||
"spec": "Taille du plateau X/Y : 220 mm x 1 ... "
|
||||
"hours_remaining": 0
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Découpeuse vinyle",
|
||||
"slug": "decoupeuse-vinyle",
|
||||
"updated_at": "2014-06-30T15:10:14.272+02:00",
|
||||
"created_at": "2014-06-30T03:32:31.977+02:00",
|
||||
"description": "La découpeuse Vinyle, Roland CAMM ...",
|
||||
"spec": "Largeurs de support acceptées: de 50 mm à 70 ... 50 cm/sec ... mécanique: 0,0125 mm/pas\r\n",
|
||||
"hours_remaining": 0
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Epilog EXT36 Laser",
|
||||
"slug": "decoupeuse-laser",
|
||||
"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, ... ",
|
||||
"spec": "Puissance : 40W Surface de trav ... ",
|
||||
"hours_remaining": 0
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "Imprimante 3D - Ultimaker",
|
||||
"slug": "imprimante-3d",
|
||||
"updated_at": "2014-12-11T15:47:02.215+01:00",
|
||||
"created_at": "2014-06-30T03:32:31.986+02:00",
|
||||
"description": "L'imprimante 3D U ... ",
|
||||
"spec": "Surface maximale de travai sés: PLA (en stock).",
|
||||
"hours_remaining": 10
|
||||
},
|
||||
# ...
|
||||
]
|
||||
}
|
||||
EOS
|
||||
end
|
||||
end
|
30
app/doc/open_api/v1/concerns/param_groups.rb
Normal file
30
app/doc/open_api/v1/concerns/param_groups.rb
Normal file
@ -0,0 +1,30 @@
|
||||
module OpenAPI::V1::Concerns::ParamGroups
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
define_param_group :pagination do
|
||||
param :page, Integer, desc: "Page number", optional: true
|
||||
param :per_page, Integer, desc: "Number of objects per page. Default is #{OpenAPI::V1::BaseDoc::PER_PAGE_DEFAULT}.", optional: true
|
||||
end
|
||||
|
||||
# define_param_group :order_type do
|
||||
# param :order_type, ['asc', 'desc'], desc: "order type: descendant or ascendant. Default value is *desc*."
|
||||
# end
|
||||
#
|
||||
# define_param_group :filter_by_tags do
|
||||
# param :tagged_with, [String, Array], desc: 'If multiple tags are given, we use an *OR* function. See parameter *order_by_matching_tag_count* to order the result. It can also be a *comma* *separated* *string*. Example: tagged_with=science,museum'
|
||||
# param :order_by_matching_tag_count, ['t',1,'true'], desc: "You can use this parameter if you are sending a parameter *tagged_with*. Send this parameter to order by number of matching tags (descendant): result will be sort firstly by matching tags and secondly by order given by *order_by* parameter. Default to *false*."
|
||||
# end
|
||||
#
|
||||
# define_param_group :filter_by_blog do
|
||||
# param :blog_slug, String, desc: "Send the blog's *slug* to only return articles belonging to specific blog."
|
||||
# end
|
||||
#
|
||||
# define_param_group :filter_by_geolocation do
|
||||
# param :latitude, Numeric, desc: "Latitude. Example: *45.166670*"
|
||||
# param :longitude, Numeric, desc: "Longitude. Example: *5.7166700*"
|
||||
# param :radius, Numeric, desc: "To be combined with parameters latitude and longitude. Default to *10*."
|
||||
# param :order_by_distance, ['t',1,'true'], desc: "You can use this parameter if you are sending parameters *latitude* and *longitude*. Send this parameter to order by distance (descendant): result will be sort firstly by distance and secondly by order given by *order_by* parameter. Default to *false*."
|
||||
# end
|
||||
end
|
||||
end
|
45
app/doc/open_api/v1/events_doc.rb
Normal file
45
app/doc/open_api/v1/events_doc.rb
Normal file
@ -0,0 +1,45 @@
|
||||
class OpenAPI::V1::EventsDoc < OpenAPI::V1::BaseDoc
|
||||
resource_description do
|
||||
short 'Events'
|
||||
desc 'Events of Fab-manager'
|
||||
formats FORMATS
|
||||
api_version API_VERSION
|
||||
end
|
||||
|
||||
include OpenAPI::V1::Concerns::ParamGroups
|
||||
|
||||
doc_for :index do
|
||||
api :GET, "/#{API_VERSION}/events", "Events index"
|
||||
param_group :pagination
|
||||
description "Events index. Order by *created_at* desc."
|
||||
example <<-EOS
|
||||
# /open_api/v1/events?page=1&per_page=2
|
||||
{
|
||||
"events": [
|
||||
{
|
||||
"id": 183,
|
||||
"title": "OPEN LAB",
|
||||
"description": "Que vous soyez Fab user, visiteur, curieux ou bricoleur, l’atelier de fabrication numérique vous ouvre ses portes les mercredis soirs pour avancer vos projets ou rencontrer la «communauté» Fab Lab. \r\n\r\nCe soir, venez spécialement découvrir les machines à commandes numérique du Fab Lab de La Casemate, venez comprendre ce lieux ouvert à tous. \r\n\r\n\r\nVenez découvrir un concept, une organisation, des machines, pour stimuler votre sens de la créativité.",
|
||||
"updated_at": "2016-04-25T10:49:40.055+02:00",
|
||||
"created_at": "2016-04-25T10:49:40.055+02:00",
|
||||
"amount": 0,
|
||||
"reduced_amount": 0,
|
||||
"nb_total_places": 18,
|
||||
"nb_free_places": 16
|
||||
},
|
||||
{
|
||||
"id": 182,
|
||||
"title": "ATELIER SKATE : SEANCE 1",
|
||||
"description": "Envie de rider à travers Grenoble sur une planche unique ? Envie de découvrir la fabrication éco-responsable d'un skate ? Alors bienvenue à l'atelier Skate Board du Fablab ! Encadré par Ivan Mago et l'équipe du FabLab, vous réaliserez votre planche (skate, longboard,...) depuis son design jusqu'à sa décoration sur 4 séances.\r\n\r\nLe tarif 50€ inclut la participation aux ateliers, l'utilisations des machines, et tout le matériel de fabrication (bois+colle+grip+vinyle).\r\n\r\nCette première séance sera consacré au design de votre planche et à la découpe des gabarits. N'hésitez pas à venir avec votre ordinateur et vos logiciels de création 2D si vous le souhaitez.\r\n\r\nNous vous attendons nombreux !",
|
||||
"updated_at": "2016-04-11T17:40:15.146+02:00",
|
||||
"created_at": "2016-04-11T17:40:15.146+02:00",
|
||||
"amount": 5000,
|
||||
"reduced_amount": null,
|
||||
"nb_total_places": 8,
|
||||
"nb_free_places": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
EOS
|
||||
end
|
||||
end
|
78
app/doc/open_api/v1/invoices_doc.rb
Normal file
78
app/doc/open_api/v1/invoices_doc.rb
Normal file
@ -0,0 +1,78 @@
|
||||
class OpenAPI::V1::InvoicesDoc < OpenAPI::V1::BaseDoc
|
||||
resource_description do
|
||||
short 'Invoices'
|
||||
desc 'Invoices'
|
||||
formats FORMATS
|
||||
api_version API_VERSION
|
||||
end
|
||||
|
||||
include OpenAPI::V1::Concerns::ParamGroups
|
||||
|
||||
doc_for :index do
|
||||
api :GET, "/#{API_VERSION}/invoices", "Invoices index"
|
||||
description "Index of users' invoices, with optional pagination. Order by *created_at* descendant."
|
||||
param_group :pagination
|
||||
param :user_id, [Integer, Array], optional: true, desc: "Scope the request to one or various users."
|
||||
example <<-EOS
|
||||
# /open_api/v1/invoices?user_id=211&page=1&per_page=3
|
||||
{
|
||||
"invoices": [
|
||||
{
|
||||
"id": 2809,
|
||||
"invoiced_id": 3257,
|
||||
"user_id": 211,
|
||||
"invoiced_type": "Reservation",
|
||||
"stp_invoice_id": "in_187DLE4zBvgjueAZ6L7SyQlU",
|
||||
"reference": "1605017/VL",
|
||||
"total": 1000,
|
||||
"type": null,
|
||||
"description": null,
|
||||
"invoice_url": "/open_api/v1/invoices/2809/download",
|
||||
"invoiced": {
|
||||
"created_at": "2016-05-04T01:54:16.686+02:00"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 2783,
|
||||
"invoiced_id": 3229,
|
||||
"user_id": 211,
|
||||
"invoiced_type": "Reservation",
|
||||
"stp_invoice_id": "in_185Hmt4zBvgjueAZl5lio1pK",
|
||||
"reference": "1604176/VL",
|
||||
"total": 2000,
|
||||
"type": null,
|
||||
"description": null,
|
||||
"invoice_url": "/open_api/v1/invoices/2783/download",
|
||||
"invoiced": {
|
||||
"created_at": "2016-04-28T18:14:52.524+02:00"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 2773,
|
||||
"invoiced_id": 3218,
|
||||
"user_id": 211,
|
||||
"invoiced_type": "Reservation",
|
||||
"stp_invoice_id": "in_184oNK4zBvgjueAZJdOxHJjT",
|
||||
"reference": "1604166/VL",
|
||||
"total": 2000,
|
||||
"type": null,
|
||||
"description": null,
|
||||
"invoice_url": "/open_api/v1/invoices/2773/download",
|
||||
"invoiced": {
|
||||
"created_at": "2016-04-27T10:50:30.806+02:00"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
EOS
|
||||
end
|
||||
|
||||
doc_for :download do
|
||||
api :GET, "/#{API_VERSION}/invoices/:id/download", "Download an invoice"
|
||||
param :id, Integer, desc: "Invoice id", required: true
|
||||
|
||||
example <<-EOS
|
||||
# /open_api/v1/invoices/2809/download
|
||||
EOS
|
||||
end
|
||||
end
|
77
app/doc/open_api/v1/machines_doc.rb
Normal file
77
app/doc/open_api/v1/machines_doc.rb
Normal file
@ -0,0 +1,77 @@
|
||||
class OpenAPI::V1::MachinesDoc < OpenAPI::V1::BaseDoc
|
||||
resource_description do
|
||||
short 'Machines'
|
||||
desc 'Machines of Fab-manager'
|
||||
formats FORMATS
|
||||
api_version API_VERSION
|
||||
end
|
||||
|
||||
doc_for :index do
|
||||
api :GET, "/#{API_VERSION}/machines", "Machines index"
|
||||
description "Machines index. Order by *created_at* ascendant."
|
||||
example <<-EOS
|
||||
# /open_api/v1/machines
|
||||
{
|
||||
"machines": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Epilog EXT36 Laser",
|
||||
"slug": "decoupeuse-laser",
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Découpeuse vinyle",
|
||||
"slug": "decoupeuse-vinyle",
|
||||
"updated_at": "2014-06-30T15:10:14.272+02:00",
|
||||
"created_at": "2014-06-30T03:32:31.977+02:00",
|
||||
"description": "La découpeuse Vinyle, Roland CAMM-1 GX24\r\n\r\nInformations générales :\r\nEnvie 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\nCette machine permet de découper principalement : vinyle, vinyle réfléchissant et 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"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "Shopbot / Grande fraiseuse",
|
||||
"slug": "shopbot-grande-fraiseuse",
|
||||
"updated_at": "2014-08-19T11:01:12.919+02:00",
|
||||
"created_at": "2014-06-30T03:32:31.982+02:00",
|
||||
"description": "La fraiseuse numérique ShopBot PRS standard\r\n\r\nInformations générales :\r\nCette machine est une 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 à la construction d'une maison ou d'un assemblage immense, le ShopBot ouvre de nombreuses portes à votre imagination ! \r\n\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<object width=\"560\" height=\"315\"><param name=\"movie\" value=\"//www.youtube.com/v/3h8VPLNapag?hl=fr_FR&version=3\"></param><param name=\"allowFullScreen\" value=\"true\"></param><param name=\"allowscriptaccess\" value=\"always\"></param><embed src=\"//www.youtube.com/v/3h8VPLNapag?hl=fr_FR&version=3\" type=\"application/x-shockwave-flash\" width=\"560\" height=\"315\" allowscriptaccess=\"always\" allowfullscreen=\"true\"></embed></object>",
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "Imprimante 3D - Ultimaker",
|
||||
"slug": "imprimante-3d",
|
||||
"updated_at": "2014-12-11T15:47:02.215+01:00",
|
||||
"created_at": "2014-06-30T03:32:31.986+02:00",
|
||||
"description": "L'imprimante 3D ULTIMAKER\r\n\r\nInformations générales :\r\nL'utimaker est une imprimante 3D peu chère 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)."
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"name": "Petite Fraiseuse",
|
||||
"slug": "petite-fraiseuse",
|
||||
"updated_at": "2014-06-30T14:33:37.638+02:00",
|
||||
"created_at": "2014-06-30T03:32:31.989+02:00",
|
||||
"description": "La fraiseuse numérique Roland Modela MDX-20\r\n\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) implique 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\n\r\nMatériaux usinables :\r\nLes principaux matériaux usinables sont : 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"
|
||||
},
|
||||
#
|
||||
# ....
|
||||
#
|
||||
{
|
||||
"id": 18,
|
||||
"name": "Canon IPF 750",
|
||||
"slug": "canon-ipf-750",
|
||||
"updated_at": "2015-10-12T18:00:24.254+02:00",
|
||||
"created_at": "2015-10-12T18:00:24.254+02:00",
|
||||
"description": "PROCHAINEMENT",
|
||||
"spec": "36 pouces\r\nType d'encre: Encre pigment et colorant réactive, 5 couleurs (MBK x 2, BK, C, M, Y)\r\nRésolution d'impression maximale:\t2400 × 1200 dpi\r\nVitesse d'impression:\t(A0, Image polychrome)\r\nPapier ordinaire: 0:48 min (mode brouillon), 1:14 min (mode standard)\r\nPapier couché: 1:14 min (mode brouillon), 2:26 min (mode standard), 3:51 min (mode qualité élevée)"
|
||||
}
|
||||
]
|
||||
}
|
||||
EOS
|
||||
end
|
||||
end
|
90
app/doc/open_api/v1/reservations_doc.rb
Normal file
90
app/doc/open_api/v1/reservations_doc.rb
Normal file
@ -0,0 +1,90 @@
|
||||
class OpenAPI::V1::ReservationsDoc < OpenAPI::V1::BaseDoc
|
||||
resource_description do
|
||||
short 'Reservations'
|
||||
desc 'Reservations made by users'
|
||||
formats FORMATS
|
||||
api_version API_VERSION
|
||||
end
|
||||
|
||||
include OpenAPI::V1::Concerns::ParamGroups
|
||||
|
||||
doc_for :index do
|
||||
api :GET, "/#{API_VERSION}/reservations", "Reservations index"
|
||||
description "Index of reservations made by users, with optional pagination. Order by *created_at* descendant."
|
||||
param_group :pagination
|
||||
param :user_id, [Integer, Array], optional: true, desc: "Scope the request to one or various users."
|
||||
param :reservable_type, ['Event', 'Machine', '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."
|
||||
|
||||
example <<-EOS
|
||||
# /open_api/v1/reservations?reservable_type=Event&page=1&per_page=3
|
||||
{
|
||||
"reservations": [
|
||||
{
|
||||
"id": 3253,
|
||||
"user_id": 1744,
|
||||
"reservable_id": 162,
|
||||
"reservable_type": "Event",
|
||||
"updated_at": "2016-05-03T14:14:00.141+02:00",
|
||||
"created_at": "2016-05-03T14:14:00.141+02:00",
|
||||
"user": {
|
||||
"id": 1744,
|
||||
"email": "xxxxxxxxxxxx",
|
||||
"created_at": "2016-05-03T13:51:03.223+02:00",
|
||||
"full_name": "xxxxxxxxxxxx"
|
||||
},
|
||||
"reservable": {
|
||||
"id": 162,
|
||||
"title": "INITIATION FAB LAB",
|
||||
"description": "A partir de 15 ans : \r\n\r\nDécouvrez le Fab Lab, familiarisez-vous avec les découpeuses laser, les imprimantes 3D, la découpeuse vinyle ... ! Fabriquez un objet simple, à ramener chez vous ! \r\n\r\nAdoptez la Fab Lab attitude !",
|
||||
"updated_at": "2016-03-21T15:55:56.306+01:00",
|
||||
"created_at": "2016-03-21T15:55:56.306+01:00"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 3252,
|
||||
"user_id": 1514,
|
||||
"reservable_id": 137,
|
||||
"reservable_type": "Event",
|
||||
"updated_at": "2016-05-03T13:54:54.072+02:00",
|
||||
"created_at": "2016-05-03T13:54:54.072+02:00",
|
||||
"user": {
|
||||
"id": 1514,
|
||||
"email": "xxxxxxxxxxxx",
|
||||
"created_at": "2016-02-24T08:45:09.050+01:00",
|
||||
"full_name": "xxxxxxxxxxxx"
|
||||
},
|
||||
"reservable": {
|
||||
"id": 137,
|
||||
"title": "INITIATION FAB LAB",
|
||||
"description": "A partir de 15 ans : \r\n\r\nDécouvrez le Fab Lab, familiarisez-vous avec les découpeuses laser, les imprimantes 3D, la découpeuse vinyle ... ! Fabriquez un objet simple, à ramener chez vous ! \r\n\r\nAdoptez la Fab Lab attitude !",
|
||||
"updated_at": "2016-05-03T13:53:47.172+02:00",
|
||||
"created_at": "2016-03-07T15:58:14.113+01:00"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 3251,
|
||||
"user_id": 1743,
|
||||
"reservable_id": 162,
|
||||
"reservable_type": "Event",
|
||||
"updated_at": "2016-05-03T12:28:50.487+02:00",
|
||||
"created_at": "2016-05-03T12:28:50.487+02:00",
|
||||
"user": {
|
||||
"id": 1743,
|
||||
"email": "xxxxxxxxxxxx",
|
||||
"created_at": "2016-05-03T12:24:38.724+02:00",
|
||||
"full_name": "xxxxxxxxxxxx"
|
||||
},
|
||||
"reservable": {
|
||||
"id": 162,
|
||||
"title": "INITIATION FAB LAB",
|
||||
"description": "A partir de 15 ans : \r\n\r\nDécouvrez le Fab Lab, familiarisez-vous avec les découpeuses laser, les imprimantes 3D, la découpeuse vinyle ... ! Fabriquez un objet simple, à ramener chez vous ! \r\n\r\nAdoptez la Fab Lab attitude !",
|
||||
"updated_at": "2016-03-21T15:55:56.306+01:00",
|
||||
"created_at": "2016-03-21T15:55:56.306+01:00"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
EOS
|
||||
end
|
||||
end
|
74
app/doc/open_api/v1/trainings_doc.rb
Normal file
74
app/doc/open_api/v1/trainings_doc.rb
Normal file
@ -0,0 +1,74 @@
|
||||
class OpenAPI::V1::TrainingsDoc < OpenAPI::V1::BaseDoc
|
||||
resource_description do
|
||||
short 'Trainings'
|
||||
desc 'Trainings of Fab-manager'
|
||||
formats FORMATS
|
||||
api_version API_VERSION
|
||||
end
|
||||
|
||||
doc_for :index do
|
||||
api :GET, "/#{API_VERSION}/trainings", "Trainings index"
|
||||
description "Trainings index. Order by *created_at* ascendant."
|
||||
example <<-EOS
|
||||
# /open_api/v1/trainings
|
||||
{
|
||||
"trainings": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Formation Imprimante 3D",
|
||||
"slug": "formation-imprimante-3d",
|
||||
"updated_at": "2015-02-05T13:49:15.025+01:00",
|
||||
"created_at": "2014-06-30T03:32:32.126+02:00",
|
||||
"nb_total_places": 8,
|
||||
"description": null
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Formation Laser / Vinyle",
|
||||
"slug": "formation-laser-vinyle",
|
||||
"updated_at": "2015-02-05T13:49:19.046+01:00",
|
||||
"created_at": "2014-06-30T03:32:32.138+02:00",
|
||||
"nb_total_places": 8,
|
||||
"description": null
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "Formation Petite fraiseuse numerique",
|
||||
"slug": "formation-petite-fraiseuse-numerique",
|
||||
"updated_at": "2015-02-05T13:49:23.040+01:00",
|
||||
"created_at": "2014-06-30T03:32:32.164+02:00",
|
||||
"nb_total_places": 8,
|
||||
"description": null
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "Formation Shopbot Grande Fraiseuse",
|
||||
"slug": "formation-shopbot-grande-fraiseuse",
|
||||
"updated_at": "2015-02-03T10:22:21.908+01:00",
|
||||
"created_at": "2014-06-30T03:32:32.168+02:00",
|
||||
"nb_total_places": 6,
|
||||
"description": null
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"name": "Formation logiciel 2D",
|
||||
"slug": "formation-logiciel-2d",
|
||||
"updated_at": "2015-02-05T13:49:27.460+01:00",
|
||||
"created_at": "2014-06-30T09:37:42.778+02:00",
|
||||
"nb_total_places": 8,
|
||||
"description": null
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"name": "Pas de Reservation",
|
||||
"slug": "pas-de-reservation",
|
||||
"updated_at": "2014-07-22T14:18:11.784+02:00",
|
||||
"created_at": "2014-07-22T14:18:11.784+02:00",
|
||||
"nb_total_places": null,
|
||||
"description": null
|
||||
}
|
||||
]
|
||||
}
|
||||
EOS
|
||||
end
|
||||
end
|
99
app/doc/open_api/v1/user_trainings_doc.rb
Normal file
99
app/doc/open_api/v1/user_trainings_doc.rb
Normal file
@ -0,0 +1,99 @@
|
||||
class OpenAPI::V1::UserTrainingsDoc < OpenAPI::V1::BaseDoc
|
||||
resource_description do
|
||||
short 'User trainings'
|
||||
desc 'Trainings validated by users'
|
||||
formats FORMATS
|
||||
api_version API_VERSION
|
||||
end
|
||||
|
||||
include OpenAPI::V1::Concerns::ParamGroups
|
||||
|
||||
doc_for :index do
|
||||
api :GET, "/#{API_VERSION}/user_trainings", "User trainings index"
|
||||
description "Index of trainings accomplished by users, with optional pagination. Order by *created_at* descendant."
|
||||
param_group :pagination
|
||||
param :training_id, [Integer, Array], optional: true, desc: "Scope the request to one or various trainings."
|
||||
param :user_id, [Integer, Array], optional: true, desc: "Scope the request to one or various users."
|
||||
example <<-EOS
|
||||
# /open_api/v1/user_trainings?training_id[]=3&training_id[]=4&page=1&per_page=2
|
||||
{
|
||||
"user_trainings": [
|
||||
{
|
||||
"id": 720,
|
||||
"user_id": 1340,
|
||||
"training_id": 3,
|
||||
"updated_at": "2016-05-03T14:16:38.373+02:00",
|
||||
"created_at": "2016-05-03T14:16:38.373+02:00",
|
||||
"user": {
|
||||
"id": 1340,
|
||||
"email": "xxxxxxxxxxx",
|
||||
"created_at": "2015-12-20T11:30:32.670+01:00",
|
||||
"full_name": "xxxxxxxxxxx"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 719,
|
||||
"user_id": 1118,
|
||||
"training_id": 4,
|
||||
"updated_at": "2016-04-29T16:55:24.651+02:00",
|
||||
"created_at": "2016-04-29T16:55:24.651+02:00",
|
||||
"user": {
|
||||
"id": 1118,
|
||||
"email": "xxxxxxxxxxx",
|
||||
"created_at": "2015-10-08T19:18:26.188+02:00",
|
||||
"full_name": "xxxxxxxxxxx"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# /open_api/v1/user_trainings?user_id=1340&page=1&per_page=3
|
||||
{
|
||||
"user_trainings": [
|
||||
{
|
||||
"id": 720,
|
||||
"user_id": 1340,
|
||||
"training_id": 3,
|
||||
"updated_at": "2016-05-03T14:16:38.373+02:00",
|
||||
"created_at": "2016-05-03T14:16:38.373+02:00",
|
||||
"training": {
|
||||
"id": 3,
|
||||
"name": "Formation Petite fraiseuse numerique",
|
||||
"slug": "formation-petite-fraiseuse-numerique",
|
||||
"updated_at": "2015-02-05T13:49:23.040+01:00",
|
||||
"created_at": "2014-06-30T03:32:32.164+02:00"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 700,
|
||||
"user_id": 1340,
|
||||
"training_id": 2,
|
||||
"updated_at": "2016-04-19T22:02:17.083+02:00",
|
||||
"created_at": "2016-04-19T22:02:17.083+02:00",
|
||||
"training": {
|
||||
"id": 2,
|
||||
"name": "Formation Laser / Vinyle",
|
||||
"slug": "formation-laser-vinyle",
|
||||
"updated_at": "2015-02-05T13:49:19.046+01:00",
|
||||
"created_at": "2014-06-30T03:32:32.138+02:00"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 694,
|
||||
"user_id": 1340,
|
||||
"training_id": 1,
|
||||
"updated_at": "2016-04-13T09:22:49.633+02:00",
|
||||
"created_at": "2016-04-13T09:22:49.633+02:00",
|
||||
"training": {
|
||||
"id": 1,
|
||||
"name": "Formation Imprimante 3D",
|
||||
"slug": "formation-imprimante-3d",
|
||||
"updated_at": "2015-02-05T13:49:15.025+01:00",
|
||||
"created_at": "2014-06-30T03:32:32.126+02:00"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
EOS
|
||||
end
|
||||
end
|
97
app/doc/open_api/v1/users_doc.rb
Normal file
97
app/doc/open_api/v1/users_doc.rb
Normal file
@ -0,0 +1,97 @@
|
||||
class OpenAPI::V1::UsersDoc < OpenAPI::V1::BaseDoc
|
||||
resource_description do
|
||||
short 'Users'
|
||||
desc 'Users of Fab-manager'
|
||||
formats FORMATS
|
||||
api_version API_VERSION
|
||||
end
|
||||
|
||||
include OpenAPI::V1::Concerns::ParamGroups
|
||||
|
||||
doc_for :index do
|
||||
api :GET, "/#{API_VERSION}/users", "Users index"
|
||||
description "Users index, with optional pagination. Order by *created_at* descendant."
|
||||
param_group :pagination
|
||||
param :email, [String, Array], optional: true, desc: "Filter users by *email* using strict matching."
|
||||
param :user_id, [Integer, Array], optional: true, desc: "Filter users by *id* using strict matching."
|
||||
example <<-EOS
|
||||
# /open_api/v1/users?page=1&per_page=4
|
||||
{
|
||||
"users": [
|
||||
{
|
||||
"id": 1746,
|
||||
"email": "xxxxxxx@xxxx.com",
|
||||
"created_at": "2016-05-04T17:21:48.403+02:00",
|
||||
"full_name": "xxxx xxxx",
|
||||
"group": {
|
||||
"id": 1,
|
||||
"name": "standard, association",
|
||||
"slug": "standard"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 1745,
|
||||
"email": "xxxxxxx@gmail.com",
|
||||
"created_at": "2016-05-03T15:21:13.125+02:00",
|
||||
"full_name": "xxxxx xxxxx",
|
||||
"group": {
|
||||
"id": 2,
|
||||
"name": "étudiant, - de 25 ans, enseignant, demandeur d'emploi",
|
||||
"slug": "student"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 1744,
|
||||
"email": "xxxxxxx@gmail.com",
|
||||
"created_at": "2016-05-03T13:51:03.223+02:00",
|
||||
"full_name": "xxxxxxx xxxx",
|
||||
"group": {
|
||||
"id": 1,
|
||||
"name": "standard, association",
|
||||
"slug": "standard"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 1743,
|
||||
"email": "xxxxxxxx@setecastronomy.eu",
|
||||
"created_at": "2016-05-03T12:24:38.724+02:00",
|
||||
"full_name": "xxx xxxxxxx",
|
||||
"group": {
|
||||
"id": 1,
|
||||
"name": "standard, association",
|
||||
"slug": "standard"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# /open_api/v1/users?user_id[]=1746&user_id[]=1745
|
||||
{
|
||||
"users": [
|
||||
{
|
||||
"id": 1746,
|
||||
"email": "xxxxxxxxxxxx",
|
||||
"created_at": "2016-05-04T17:21:48.403+02:00",
|
||||
"full_name": "xxxx xxxxxx",
|
||||
"group": {
|
||||
"id": 1,
|
||||
"name": "standard, association",
|
||||
"slug": "standard"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 1745,
|
||||
"email": "xxxxxxxxx@gmail.com",
|
||||
"created_at": "2016-05-03T15:21:13.125+02:00",
|
||||
"full_name": "xxxxx xxxxxx",
|
||||
"group": {
|
||||
"id": 2,
|
||||
"name": "étudiant, - de 25 ans, enseignant, demandeur d'emploi",
|
||||
"slug": "student"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
EOS
|
||||
end
|
||||
end
|
@ -16,7 +16,7 @@ class Availability < ActiveRecord::Base
|
||||
accepts_nested_attributes_for :tags, allow_destroy: true
|
||||
|
||||
scope :machines, -> { where(available_type: 'machines') }
|
||||
scope :trainings, -> { where(available_type: 'training') }
|
||||
scope :trainings, -> { includes(:trainings).where(available_type: 'training') }
|
||||
|
||||
attr_accessor :is_reserved, :slot_id, :can_modify
|
||||
|
||||
@ -51,9 +51,10 @@ class Availability < ActiveRecord::Base
|
||||
# if haven't defined a nb_total_places, places are unlimited
|
||||
def is_completed
|
||||
return false if nb_total_places.blank?
|
||||
nb_total_places <= slots.where(canceled_at: nil).size
|
||||
nb_total_places <= slots.to_a.select {|s| s.canceled_at == nil }.size
|
||||
end
|
||||
|
||||
# TODO: refactoring this function for avoid N+1 query
|
||||
def nb_total_places
|
||||
if read_attribute(:nb_total_places).present?
|
||||
read_attribute(:nb_total_places)
|
||||
|
@ -7,6 +7,7 @@ class Event < ActiveRecord::Base
|
||||
accepts_nested_attributes_for :event_files, allow_destroy: true, reject_if: :all_blank
|
||||
has_and_belongs_to_many :categories, join_table: :events_categories
|
||||
validates :categories, presence: true
|
||||
has_many :reservations, as: :reservable, dependent: :destroy
|
||||
|
||||
belongs_to :availability, dependent: :destroy
|
||||
accepts_nested_attributes_for :availability
|
||||
@ -15,6 +16,8 @@ class Event < ActiveRecord::Base
|
||||
|
||||
after_create :event_recurrence
|
||||
before_save :update_nb_free_places
|
||||
# update event updated_at for index cache
|
||||
after_save -> { self.touch }
|
||||
|
||||
def name
|
||||
title
|
||||
@ -33,9 +36,9 @@ class Event < ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
|
||||
def reservations
|
||||
Reservation.where(reservable: self)
|
||||
end
|
||||
# def reservations
|
||||
# Reservation.where(reservable: self)
|
||||
# end
|
||||
|
||||
private
|
||||
def event_recurrence
|
||||
|
5
app/models/open_api.rb
Normal file
5
app/models/open_api.rb
Normal file
@ -0,0 +1,5 @@
|
||||
module OpenAPI
|
||||
def self.table_name_prefix
|
||||
'open_api_'
|
||||
end
|
||||
end
|
4
app/models/open_api/calls_count_tracing.rb
Normal file
4
app/models/open_api/calls_count_tracing.rb
Normal file
@ -0,0 +1,4 @@
|
||||
class OpenAPI::CallsCountTracing < ActiveRecord::Base
|
||||
belongs_to :client, foreign_key: :open_api_client_id
|
||||
validates :client, :at, presence: true
|
||||
end
|
9
app/models/open_api/client.rb
Normal file
9
app/models/open_api/client.rb
Normal file
@ -0,0 +1,9 @@
|
||||
class OpenAPI::Client < ActiveRecord::Base
|
||||
has_many :calls_count_tracings, foreign_key: :open_api_client_id, dependent: :destroy
|
||||
has_secure_token
|
||||
validates :name, presence: true
|
||||
|
||||
def increment_calls_count
|
||||
update_column(:calls_count, calls_count+1)
|
||||
end
|
||||
end
|
1
app/models/open_api/parameter_error.rb
Normal file
1
app/models/open_api/parameter_error.rb
Normal file
@ -0,0 +1 @@
|
||||
class OpenAPI::ParameterError < StandardError; end
|
@ -2,12 +2,12 @@ class EventPolicy < ApplicationPolicy
|
||||
class Scope < Scope
|
||||
def resolve
|
||||
if user.nil? or (user and !user.is_admin?)
|
||||
scope.includes(:event_image, :event_files, :availability)
|
||||
scope.includes(:event_image, :event_files, :availability, :categories)
|
||||
.where('availabilities.start_at >= ?', Time.now)
|
||||
.order('availabilities.start_at ASC')
|
||||
.references(:availabilities)
|
||||
else
|
||||
scope.includes(:event_image, :event_files, :availability)
|
||||
scope.includes(:event_image, :event_files, :availability, :categories)
|
||||
.order('availabilities.start_at DESC')
|
||||
.references(:availabilities)
|
||||
end
|
||||
|
21
app/policies/open_api/client_policy.rb
Normal file
21
app/policies/open_api/client_policy.rb
Normal file
@ -0,0 +1,21 @@
|
||||
class OpenAPI::ClientPolicy < ApplicationPolicy
|
||||
def index?
|
||||
user.has_role? :admin
|
||||
end
|
||||
|
||||
def create?
|
||||
user.has_role? :admin
|
||||
end
|
||||
|
||||
def update?
|
||||
user.has_role? :admin
|
||||
end
|
||||
|
||||
def reset_token?
|
||||
user.has_role? :admin
|
||||
end
|
||||
|
||||
def destroy?
|
||||
user.has_role? :admin and record.calls_count == 0
|
||||
end
|
||||
end
|
@ -17,7 +17,7 @@ json.array!(@slots) do |slot|
|
||||
json.user do
|
||||
json.id slot.reservation.user.id
|
||||
json.name slot.reservation.user.profile.full_name
|
||||
end if slot.reservation # ... if the slot was reserved
|
||||
end if @current_user_role == 'admin' and slot.reservation # ... if the slot was reserved
|
||||
json.tag_ids slot.availability.tag_ids
|
||||
json.tags slot.availability.tags do |t|
|
||||
json.id t.id
|
||||
|
@ -2,26 +2,21 @@ json.array!(@availabilities) do |a|
|
||||
json.id a.id
|
||||
json.slot_id a.slot_id if a.slot_id
|
||||
if a.is_reserved
|
||||
json.is_reserved true
|
||||
json.title "#{a.trainings[0].name}' - #{t('trainings.i_ve_reserved')}"
|
||||
json.borderColor '#b2e774'
|
||||
elsif a.is_completed
|
||||
json.is_completed true
|
||||
json.title "#{a.trainings[0].name} - #{t('trainings.completed')}"
|
||||
json.borderColor '#eeeeee'
|
||||
else
|
||||
json.title a.trainings[0].name
|
||||
json.borderColor '#bd7ae9'
|
||||
end
|
||||
json.start a.start_at.iso8601
|
||||
json.end a.end_at.iso8601
|
||||
json.is_reserved a.is_reserved
|
||||
json.backgroundColor 'white'
|
||||
json.borderColor a.is_reserved ? '#b2e774' : '#bd7ae9'
|
||||
if a.is_reserved
|
||||
json.borderColor '#b2e774'
|
||||
elsif a.is_completed
|
||||
json.borderColor '#eeeeee'
|
||||
else
|
||||
json.borderColor '#bd7ae9'
|
||||
end
|
||||
json.can_modify a.can_modify
|
||||
json.is_completed a.is_completed
|
||||
json.nb_total_places a.nb_total_places
|
||||
|
||||
json.training do
|
||||
@ -32,7 +27,6 @@ json.array!(@availabilities) do |a|
|
||||
json.id m.id
|
||||
json.name m.name
|
||||
end
|
||||
json.amount a.trainings.first.amount_by_group(@user.group_id).amount_by_plan(nil)/100.0 if @user
|
||||
end
|
||||
json.tag_ids a.tag_ids
|
||||
json.tags a.tags do |t|
|
||||
|
@ -1,7 +1,8 @@
|
||||
json.array!(@events) do |event|
|
||||
json.partial! 'api/events/event', event: event
|
||||
json.event_image_small event.event_image.attachment.small.url if event.event_image
|
||||
json.url event_url(event, format: :json)
|
||||
json.nb_total_events @total
|
||||
json.cache! [@events, @page] do
|
||||
json.array!(@events) do |event|
|
||||
json.partial! 'api/events/event', event: event
|
||||
json.event_image_small event.event_image.attachment.small.url if event.event_image
|
||||
json.url event_url(event, format: :json)
|
||||
json.nb_total_events @total
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
json.array!(@events) do |event|
|
||||
json.partial! 'api/events/event', event: event
|
||||
json.event_image_medium event.event_image.attachment.medium.url if event.event_image
|
||||
json.url event_url(event, format: :json)
|
||||
json.cache! @events do
|
||||
json.array!(@events) do |event|
|
||||
json.partial! 'api/events/event', event: event
|
||||
json.event_image_medium event.event_image.attachment.medium.url if event.event_image
|
||||
json.url event_url(event, format: :json)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1 +1,3 @@
|
||||
json.partial! 'api/groups/group', collection: @groups, as: :group
|
||||
json.cache! @groups do
|
||||
json.partial! 'api/groups/group', collection: @groups, as: :group
|
||||
end
|
||||
|
@ -1,13 +1,7 @@
|
||||
user_is_admin = (current_user and current_user.is_admin?)
|
||||
|
||||
json.array!(@machines) do |machine|
|
||||
json.extract! machine, :id, :name, :description, :spec, :slug
|
||||
json.url machine_url(machine, format: :json)
|
||||
json.machine_image machine.machine_image.attachment.medium.url if machine.machine_image
|
||||
json.current_user_is_training current_user.is_training_machine?(machine) if current_user
|
||||
json.current_user_training_reservation do
|
||||
json.partial! 'api/reservations/reservation', reservation: current_user.training_reservation_by_machine(machine)
|
||||
end if current_user and !current_user.is_training_machine?(machine) and current_user.training_reservation_by_machine(machine)
|
||||
|
||||
json.plan_ids machine.plan_ids if user_is_admin
|
||||
json.cache! @machines do
|
||||
json.array!(@machines) do |machine|
|
||||
json.extract! machine, :id, :name, :description, :spec, :slug
|
||||
json.url machine_url(machine, format: :json)
|
||||
json.machine_image machine.machine_image.attachment.medium.url if machine.machine_image
|
||||
end
|
||||
end
|
||||
|
@ -11,16 +11,6 @@ json.current_user_training_reservation do
|
||||
json.partial! 'api/reservations/reservation', reservation: current_user.training_reservation_by_machine(@machine)
|
||||
end if current_user and !current_user.is_training_machine?(@machine) and current_user.training_reservation_by_machine(@machine)
|
||||
|
||||
json.amount_by_group Group.all do |g|
|
||||
json.id g.id
|
||||
json.name g.name
|
||||
json.not_subscribe_amount @machine.not_subscribe_price(g.id).amount/100.0
|
||||
|
||||
json.amount_by_plan @machine.prices_by_group(g.id) do |price|
|
||||
json.plan_id price.plan_id
|
||||
json.amount price.amount/100.0
|
||||
end
|
||||
end
|
||||
json.machine_projects @machine.projects.published.last(10) do |p|
|
||||
json.id p.id
|
||||
json.name p.name
|
||||
|
1
app/views/api/open_api_clients/_client.json.jbuilder
Normal file
1
app/views/api/open_api_clients/_client.json.jbuilder
Normal file
@ -0,0 +1 @@
|
||||
json.extract! client, :id, :name, :calls_count, :token, :created_at
|
1
app/views/api/open_api_clients/create.json.jbuilder
Normal file
1
app/views/api/open_api_clients/create.json.jbuilder
Normal file
@ -0,0 +1 @@
|
||||
json.partial! 'api/open_api_clients/client', client: @client
|
3
app/views/api/open_api_clients/index.json.jbuilder
Normal file
3
app/views/api/open_api_clients/index.json.jbuilder
Normal file
@ -0,0 +1,3 @@
|
||||
json.array! @clients do |client|
|
||||
json.partial! 'api/open_api_clients/client', client: client
|
||||
end
|
1
app/views/api/open_api_clients/reset_token.json.jbuilder
Normal file
1
app/views/api/open_api_clients/reset_token.json.jbuilder
Normal file
@ -0,0 +1 @@
|
||||
json.partial! 'api/open_api_clients/client', client: @client
|
1
app/views/api/open_api_clients/update.json.jbuilder
Normal file
1
app/views/api/open_api_clients/update.json.jbuilder
Normal file
@ -0,0 +1 @@
|
||||
json.partial! 'api/open_api_clients/client', client: @client
|
@ -6,23 +6,8 @@ json.plan_file_attributes do
|
||||
json.attachment_identifier plan.plan_file.attachment_identifier
|
||||
end if plan.plan_file
|
||||
|
||||
json.prices plan.prices do |price|
|
||||
json.extract! price, :id, :group_id, :plan_id, :priceable_type, :priceable_id
|
||||
json.amount price.amount / 100.0
|
||||
json.priceable_name price.priceable.name
|
||||
end
|
||||
|
||||
json.partners plan.partners do |partner|
|
||||
json.first_name partner.first_name
|
||||
json.last_name partner.last_name
|
||||
json.email partner.email
|
||||
end if plan.respond_to?(:partners)
|
||||
|
||||
json.training_credits plan.training_credits do |tc|
|
||||
json.training_id tc.creditable_id
|
||||
end if attribute_requested?(@attributes_requested, 'trainings_credits')
|
||||
|
||||
json.machine_credits plan.machine_credits do |mc|
|
||||
json.machine_id mc.creditable_id
|
||||
json.hours mc.hours
|
||||
end if attribute_requested?(@attributes_requested, 'machines_credits')
|
||||
|
@ -1 +1,5 @@
|
||||
json.partial! 'api/plans/plan', collection: @plans, as: :plan
|
||||
json.array!(@plans) do |plan|
|
||||
json.extract! plan, :id, :base_name, :name, :interval, :interval_count, :group_id, :training_credit_nb, :description, :type, :ui_weight
|
||||
json.amount (plan.amount / 100.00)
|
||||
json.plan_file_url plan.plan_file.attachment_url if plan.plan_file
|
||||
end
|
||||
|
@ -1 +1,3 @@
|
||||
json.prices @prices, partial: 'api/prices/price', as: :price
|
||||
json.cache! @prices do
|
||||
json.partial! 'api/prices/price', collection: @prices, as: :price
|
||||
end
|
||||
|
@ -1,19 +1,13 @@
|
||||
json.array!(@trainings) do |training|
|
||||
json.id training.id
|
||||
json.name training.name
|
||||
json.description training.description
|
||||
json.machine_ids training.machine_ids
|
||||
json.availabilities training.availabilities do |a|
|
||||
json.id a.id
|
||||
json.start_at a.start_at.iso8601
|
||||
json.end_at a.end_at.iso8601
|
||||
json.reservation_users a.slots.map do |slot|
|
||||
json.id slot.reservation.user.id
|
||||
json.full_name slot.reservation.user.profile.full_name
|
||||
json.is_valid slot.reservation.user.trainings.include?(training)
|
||||
end
|
||||
end if attribute_requested?(@requested_attributes, 'availabilities')
|
||||
json.nb_total_places training.nb_total_places
|
||||
role = (current_user and current_user.is_admin?) ? 'admin' : 'user'
|
||||
|
||||
json.plan_ids training.plan_ids if current_user and current_user.is_admin?
|
||||
json.cache! [@trainings, role] do
|
||||
json.array!(@trainings) do |training|
|
||||
json.id training.id
|
||||
json.name training.name
|
||||
json.description training.description
|
||||
json.machine_ids training.machine_ids
|
||||
json.nb_total_places training.nb_total_places
|
||||
|
||||
json.plan_ids training.plan_ids if role === 'admin'
|
||||
end
|
||||
end
|
||||
|
@ -1,5 +1,5 @@
|
||||
json.extract! @training, :id, :name, :machine_ids, :nb_total_places
|
||||
json.availabilities @training.availabilities.order('start_at DESC') do |a|
|
||||
json.availabilities @training.availabilities do |a|
|
||||
json.id a.id
|
||||
json.start_at a.start_at.iso8601
|
||||
json.end_at a.end_at.iso8601
|
||||
|
@ -1,5 +1,2 @@
|
||||
json.extract! trainings_pricing, :id, :group_id, :training_id
|
||||
json.amount trainings_pricing.amount / 100.0
|
||||
json.training do
|
||||
json.name trainings_pricing.training.name
|
||||
end
|
||||
|
@ -0,0 +1,5 @@
|
||||
json.machines @machines do |machine|
|
||||
json.partial! 'open_api/v1/machines/machine', machine: machine
|
||||
json.extract! machine, :description, :spec
|
||||
json.hours_remaining @hours_remaining[machine.id]
|
||||
end
|
1
app/views/open_api/v1/events/_event.json.jbuilder
Normal file
1
app/views/open_api/v1/events/_event.json.jbuilder
Normal file
@ -0,0 +1 @@
|
||||
json.extract! event, :id, :title, :description, :updated_at, :created_at
|
4
app/views/open_api/v1/events/index.json.jbuilder
Normal file
4
app/views/open_api/v1/events/index.json.jbuilder
Normal file
@ -0,0 +1,4 @@
|
||||
json.events @events do |event|
|
||||
json.partial! 'open_api/v1/events/event', event: event
|
||||
json.extract! event, :amount, :reduced_amount, :nb_total_places, :nb_free_places
|
||||
end
|
8
app/views/open_api/v1/invoices/index.json.jbuilder
Normal file
8
app/views/open_api/v1/invoices/index.json.jbuilder
Normal file
@ -0,0 +1,8 @@
|
||||
json.invoices @invoices do |invoice|
|
||||
json.extract! invoice, :id, :invoiced_id, :user_id, :invoiced_type, :stp_invoice_id, :reference, :total, :type, :description
|
||||
|
||||
json.invoice_url download_open_api_v1_invoice_path(invoice)
|
||||
json.invoiced do
|
||||
json.created_at invoice.invoiced.created_at
|
||||
end
|
||||
end
|
1
app/views/open_api/v1/machines/_machine.json.jbuilder
Normal file
1
app/views/open_api/v1/machines/_machine.json.jbuilder
Normal file
@ -0,0 +1 @@
|
||||
json.extract! machine, :id, :name, :slug, :updated_at, :created_at
|
4
app/views/open_api/v1/machines/index.json.jbuilder
Normal file
4
app/views/open_api/v1/machines/index.json.jbuilder
Normal file
@ -0,0 +1,4 @@
|
||||
json.machines @machines do |machine|
|
||||
json.partial! 'open_api/v1/machines/machine', machine: machine
|
||||
json.extract! machine, :description, :spec
|
||||
end
|
19
app/views/open_api/v1/reservations/index.json.jbuilder
Normal file
19
app/views/open_api/v1/reservations/index.json.jbuilder
Normal file
@ -0,0 +1,19 @@
|
||||
json.reservations @reservations do |reservation|
|
||||
json.extract! reservation, :id, :user_id, :reservable_id, :reservable_type, :updated_at, :created_at
|
||||
|
||||
if reservation.association(:user).loaded?
|
||||
json.user do
|
||||
json.partial! 'open_api/v1/users/user', user: reservation.user
|
||||
end
|
||||
end
|
||||
|
||||
json.reservable do
|
||||
if reservation.reservable_type == "Training"
|
||||
json.partial! 'open_api/v1/trainings/training', training: reservation.reservable
|
||||
elsif reservation.reservable_type == "Machine"
|
||||
json.partial! 'open_api/v1/machines/machine', machine: reservation.reservable
|
||||
elsif reservation.reservable_type == "Event"
|
||||
json.partial! 'open_api/v1/events/event', event: reservation.reservable
|
||||
end
|
||||
end
|
||||
end
|
1
app/views/open_api/v1/trainings/_training.json.jbuilder
Normal file
1
app/views/open_api/v1/trainings/_training.json.jbuilder
Normal file
@ -0,0 +1 @@
|
||||
json.extract! training, :id, :name, :slug, :updated_at, :created_at
|
4
app/views/open_api/v1/trainings/index.json.jbuilder
Normal file
4
app/views/open_api/v1/trainings/index.json.jbuilder
Normal file
@ -0,0 +1,4 @@
|
||||
json.trainings @trainings do |training|
|
||||
json.partial! 'open_api/v1/trainings/training', training: training
|
||||
json.extract! training, :nb_total_places, :description
|
||||
end
|
15
app/views/open_api/v1/user_trainings/index.json.jbuilder
Normal file
15
app/views/open_api/v1/user_trainings/index.json.jbuilder
Normal file
@ -0,0 +1,15 @@
|
||||
json.user_trainings @user_trainings do |user_training|
|
||||
json.extract! user_training, :id, :user_id, :training_id, :updated_at, :created_at
|
||||
|
||||
if user_training.association(:user).loaded?
|
||||
json.user do
|
||||
json.partial! 'open_api/v1/users/user', user: user_training.user
|
||||
end
|
||||
end
|
||||
|
||||
if user_training.association(:training).loaded?
|
||||
json.training do
|
||||
json.partial! 'open_api/v1/trainings/training', training: user_training.training
|
||||
end
|
||||
end
|
||||
end
|
15
app/views/open_api/v1/users/_user.json.jbuilder
Normal file
15
app/views/open_api/v1/users/_user.json.jbuilder
Normal file
@ -0,0 +1,15 @@
|
||||
json.extract! user, :id, :email, :created_at
|
||||
|
||||
if user.association(:profile).loaded?
|
||||
json.full_name user.profile.full_name
|
||||
end
|
||||
|
||||
if user.association(:group).loaded?
|
||||
json.group do
|
||||
if user.group_id?
|
||||
json.extract! user.group, :id, :name, :slug
|
||||
else
|
||||
json.nil!
|
||||
end
|
||||
end
|
||||
end
|
3
app/views/open_api/v1/users/index.json.jbuilder
Normal file
3
app/views/open_api/v1/users/index.json.jbuilder
Normal file
@ -0,0 +1,3 @@
|
||||
json.users @users do |user|
|
||||
json.partial! 'open_api/v1/users/user', user: user
|
||||
end
|
10
app/workers/open_api_trace_calls_count_worker.rb
Normal file
10
app/workers/open_api_trace_calls_count_worker.rb
Normal file
@ -0,0 +1,10 @@
|
||||
class OpenAPITraceCallsCountWorker < ActiveJob::Base
|
||||
include Sidekiq::Worker
|
||||
sidekiq_options queue: 'default', retry: true
|
||||
|
||||
def perform
|
||||
OpenAPI::Client.find_each do |client|
|
||||
OpenAPI::CallsCountTracing.create!(client: client, calls_count: client.calls_count, at: DateTime.now)
|
||||
end
|
||||
end
|
||||
end
|
@ -36,7 +36,6 @@ module Fablab
|
||||
#
|
||||
config.i18n.default_locale = Rails.application.secrets.rails_locale
|
||||
|
||||
|
||||
config.assets.paths << Rails.root.join('vendor', 'assets', 'components').to_s
|
||||
|
||||
# Do not swallow errors in after_commit/after_rollback callbacks.
|
||||
|
@ -11,7 +11,7 @@ Rails.application.configure do
|
||||
|
||||
# Show full error reports and disable caching.
|
||||
config.consider_all_requests_local = true
|
||||
config.action_controller.perform_caching = false
|
||||
config.action_controller.perform_caching = true
|
||||
|
||||
# Don't care if the mailer can't send.
|
||||
config.action_mailer.raise_delivery_errors = false
|
||||
|
19
config/initializers/apipie.rb
Normal file
19
config/initializers/apipie.rb
Normal file
@ -0,0 +1,19 @@
|
||||
Apipie.configure do |config|
|
||||
config.app_name = "Fab-manager"
|
||||
config.api_base_url = "/open_api"
|
||||
config.doc_base_url = "/open_api/doc"
|
||||
# where is your API defined?
|
||||
config.api_controllers_matcher = "#{Rails.root}/app/controllers/open_api/v1/*.rb"
|
||||
config.validate = false
|
||||
config.app_info['v1'] = <<-EOS
|
||||
= Pagination
|
||||
---
|
||||
Pagination is done using headers. Following RFC-5988 standard for web linking.
|
||||
It uses headers *Link*, *Total* and *Per-Page*.
|
||||
|
||||
= Authentification
|
||||
---
|
||||
Authentification is done using *Authorization* header.
|
||||
You just have to set header *Authorization* to <tt>Token token=YOUR_TOKEN</tt> for every request.
|
||||
EOS
|
||||
end
|
@ -468,3 +468,18 @@ en:
|
||||
reservations_cancelling: "Reservations cancelling"
|
||||
customization_of_SETTING_successfully_saved: "Customization of {{SETTING}} successfully saved." # angular interpolation
|
||||
file_successfully_updated: "File successfully updated."
|
||||
|
||||
open_api_clients:
|
||||
add_new_client: "Create new API client"
|
||||
api_documentation: "API documentation"
|
||||
open_api_clients: "OpenAPI clients"
|
||||
calls_count: "calls count"
|
||||
created_at: "Creation date"
|
||||
reset_token: "revoke access"
|
||||
client_name: "Client's name"
|
||||
do_you_really_want_to_delete_this_open_api_client: "Do you really want to delete this OpenAPI client?"
|
||||
do_you_really_want_to_revoke_this_open_api_access: "Do you really want to revoke this access ? It will erase and replace the current token."
|
||||
client_successfully_created: "Client successfully created."
|
||||
client_successfully_updated: "Client successfully updated."
|
||||
client_successfully_deleted: "Client successfully deleted."
|
||||
access_successfully_revoked: "Access successfully revoked."
|
||||
|
@ -468,3 +468,18 @@ fr:
|
||||
reservations_cancelling: "Annulation des réservations"
|
||||
customization_of_SETTING_successfully_saved: "La personnalisation de {{SETTING}} a bien été enregistrée." # angular interpolation
|
||||
file_successfully_updated: "Le fichier a bien été mis à jour."
|
||||
|
||||
open_api_clients:
|
||||
add_new_client: "Créer un compte client"
|
||||
api_documentation: "Documentation de l'API"
|
||||
open_api_clients: "Clients OpenAPI"
|
||||
calls_count: "Nombre d'appels"
|
||||
created_at: "Date de création"
|
||||
reset_token: "Révoquer l'accès"
|
||||
client_name: "Nom du client"
|
||||
do_you_really_want_to_delete_this_open_api_client: "Voulez vous vraiment supprimer ce compte client OpenAPI ?"
|
||||
do_you_really_want_to_revoke_this_open_api_access: "Voulez vous vraiment revoquer l'accès de ce compte OpenAPI ? Une confirmation aura pour effet la génération d'un nouveau token."
|
||||
client_successfully_created: "Le compte client a bien été créé."
|
||||
client_successfully_updated: "Les modifications ont été enregistrées."
|
||||
client_successfully_deleted: "Le compte client a bien été supprimé."
|
||||
access_successfully_revoked: "L'accès a bien été revoqué."
|
||||
|
@ -45,6 +45,7 @@ en:
|
||||
manage_the_projects_elements: "Manage the Projects Elements"
|
||||
statistics: "Statistics"
|
||||
customization: "Customization"
|
||||
open_api_clients: "OpenAPI clients"
|
||||
|
||||
# account creation modal
|
||||
create_your_account: "Create your account"
|
||||
@ -155,6 +156,7 @@ en:
|
||||
tell_us_why_this_looks_abusive: "Tell us why this looks abusive"
|
||||
message_is_required: "Message is required."
|
||||
report: "Report"
|
||||
do_you_really_want_to_delete_this_project: "Do you really want to delete this project?"
|
||||
|
||||
machines_list:
|
||||
# list of machines
|
||||
|
@ -45,6 +45,7 @@ fr:
|
||||
manage_the_projects_elements: "Gérer les éléments projets"
|
||||
statistics: "Statistiques"
|
||||
customization: "Personnalisation"
|
||||
open_api_clients: "Clients OpenAPI"
|
||||
|
||||
# fenêtre de création de compte
|
||||
create_your_account: "Créer votre compte"
|
||||
@ -155,6 +156,7 @@ fr:
|
||||
tell_us_why_this_looks_abusive: "Dites nous en quoi cela vous semble abusif"
|
||||
message_is_required: "Le message est requis."
|
||||
report: "Signaler"
|
||||
do_you_really_want_to_delete_this_project: "Êtes-vous sur de vouloir supprimer ce projet ?"
|
||||
|
||||
machines_list:
|
||||
# liste des machines
|
||||
|
@ -80,7 +80,7 @@ Rails.application.routes.draw do
|
||||
end
|
||||
|
||||
resources :invoices, only: [:index, :show, :create] do
|
||||
get ':id/download', action: 'download', on: :collection
|
||||
get 'download', action: 'download', on: :member
|
||||
post 'list', action: 'list', on: :collection
|
||||
end
|
||||
|
||||
@ -97,11 +97,34 @@ Rails.application.routes.draw do
|
||||
get 'active', action: 'active', on: :collection
|
||||
end
|
||||
resources :abuses, only: [:create]
|
||||
resources :open_api_clients, only: [:index, :create, :update, :destroy] do
|
||||
patch :reset_token, on: :member
|
||||
end
|
||||
|
||||
# i18n
|
||||
get 'translations/:locale/:state' => 'translations#show', :constraints => { :state => /[^\/]+/ } # allow dots in URL for 'state'
|
||||
end
|
||||
|
||||
# open_api
|
||||
|
||||
namespace :open_api do
|
||||
namespace :v1 do
|
||||
scope only: :index do
|
||||
resources :users
|
||||
resources :trainings
|
||||
resources :user_trainings
|
||||
resources :reservations
|
||||
resources :machines
|
||||
resources :bookable_machines
|
||||
resources :invoices do
|
||||
get :download, on: :member
|
||||
end
|
||||
resources :events
|
||||
resources :availabilities
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
%w(account event machine project subscription training user).each do |path|
|
||||
post "/stats/#{path}/_search", to: "api/statistics##{path}"
|
||||
end
|
||||
@ -113,4 +136,5 @@ Rails.application.routes.draw do
|
||||
mount Sidekiq::Web => '/admin/sidekiq'
|
||||
end
|
||||
|
||||
apipie
|
||||
end
|
||||
|
@ -15,4 +15,8 @@ generate_statistic:
|
||||
class: "StatisticWorker"
|
||||
queue: default
|
||||
|
||||
open_api_trace_calls_count:
|
||||
cron: "0 4 * * 0" # every sunday at 4am
|
||||
class: "OpenAPITraceCallsCountWorker"
|
||||
|
||||
<%= PluginRegistry.insert_code('yml.schedule') %>
|
||||
|
10
db/migrate/20160504085703_create_open_api_clients.rb
Normal file
10
db/migrate/20160504085703_create_open_api_clients.rb
Normal file
@ -0,0 +1,10 @@
|
||||
class CreateOpenAPIClients < ActiveRecord::Migration
|
||||
def change
|
||||
create_table :open_api_clients do |t|
|
||||
t.string :name
|
||||
t.integer :calls_count, default: 0
|
||||
t.string :token
|
||||
t.timestamps null: false
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,10 @@
|
||||
class CreateOpenAPICallsCountTracings < ActiveRecord::Migration
|
||||
def change
|
||||
create_table :open_api_calls_count_tracings do |t|
|
||||
t.belongs_to :open_api_client, foreign_key: true, index: true
|
||||
t.integer :calls_count, null: false
|
||||
t.datetime :at, null: false
|
||||
t.timestamps null: false
|
||||
end
|
||||
end
|
||||
end
|
@ -2,8 +2,8 @@ class CreateUnaccentFunction < ActiveRecord::Migration
|
||||
|
||||
# PostgreSQL only
|
||||
def up
|
||||
execute 'CREATE EXTENSION unaccent;'
|
||||
execute 'CREATE EXTENSION pg_trgm;'
|
||||
execute 'CREATE EXTENSION IF NOT EXISTS unaccent;'
|
||||
execute 'CREATE EXTENSION IF NOT EXISTS pg_trgm;'
|
||||
execute "CREATE OR REPLACE FUNCTION f_unaccent(text)
|
||||
RETURNS text AS
|
||||
$func$
|
||||
|
@ -11,7 +11,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20160526102307) do
|
||||
ActiveRecord::Schema.define(version: 20160613093842) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
@ -227,7 +227,7 @@ ActiveRecord::Schema.define(version: 20160526102307) do
|
||||
t.boolean "is_read", default: false
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.string "receiver_type", limit: 255
|
||||
t.string "receiver_type"
|
||||
t.boolean "is_send", default: false
|
||||
t.jsonb "meta_data", default: {}
|
||||
end
|
||||
@ -456,8 +456,8 @@ ActiveRecord::Schema.define(version: 20160526102307) do
|
||||
t.datetime "updated_at"
|
||||
t.integer "availability_id"
|
||||
t.datetime "ex_start_at"
|
||||
t.datetime "canceled_at"
|
||||
t.datetime "ex_end_at"
|
||||
t.datetime "canceled_at"
|
||||
t.boolean "offered", default: false
|
||||
end
|
||||
|
||||
@ -615,7 +615,6 @@ ActiveRecord::Schema.define(version: 20160526102307) 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 "username", limit: 255
|
||||
t.string "email", limit: 255, default: "", null: false
|
||||
t.string "encrypted_password", limit: 255, default: "", null: false
|
||||
t.string "reset_password_token", limit: 255
|
||||
@ -638,6 +637,7 @@ ActiveRecord::Schema.define(version: 20160526102307) do
|
||||
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.boolean "invoicing_disabled", default: false
|
||||
|
12
lib/tasks/fablab/fix.rake
Normal file
12
lib/tasks/fablab/fix.rake
Normal file
@ -0,0 +1,12 @@
|
||||
namespace :fablab do
|
||||
namespace :fix do
|
||||
task reservations_not_existing_reservable: :environment do
|
||||
ActiveRecord::Base.logger = Logger.new(STDOUT)
|
||||
ActiveRecord::Base.connection.execute(
|
||||
'UPDATE reservations SET reservable_type = NULL, reservable_id = NULL'\
|
||||
' WHERE NOT EXISTS (SELECT 1 FROM events WHERE events.id = reservations.reservable_id)'\
|
||||
' AND reservations.reservable_type = \'Event\''
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
Loading…
x
Reference in New Issue
Block a user