1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-02-19 13:54:25 +01:00

Merge branch 'dev' into host

This commit is contained in:
Sylvain 2019-01-21 16:34:06 +01:00
commit da672ea8b6
75 changed files with 1407 additions and 1078 deletions

View File

@ -1,5 +1,5 @@
Metrics/LineLength:
Max: 125
Max: 130
Metrics/MethodLength:
Max: 30
Metrics/CyclomaticComplexity:
@ -8,6 +8,8 @@ Metrics/PerceivedComplexity:
Max: 9
Metrics/AbcSize:
Max: 45
Metrics/ClassLength:
Max: 200
Style/BracesAroundHashParameters:
EnforcedStyle: context_dependent
Style/RegexpLiteral:

View File

@ -1,10 +1,16 @@
# Changelog Fab Manager
- Removed ability to disable invoicing for an user
- Fixed a missing translation in plan form
- Fix a bug: error handling on password recovery
- Fix a bug: error handling on machine attachment upload
- Fix a bug: first day of week is ignored in statistics custom filter
- Fix a bug: rails DSB locale is invalid
- Removed ability to disable invoicing for an user
- Fix a bug: unable to delete an admin who has changed a setting
- Fix a bug: unable to create/edit a plan of 12 months or 52 weeks
- Fix a bug: Unable to search in user autocomplete fields
- Fix a bug: Invalid translation in new partner modal
- Improved user autocompletion when using multiple words
- Refactored frontend invoices translations
- Updated RailRoady 1.4.0 to 1.5.3

View File

@ -160,10 +160,14 @@ This procedure is not easy to follow so if you don't need to write some code for
- **Please note**: Your password length must be between 8 and 128 characters, otherwise db:seed will be rejected. This is configured in [config/initializers/devise.rb](config/initializers/devise.rb)
```bash
# for dev
rake db:create
rake db:migrate
ADMIN_EMAIL='youradminemail' ADMIN_PASSWORD='youradminpassword' rake db:seed
rake fablab:es_build_stats
# for tests
RAILS_ENV=test rake db:create
RAILS_ENV=test rake db:migrate
```
14. Create the pids folder used by Sidekiq. If you want to use a different location, you can configure it in `config/sidekiq.yml`
@ -252,6 +256,9 @@ environment.
rake db:migrate
ADMIN_EMAIL='youradminemail' ADMIN_PASSWORD='youradminpassword' rake db:seed
rake fablab:es_build_stats
# for tests
RAILS_ENV=test rake db:create
RAILS_ENV=test rake db:migrate
```
11. Start the application and visit `localhost:3000` on your browser to check that it works:

View File

@ -180,6 +180,12 @@
}
}
.no-user-label {
font-style: italic;
color: #5a5a5a;
}
table.closings-table {
width: 100%;
border-spacing: 0;

View File

@ -403,7 +403,7 @@
<tr ng-repeat="value in history">
<td>{{value.value}} %</td>
<td>{{value.created_at | amDateFormat:'L LT'}}</td>
<td>{{value.user.name}}</td>
<td>{{value.user.name}}<span class="no-user-label" ng-hide="value.user" translate>{{ 'invoices.deleted_user' }}</span></td>
</tr>
</tbody>
</table>

View File

@ -1,5 +1,5 @@
<div class="modal-header">
<h3 class="modal-title" translate>{{ 'new_partner' }}</h3>
<h3 class="modal-title" translate>{{ 'plan_form.new_partner' }}</h3>
</div>
<div class="modal-body m-lg">
<form name="partnerForm">

View File

@ -34,7 +34,7 @@ class API::AdminsController < API::ApiController
def destroy
@admin = User.admins.find(params[:id])
if current_user.admin? and @admin != current_user
if current_user.admin? && @admin != current_user
@admin.destroy
head :no_content
else

View File

@ -22,7 +22,6 @@ class API::AgeRangesController < API::ApiController
end
end
def update
authorize AgeRange
if @age_range.update(age_range_params)
@ -42,6 +41,7 @@ class API::AgeRangesController < API::ApiController
end
private
def set_age_range
@age_range = AgeRange.find(params[:id])
end

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
class API::ApiController < ApplicationController

View File

@ -1,6 +1,10 @@
# frozen_string_literal: true
# API Controller for resources of type AuthProvider
# AuthProvider are used to connect users through single-sign on systems
class API::AuthProvidersController < API::ApiController
before_action :set_provider, only: [:show, :update, :destroy]
before_action :set_provider, only: %i[show update destroy]
def index
@providers = policy_scope(AuthProvider)
end
@ -57,33 +61,33 @@ class API::AuthProvidersController < API::ApiController
NotificationCenter.call type: 'notify_user_auth_migration',
receiver: user,
attached_object: user
render json: {status: 'processing'}, status: :ok
render json: { status: 'processing' }, status: :ok
else
render json: {status: 'error', error: I18n.t('members.current_authentication_method_no_code')}, status: :bad_request
render json: { status: 'error', error: I18n.t('members.current_authentication_method_no_code') }, status: :bad_request
end
else
render json: {status: 'error', error: I18n.t('members.requested_account_does_not_exists')}, status: :bad_request
render json: { status: 'error', error: I18n.t('members.requested_account_does_not_exists') }, status: :bad_request
end
end
private
def set_provider
@provider = AuthProvider.find(params[:id])
end
def set_provider
@provider = AuthProvider.find(params[:id])
end
def provider_params
if params['auth_provider']['providable_type'] == DatabaseProvider.name
params.require(:auth_provider).permit(:name, :providable_type)
elsif params['auth_provider']['providable_type'] == OAuth2Provider.name
params.require(:auth_provider).permit(:name, :providable_type, providable_attributes: [
:id, :base_url, :token_endpoint, :authorization_endpoint, :logout_endpoint, :profile_url, :client_id, :client_secret,
o_auth2_mappings_attributes: [
:id, :local_model, :local_field, :api_field, :api_endpoint, :api_data_type, :_destroy,
transformation: [:type, :format, :true_value, :false_value, mapping: [:from, :to]]
]
])
end
def provider_params
if params['auth_provider']['providable_type'] == DatabaseProvider.name
params.require(:auth_provider).permit(:name, :providable_type)
elsif params['auth_provider']['providable_type'] == OAuth2Provider.name
params.require(:auth_provider)
.permit(:name, :providable_type,
providable_attributes: [:id, :base_url, :token_endpoint, :authorization_endpoint, :logout_endpoint,
:profile_url, :client_id, :client_secret,
o_auth2_mappings_attributes: [:id, :local_model, :local_field, :api_field,
:api_endpoint, :api_data_type, :_destroy,
transformation: [:type, :format, :true_value,
:false_value, mapping: %i[from to]]]])
end
end
end
end

View File

@ -1,3 +1,6 @@
# frozen_string_literal: true
# API Controller for resources of type Availability
class API::AvailabilitiesController < API::ApiController
include FablabConfiguration
@ -10,94 +13,30 @@ class API::AvailabilitiesController < API::ApiController
authorize Availability
start_date = ActiveSupport::TimeZone[params[:timezone]].parse(params[:start])
end_date = ActiveSupport::TimeZone[params[:timezone]].parse(params[:end]).end_of_day
@availabilities = Availability.includes(:machines, :tags, :trainings, :spaces).where.not(available_type: 'event')
@availabilities = Availability.includes(:machines, :tags, :trainings, :spaces)
.where.not(available_type: 'event')
.where('start_at >= ? AND end_at <= ?', start_date, end_date)
if fablab_spaces_deactivated?
@availabilities = @availabilities.where.not(available_type: 'space')
end
@availabilities = @availabilities.where.not(available_type: 'space') if fablab_spaces_deactivated?
end
def public
start_date = ActiveSupport::TimeZone[params[:timezone]].parse(params[:start])
end_date = ActiveSupport::TimeZone[params[:timezone]].parse(params[:end]).end_of_day
@reservations = Reservation.includes(:slots, user: [:profile]).references(:slots, :user)
.where('slots.start_at >= ? AND slots.end_at <= ?', start_date, end_date)
@reservations = Reservation.includes(:slots, user: [:profile])
.references(:slots, :user)
.where('slots.start_at >= ? AND slots.end_at <= ?', start_date, end_date)
# request for 1 single day
if in_same_day(start_date, end_date)
# trainings, events
@training_and_event_availabilities = Availability.includes(:tags, :trainings, :event, :slots)
.where(available_type: %w[training event])
.where('start_at >= ? AND end_at <= ?', start_date, end_date)
.where(lock: false)
# machines
@machine_availabilities = Availability.includes(:tags, :machines)
.where(available_type: 'machines')
.where('start_at >= ? AND end_at <= ?', start_date, end_date)
.where(lock: false)
@machine_slots = []
@machine_availabilities.each do |a|
a.machines.each do |machine|
next unless params[:m]&.include?(machine.id.to_s)
((a.end_at - a.start_at)/ApplicationHelper::SLOT_DURATION.minutes).to_i.times do |i|
slot = Slot.new(
start_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes,
end_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes + ApplicationHelper::SLOT_DURATION.minutes,
availability_id: a.id,
availability: a,
machine: machine,
title: machine.name
)
slot = verify_machine_is_reserved(slot, @reservations, current_user, '')
@machine_slots << slot
end
end
end
# spaces
@space_availabilities = Availability.includes(:tags, :spaces).where(available_type: 'space')
.where('start_at >= ? AND end_at <= ?', start_date, end_date)
.where(lock: false)
@space_availabilities.where(available_id: params[:s]) if params[:s]
@space_slots = []
@space_availabilities.each do |a|
space = a.spaces.first
((a.end_at - a.start_at)/ApplicationHelper::SLOT_DURATION.minutes).to_i.times do |i|
next unless (a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes) > Time.now
slot = Slot.new(
start_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes,
end_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes + ApplicationHelper::SLOT_DURATION.minutes,
availability_id: a.id,
availability: a,
space: space,
title: space.name
)
slot = verify_space_is_reserved(slot, @reservations, current_user, '')
@space_slots << slot
end
end
@availabilities = [].concat(@training_and_event_availabilities).concat(@machine_slots).concat(@space_slots)
# request for many days (week or month)
else
@availabilities = Availability.includes(:tags, :machines, :trainings, :spaces, :event, :slots)
.where('start_at >= ? AND end_at <= ?', start_date, end_date)
.where(lock: false)
@availabilities.each do |a|
if a.available_type == 'training' or a.available_type == 'event'
a = verify_training_event_is_reserved(a, @reservations, current_user)
elsif a.available_type == 'space'
a.is_reserved = is_reserved_availability(a, current_user)
end
end
end
machine_ids = params[:m] || []
@title_filter = {machine_ids: machine_ids.map(&:to_i)}
service = Availabilities::PublicAvailabilitiesService.new(current_user)
@availabilities = service.public_availabilities(
start_date,
end_date,
@reservations,
machines: machine_ids, spaces: params[:s]
)
@title_filter = { machine_ids: machine_ids.map(&:to_i) }
@availabilities = filter_availabilites(@availabilities)
end
@ -134,138 +73,22 @@ class API::AvailabilitiesController < API::ApiController
end
def machine
@user = if params[:member_id]
User.find(params[:member_id])
else
current_user
end
@current_user_role = current_user.admin? ? 'admin' : 'user'
@machine = Machine.friendly.find(params[:machine_id])
@slots = []
@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.admin?
@availabilities = @machine.availabilities.includes(:tags)
.where("end_at > ? AND available_type = 'machines'", Time.now)
.where(lock: false)
else
end_at = @visi_max_other
end_at = @visi_max_year if is_subscription_year(@user)
@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]))
.where(lock: false)
end
@availabilities.each do |a|
((a.end_at - a.start_at)/ApplicationHelper::SLOT_DURATION.minutes).to_i.times do |i|
next unless (a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes) > Time.now
slot = Slot.new(
start_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes,
end_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes + ApplicationHelper::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
service = Availabilities::AvailabilitiesService.new(current_user, other: @visi_max_other, year: @visi_max_year)
@slots = service.machines(params[:machine_id], user)
end
def trainings
@user = if params[:member_id]
User.find(params[:member_id])
else
current_user
end
@slots = []
# first, we get the already-made reservations
@reservations = @user.reservations.where("reservable_type = 'Training'")
@reservations = @reservations.where('reservable_id = :id', id: params[:training_id].to_i) if params[:training_id].is_number?
@reservations = @reservations.joins(:slots).where('slots.start_at > ?', Time.now)
# what is requested?
# 1) a single training
@availabilities = if params[:training_id].is_number? or (params[:training_id].length > 0 and params[:training_id] != 'all')
Training.friendly.find(params[:training_id]).availabilities
# 2) all trainings
else
Availability.trainings
end
# who made the request?
# 1) an admin (he can see all future availabilities)
if current_user.admin?
@availabilities = @availabilities.includes(:tags, :slots, trainings: [:machines])
.where('availabilities.start_at > ?', Time.now)
.where(lock: false)
# 2) an user (he cannot see availabilities further than 1 (or 3) months)
else
end_at = @visi_max_year
end_at = @visi_max_year if can_show_slot_plus_three_months(@user)
@availabilities = @availabilities.includes(:tags, :slots, :availability_tags, trainings: [:machines])
.where('availabilities.start_at > ? AND availabilities.start_at < ?', Time.now, end_at)
.where('availability_tags.tag_id' => @user.tag_ids.concat([nil]))
.where(lock: false)
end
# finally, we merge the availabilities with the reservations
@availabilities.each do |a|
a = verify_training_event_is_reserved(a, @reservations, @user)
end
service = Availabilities::AvailabilitiesService.new(current_user, other: @visi_max_other, year: @visi_max_year)
@availabilities = service.trainings(params[:training_id], user)
end
def spaces
@user = if params[:member_id]
User.find(params[:member_id])
else
current_user
end
@current_user_role = current_user.admin? ? 'admin' : 'user'
@space = Space.friendly.find(params[:space_id])
@slots = []
@reservations = Reservation.where('reservable_type = ? and reservable_id = ?', @space.class.to_s, @space.id)
.includes(:slots, user: [:profile]).references(:slots, :user)
.where('slots.start_at > ?', Time.now)
if current_user.admin?
@availabilities = @space.availabilities.includes(:tags)
.where("end_at > ? AND available_type = 'space'", Time.now)
.where(lock: false)
else
end_at = @visi_max_other
end_at = @visi_max_year if is_subscription_year(@user)
@availabilities = @space.availabilities.includes(:tags)
.where("end_at > ? AND end_at < ? AND available_type = 'space'", Time.now, end_at)
.where('availability_tags.tag_id' => @user.tag_ids.concat([nil]))
.where(lock: false)
end
@availabilities.each do |a|
((a.end_at - a.start_at)/ApplicationHelper::SLOT_DURATION.minutes).to_i.times do |i|
next unless (a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes) > Time.now
slot = Slot.new(
start_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes,
end_at: a.start_at + (i*ApplicationHelper::SLOT_DURATION).minutes + ApplicationHelper::SLOT_DURATION.minutes,
availability_id: a.id,
availability: a,
space: @space,
title: ''
)
slot = verify_space_is_reserved(slot, @reservations, @user, @current_user_role)
@slots << slot
end
end
@slots.each do |s|
if s.is_complete? and not s.is_reserved
s.title = t('availabilities.not_available')
end
end
service = Availabilities::AvailabilitiesService.new(current_user, other: @visi_max_other, year: @visi_max_year)
@slots = service.spaces(params[:space_id], user)
end
def reservations
@ -281,7 +104,7 @@ class API::AvailabilitiesController < API::ApiController
if export.nil? || !FileTest.exist?(export.file)
@export = Export.new(category: 'availabilities', export_type: 'index', user: current_user)
if @export.save
render json: {export_id: @export.id}, status: :ok
render json: { export_id: @export.id }, status: :ok
else
render json: @export.errors, status: :unprocessable_entity
end
@ -303,6 +126,14 @@ class API::AvailabilitiesController < API::ApiController
private
def user
if params[:member_id]
User.find(params[:member_id])
else
current_user
end
end
def set_availability
@availability = Availability.find(params[:id])
end
@ -317,104 +148,6 @@ class API::AvailabilitiesController < API::ApiController
params.require(:lock)
end
def is_reserved_availability(availability, user)
if user
reserved_slots = []
availability.slots.each do |s|
if s.canceled_at.nil?
reserved_slots << s
end
end
reserved_slots.map(&:reservations).flatten.map(&:user_id).include? user.id
else
false
end
end
def is_reserved(start_at, reservations)
is_reserved = false
reservations.each do |r|
r.slots.each do |s|
is_reserved = true if s.start_at == start_at
end
end
is_reserved
end
def verify_machine_is_reserved(slot, reservations, user, user_role)
show_name = (user_role == 'admin' or Setting.find_by(name: 'display_name_enable').value == 'true')
reservations.each do |r|
r.slots.each do |s|
next unless slot.machine.id == r.reservable_id
if s.start_at == slot.start_at and s.canceled_at == nil
slot.id = s.id
slot.is_reserved = true
slot.title = "#{slot.machine.name} - #{show_name ? r.user.profile.full_name : t('availabilities.not_available')}"
slot.can_modify = true if user_role === 'admin'
slot.reservations.push r
end
if s.start_at == slot.start_at and r.user == user and s.canceled_at == nil
slot.title = "#{slot.machine.name} - #{t('availabilities.i_ve_reserved')}"
slot.can_modify = true
slot.is_reserved_by_current_user = true
end
end
end
slot
end
def verify_space_is_reserved(slot, reservations, user, user_role)
reservations.each do |r|
r.slots.each do |s|
next unless slot.space.id == r.reservable_id
if s.start_at == slot.start_at and s.canceled_at == nil
slot.can_modify = true if user_role === 'admin'
slot.reservations.push r
end
if s.start_at == slot.start_at and r.user == user and s.canceled_at == nil
slot.id = s.id
slot.title = t('availabilities.i_ve_reserved')
slot.can_modify = true
slot.is_reserved = true
end
end
end
slot
end
def verify_training_event_is_reserved(availability, reservations, user)
reservations.each do |r|
r.slots.each do |s|
next unless (
(availability.available_type == 'training' && availability.trainings.first.id == r.reservable_id) ||
(availability.available_type == 'event' && availability.event.id == r.reservable_id)
) && s.start_at == availability.start_at && s.canceled_at == nil
availability.slot_id = s.id
if r.user == user
availability.is_reserved = true
availability.can_modify = true
end
end
end
availability
end
def can_show_slot_plus_three_months(user)
# member must have validated at least 1 training and must have a valid yearly subscription.
user.trainings.size > 0 and is_subscription_year(user)
end
def is_subscription_year(user)
user.subscription and user.subscription.plan.interval == 'year' and user.subscription.expired_at >= Time.now
end
def in_same_day(start_date, end_date)
(end_date.to_date - start_date.to_date).to_i == 1
end
def filter_availabilites(availabilities)
availabilities_filtered = []
availabilities.to_ary.each do |a|
@ -422,35 +155,33 @@ class API::AvailabilitiesController < API::ApiController
if !a.try(:available_type)
availabilities_filtered << a
else
# training
if params[:t] and a.available_type == 'training'
if params[:t].include?(a.trainings.first.id.to_s)
availabilities_filtered << a
end
end
# space
if params[:s] and a.available_type == 'space'
if params[:s].include?(a.spaces.first.id.to_s)
availabilities_filtered << a
end
end
# machines
if params[:m] and a.available_type == 'machines'
if (params[:m].map(&:to_i) & a.machine_ids).any?
availabilities_filtered << a
end
end
# event
if params[:evt] and params[:evt] == 'true' and a.available_type == 'event'
availabilities_filtered << a
end
end
end
availabilities_filtered.delete_if do |a|
if params[:dispo] == 'false'
a.is_reserved or (a.try(:completed?) and a.completed?)
availabilities_filtered << a if filter_training?(a)
availabilities_filtered << a if filter_space?(a)
availabilities_filtered << a if filter_machine?(a)
availabilities_filtered << a if filter_event?(a)
end
end
availabilities_filtered.delete_if(&method(:remove_completed?))
end
def filter_training?(availability)
params[:t] && availability.available_type == 'training' && params[:t].include?(availability.trainings.first.id.to_s)
end
def filter_space?(availability)
params[:s] && availability.available_type == 'space' && params[:s].include?(availability.spaces.first.id.to_s)
end
def filter_machine?(availability)
params[:m] && availability.available_type == 'machines' && (params[:m].map(&:to_i) & availability.machine_ids).any?
end
def filter_event?(availability)
params[:evt] && params[:evt] == 'true' && availability.available_type == 'event'
end
def remove_completed?(availability)
params[:dispo] == 'false' && (availability.is_reserved || (availability.try(:completed?) && availability.completed?))
end
def define_max_visibility

View File

@ -1,13 +1,16 @@
# frozen_string_literal: true
# API Controller for resources of type Category
# Categories are used to classify Events
class API::CategoriesController < API::ApiController
before_action :authenticate_user!, except: [:index]
before_action :set_category, only: [:show, :update, :destroy]
before_action :set_category, only: %i[show update destroy]
def index
@categories = Category.all
end
def show
end
def show; end
def create
authorize Category
@ -39,11 +42,12 @@ class API::CategoriesController < API::ApiController
end
private
def set_category
@category = Category.find(params[:id])
end
def category_params
params.require(:category).permit(:name)
end
def set_category
@category = Category.find(params[:id])
end
def category_params
params.require(:category).permit(:name)
end
end

View File

@ -1,13 +1,16 @@
# frozen_string_literal: true
# API Controller for resources of type Component
# Components are used in Projects
class API::ComponentsController < API::ApiController
before_action :authenticate_user!, except: [:index, :show]
before_action :set_component, only: [:show, :update, :destroy]
before_action :authenticate_user!, except: %i[index show]
before_action :set_component, only: %i[show update destroy]
def index
@components = Component.all
end
def show
end
def show; end
def create
authorize Component
@ -35,11 +38,12 @@ class API::ComponentsController < API::ApiController
end
private
def set_component
@component = Component.find(params[:id])
end
def component_params
params.require(:component).permit(:name)
end
def set_component
@component = Component.find(params[:id])
end
def component_params
params.require(:component).permit(:name)
end
end

View File

@ -1,13 +1,16 @@
# frozen_string_literal: true
# API Controller for resources of type Coupon
# Coupons are used in payments
class API::CouponsController < API::ApiController
before_action :authenticate_user!
before_action :set_coupon, only: [:show, :update, :destroy]
before_action :set_coupon, only: %i[show update destroy]
def index
@coupons = Coupon.all
end
def show
end
def show; end
def create
authorize Coupon
@ -22,18 +25,18 @@ class API::CouponsController < API::ApiController
def validate
@coupon = Coupon.find_by(code: params[:code])
if @coupon.nil?
render json: {status: 'rejected'}, status: :not_found
render json: { status: 'rejected' }, status: :not_found
else
if !current_user.admin?
_user_id = current_user.id
else
_user_id = params[:user_id]
end
_user_id = if !current_user.admin?
current_user.id
else
params[:user_id]
end
amount = params[:amount].to_f * 100.0
status = @coupon.status(_user_id, amount)
if status != 'active'
render json: {status: status}, status: :unprocessable_entity
render json: { status: status }, status: :unprocessable_entity
else
render :validate, status: :ok, location: @coupon
end
@ -62,18 +65,17 @@ class API::CouponsController < API::ApiController
authorize Coupon
@coupon = Coupon.find_by(code: params[:coupon_code])
if @coupon.nil?
render json: {error: "no coupon with code #{params[:coupon_code]}"}, status: :not_found
else
if @coupon.send_to(params[:user_id])
render :show, status: :ok, location: @coupon
else
render json: @coupon.errors, status: :unprocessable_entity
end
end
if @coupon.nil?
render json: { error: "no coupon with code #{params[:coupon_code]}" }, status: :not_found
elsif @coupon.send_to(params[:user_id])
render :show, status: :ok, location: @coupon
else
render json: @coupon.errors, status: :unprocessable_entity
end
end
private
def set_coupon
@coupon = Coupon.find(params[:id])
end
@ -85,7 +87,8 @@ class API::CouponsController < API::ApiController
@parameters = params
@parameters[:coupon][:amount_off] = @parameters[:coupon][:amount_off].to_f * 100.0 if @parameters[:coupon][:amount_off]
@parameters = @parameters.require(:coupon).permit(:name, :code, :percent_off, :amount_off, :validity_per_user, :valid_until, :max_usages, :active)
@parameters = @parameters.require(:coupon).permit(:name, :code, :percent_off, :amount_off, :validity_per_user, :valid_until,
:max_usages, :active)
end
end

View File

@ -1,14 +1,18 @@
# frozen_string_literal: true
# API Controller for resources of type Credit
# Credits are used to give free reservations to users
class API::CreditsController < API::ApiController
before_action :authenticate_user!
before_action :set_credit, only: [:show, :update, :destroy]
before_action :set_credit, only: %i[show update destroy]
def index
authorize Credit
if params
@credits = Credit.includes(:creditable).where(params.permit(:creditable_type))
else
@credits = Credit.includes(:creditable).all
end
@credits = if params
Credit.includes(:creditable).where(params.permit(:creditable_type))
else
Credit.includes(:creditable).all
end
end
def create
@ -37,11 +41,12 @@ class API::CreditsController < API::ApiController
end
private
def set_credit
@credit = Credit.find(params[:id])
end
def credit_params
params.require(:credit).permit!
end
def set_credit
@credit = Credit.find(params[:id])
end
def credit_params
params.require(:credit).permit!
end
end

View File

@ -1,10 +1,10 @@
class API::CustomAssetsController < API::ApiController
before_action :authenticate_user!, only: [:index, :update, :create, :destroy]
before_action :set_custom_asset, only: [:show, :update, :destroy]
# frozen_string_literal: true
def index
#TODO GET /api/custom_assets/
end
# API Controller for resources of type CustomAsset
# CustomAssets are used in settings
class API::CustomAssetsController < API::ApiController
before_action :authenticate_user!, only: %i[index update create destroy]
before_action :set_custom_asset, only: %i[show update destroy]
# PUT /api/custom_assets/1/
def update
@ -28,14 +28,10 @@ class API::CustomAssetsController < API::ApiController
end
# GET /api/custom_assets/1/
def show
end
def destroy
#TODO DELETE /api/custom_assets/1/
end
def show; end
private
def set_custom_asset
@custom_asset = CustomAsset.find_by(name: params[:id])
end
@ -45,4 +41,4 @@ class API::CustomAssetsController < API::ApiController
params.required(:custom_asset).permit(:name, custom_asset_file_attributes: [:attachment])
end
end
end

View File

@ -1,13 +1,16 @@
# frozen_string_literal: true
# API Controller for resources of type EventTheme
# EventTheme are used to classify Events
class API::EventThemesController < API::ApiController
before_action :authenticate_user!, except: [:index]
before_action :set_event_theme, only: [:show, :update, :destroy]
before_action :set_event_theme, only: %i[show update destroy]
def index
@event_themes = EventTheme.all
end
def show
end
def show; end
def create
authorize EventTheme
@ -39,6 +42,7 @@ class API::EventThemesController < API::ApiController
end
private
def set_event_theme
@event_theme = EventTheme.find(params[:id])
end

View File

@ -1,5 +1,8 @@
# frozen_string_literal: true
# API Controller for resources of type Event
class API::EventsController < API::ApiController
before_action :set_event, only: [:show, :update, :destroy]
before_action :set_event, only: %i[show update destroy]
def index
@events = policy_scope(Event)
@ -11,17 +14,17 @@ class API::EventsController < API::ApiController
@events = @events.joins(:event_themes).where('event_themes.id = :theme', theme: params[:theme_id]) if params[:theme_id]
@events = @events.where('age_range_id = :age_range', age_range: params[:age_range_id]) if params[:age_range_id]
if current_user and current_user.admin?
case params[:scope]
when 'future'
@events = @events.where('availabilities.start_at >= ?', Time.now).order('availabilities.start_at DESC')
when 'future_asc'
@events = @events.where('availabilities.start_at >= ?', Time.now).order('availabilities.start_at ASC')
when 'passed'
@events = @events.where('availabilities.start_at < ?', Time.now).order('availabilities.start_at DESC')
else
@events = @events.order('availabilities.start_at DESC')
end
if current_user&.admin?
@events = case params[:scope]
when 'future'
@events.where('availabilities.start_at >= ?', Time.now).order('availabilities.start_at DESC')
when 'future_asc'
@events.where('availabilities.start_at >= ?', Time.now).order('availabilities.start_at ASC')
when 'passed'
@events.where('availabilities.start_at < ?', Time.now).order('availabilities.start_at DESC')
else
@events.order('availabilities.start_at DESC')
end
end
# paginate
@ -38,8 +41,7 @@ class API::EventsController < API::ApiController
.limit(limit)
end
def show
end
def show; end
def create
authorize Event
@ -61,9 +63,10 @@ class API::EventsController < API::ApiController
end
rescue ActiveRecord::RecordNotDestroyed => e
if e.record.class.name == 'EventPriceCategory'
render json: {error: ["#{e.record.price_category.name}: #{t('events.error_deleting_reserved_price')}"]}, status: :unprocessable_entity
render json: { error: ["#{e.record.price_category.name}: #{t('events.error_deleting_reserved_price')}"] },
status: :unprocessable_entity
else
render json: {error: [t('events.other_error')]}, status: :unprocessable_entity
render json: { error: [t('events.other_error')] }, status: :unprocessable_entity
end
end
@ -79,48 +82,22 @@ class API::EventsController < API::ApiController
end
private
# Use callbacks to share common setup or constraints between actions.
def set_event
@event = Event.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def event_params
# handle general properties
event_preparams = params.required(:event).permit(:title, :description, :start_date, :start_time, :end_date, :end_time,
:amount, :nb_total_places, :availability_id,
:all_day, :recurrence, :recurrence_end_at, :category_id, :event_theme_ids,
:age_range_id, event_theme_ids: [],
event_image_attributes: [:attachment],
event_files_attributes: [:id, :attachment, :_destroy],
event_price_categories_attributes: [:id, :price_category_id, :amount, :_destroy]
)
# handle dates & times (whole-day events or not, maybe during many days)
start_date = Time.zone.parse(event_preparams[:start_date])
end_date = Time.zone.parse(event_preparams[:end_date])
start_time = Time.parse(event_preparams[:start_time]) if event_preparams[:start_time]
end_time = Time.parse(event_preparams[:end_time]) if event_preparams[:end_time]
if event_preparams[:all_day] == 'true'
start_at = DateTime.new(start_date.year, start_date.month, start_date.day, 0, 0, 0, start_date.zone)
end_at = DateTime.new(end_date.year, end_date.month, end_date.day, 23, 59, 59, end_date.zone)
else
start_at = DateTime.new(start_date.year, start_date.month, start_date.day, start_time.hour, start_time.min, start_time.sec, start_date.zone)
end_at = DateTime.new(end_date.year, end_date.month, end_date.day, end_time.hour, end_time.min, end_time.sec, end_date.zone)
end
event_preparams.merge!(availability_attributes: {id: event_preparams[:availability_id], start_at: start_at, end_at: end_at, available_type: 'event'})
.except!(:start_date, :end_date, :start_time, :end_time, :all_day)
# convert main price to centimes
event_preparams.merge!(amount: (event_preparams[:amount].to_f * 100 if event_preparams[:amount].present?))
# delete non-complete "other" prices and convert them to centimes
unless event_preparams[:event_price_categories_attributes].nil?
event_preparams[:event_price_categories_attributes].delete_if { |price_cat| price_cat[:price_category_id].empty? or price_cat[:amount].empty? }
event_preparams[:event_price_categories_attributes].each do |price_cat|
price_cat[:amount] = price_cat[:amount].to_f * 100
end
end
# return the resulting params object
event_preparams
end
# Use callbacks to share common setup or constraints between actions.
def set_event
@event = Event.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def event_params
# handle general properties
event_preparams = params.required(:event).permit(:title, :description, :start_date, :start_time, :end_date, :end_time,
:amount, :nb_total_places, :availability_id, :all_day, :recurrence,
:recurrence_end_at, :category_id, :event_theme_ids, :age_range_id,
event_theme_ids: [],
event_image_attributes: [:attachment],
event_files_attributes: %i[id attachment_destroy],
event_price_categories_attributes: %i[id price_category_id amount _destroy])
EventService.process_params(event_preparams)
end
end

View File

@ -1,3 +1,7 @@
# frozen_string_literal: true
# API Controller for resources of type Export
# Export are used to download data tables in offline files
class API::ExportsController < API::ApiController
before_action :authenticate_user!
before_action :set_export, only: [:download]
@ -6,7 +10,9 @@ class API::ExportsController < API::ApiController
authorize @export
if FileTest.exist?(@export.file)
send_file File.join(Rails.root, @export.file), :type => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', :disposition => 'attachment'
send_file File.join(Rails.root, @export.file),
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
disposition: 'attachment'
else
render text: I18n.t('errors.messages.export_not_found'), status: :not_found
end
@ -15,38 +21,39 @@ class API::ExportsController < API::ApiController
def status
authorize Export
export = Export.where({category: params[:category], export_type: params[:type], query: params[:query], key: params[:key]})
export = Export.where(category: params[:category], export_type: params[:type], query: params[:query], key: params[:key])
if params[:category] === 'users'
if params[:category] == 'users'
case params[:type]
when 'subscriptions'
export = export.where('created_at > ?', Subscription.maximum('updated_at'))
when 'reservations'
export = export.where('created_at > ?', Reservation.maximum('updated_at'))
when 'members'
export = export.where('created_at > ?', User.with_role(:member).maximum('updated_at'))
else
raise ArgumentError, "Unknown export users/#{params[:type]}"
when 'subscriptions'
export = export.where('created_at > ?', Subscription.maximum('updated_at'))
when 'reservations'
export = export.where('created_at > ?', Reservation.maximum('updated_at'))
when 'members'
export = export.where('created_at > ?', User.with_role(:member).maximum('updated_at'))
else
raise ArgumentError, "Unknown export users/#{params[:type]}"
end
elsif params[:category] === 'availabilities'
elsif params[:category] == 'availabilities'
case params[:type]
when 'index'
export = export.where('created_at > ?', Availability.maximum('updated_at'))
else
raise ArgumentError, "Unknown type availabilities/#{params[:type]}"
when 'index'
export = export.where('created_at > ?', Availability.maximum('updated_at'))
else
raise ArgumentError, "Unknown type availabilities/#{params[:type]}"
end
end
export = export.last
if export.nil? || !FileTest.exist?(export.file)
render json: {exists: false, id: nil}, status: :ok
render json: { exists: false, id: nil }, status: :ok
else
render json: {exists: true, id: export.id}, status: :ok
render json: { exists: true, id: export.id }, status: :ok
end
end
private
def set_export
@export = Export.find(params[:id])
end
end
end

View File

@ -1,16 +1,19 @@
# frozen_string_literal: true
# API Controller to wrap social networks public feeds
class API::FeedsController < API::ApiController
respond_to :json
def twitter_timelines
if params
limit = params[:limit]
else
limit = 3
end
limit = if params
params[:limit]
else
3
end
from_account = Setting.find_by(name: 'twitter_name').try(:value) || ENV['TWITTER_NAME']
begin
@tweet_news = Feed.twitter.user_timeline(from_account, {count: limit})
@tweet_news = Feed.twitter.user_timeline(from_account, count: limit)
rescue Twitter::Error::BadRequest => e
STDERR.puts "[WARNING] Unable to retrieve the twitter feed, please check your ENV configuration. Details: #{e.message}"
render status: :no_content

View File

@ -1,12 +1,16 @@
# frozen_string_literal: true
# API Controller for resources of type Group
# Groups are used for categorizing Users
class API::GroupsController < API::ApiController
before_action :authenticate_user!, except: :index
def index
if current_user and current_user.admin?
@groups = Group.all
else
@groups = Group.where.not(slug: 'admins')
end
@groups = if current_user&.admin?
Group.all
else
Group.where.not(slug: 'admins')
end
end
@ -39,7 +43,7 @@ class API::GroupsController < API::ApiController
private
def group_params
params.require(:group).permit(:name, :disabled)
end
def group_params
params.require(:group).permit(:name, :disabled)
end
end

View File

@ -1,13 +1,16 @@
# frozen_string_literal: true
# API Controller for resources of type Licence
# Licenses are used in Projects
class API::LicencesController < API::ApiController
before_action :authenticate_user!, except: [:index, :show]
before_action :set_licence, only: [:show, :update, :destroy]
before_action :authenticate_user!, except: %i[index show]
before_action :set_licence, only: %i[show update destroy]
def index
@licences = Licence.all
end
def show
end
def show; end
def create
authorize Licence
@ -35,11 +38,12 @@ class API::LicencesController < API::ApiController
end
private
def set_licence
@licence = Licence.find(params[:id])
end
def licence_params
params.require(:licence).permit(:name, :description)
end
def set_licence
@licence = Licence.find(params[:id])
end
def licence_params
params.require(:licence).permit(:name, :description)
end
end

View File

@ -1,15 +1,18 @@
# frozen_string_literal: true
# API Controller for resources of type Machine
class API::MachinesController < API::ApiController
before_action :authenticate_user!, except: [:index, :show]
before_action :set_machine, only: [:update, :destroy]
before_action :authenticate_user!, except: %i[index show]
before_action :set_machine, only: %i[update destroy]
respond_to :json
def index
sort_by = Setting.find_by(name: 'machines_sort_by').value || 'default'
if sort_by === 'default'
@machines = Machine.includes(:machine_image, :plans)
else
@machines = Machine.includes(:machine_image, :plans).order(sort_by)
end
@machines = if sort_by == 'default'
Machine.includes(:machine_image, :plans)
else
Machine.includes(:machine_image, :plans).order(sort_by)
end
end
def show
@ -42,22 +45,14 @@ class API::MachinesController < API::ApiController
end
private
def set_machine
@machine = Machine.find(params[:id])
end
def machine_params
params.require(:machine).permit(:name, :description, :spec, :disabled, :plan_ids, plan_ids: [], machine_image_attributes: [:attachment],
machine_files_attributes: [:id, :attachment, :_destroy])
end
def set_machine
@machine = Machine.find(params[:id])
end
def is_reserved(start_at, reservations)
is_reserved = false
reservations.each do |r|
r.slots.each do |s|
is_reserved = true if s.start_at == start_at
end
end
is_reserved
end
def machine_params
params.require(:machine).permit(:name, :description, :spec, :disabled, :plan_ids,
plan_ids: [], machine_image_attributes: [:attachment],
machine_files_attributes: %i[id attachment _destroy])
end
end

View File

@ -1,15 +1,16 @@
# frozen_string_literal: true
# API Controller for resources of type User with role 'member'
class API::MembersController < API::ApiController
before_action :authenticate_user!, except: [:last_subscribed]
before_action :set_member, only: [:update, :destroy, :merge]
before_action :set_member, only: %i[update destroy merge]
respond_to :json
def index
@requested_attributes = params[:requested_attributes]
@query = policy_scope(User)
unless params[:page].nil? and params[:size].nil?
@query = @query.page(params[:page].to_i).per(params[:size].to_i)
end
@query = @query.page(params[:page].to_i).per(params[:size].to_i) unless params[:page].nil? && params[:size].nil?
# remove unmerged profiles from list
@members = @query.to_a
@ -17,7 +18,11 @@ class API::MembersController < API::ApiController
end
def last_subscribed
@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])
@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
@ -34,27 +39,11 @@ class API::MembersController < API::ApiController
def create
authorize User
if !user_params[:password] and !user_params[:password_confirmation]
generated_password = Devise.friendly_token.first(8)
@member = User.new(user_params.merge(password: generated_password).permit!)
else
@member = User.new(user_params.permit!)
end
@member = User.new(user_params.permit!)
members_service = Members::MembersService.new(@member)
# if the user is created by an admin and the authentication is made through an SSO, generate a migration token
if current_user.admin? and AuthProvider.active.providable_type != DatabaseProvider.name
@member.generate_auth_migration_token
end
if @member.save
@member.generate_subscription_invoice
@member.send_confirmation_instructions
if !user_params[:password] and !user_params[:password_confirmation]
UsersMailer.delay.notify_user_account_created(@member, generated_password)
else
UsersMailer.delay.notify_user_account_created(@member, user_params[:password])
end
if members_service.create(current_user, user_params)
render :show, status: :created, location: member_path(@member)
else
render json: @member.errors, status: :unprocessable_entity
@ -63,21 +52,14 @@ class API::MembersController < API::ApiController
def update
authorize @member
members_service = MembersService.new(@member)
members_service = Members::MembersService.new(@member)
if user_params[:group_id] && @member.group_id != user_params[:group_id].to_i && !@member.subscribed_plan.nil?
# here a group change is requested but unprocessable, handle the exception
@member.errors[:group_id] = t('members.unable_to_change_the_group_while_a_subscription_is_running')
render json: @member.errors, status: :unprocessable_entity
if members_service.update(user_params)
# Update password without logging out
sign_in(@member, bypass: true) unless current_user.id != params[:id].to_i
render :show, status: :ok, location: member_path(@member)
else
# otherwise, run the user update
if members_service.update(user_params)
# Update password without logging out
sign_in(@member, bypass: true) unless current_user.id != params[:id].to_i
render :show, status: :ok, location: member_path(@member)
else
render json: @member.errors, status: :unprocessable_entity
end
render json: @member.errors, status: :unprocessable_entity
end
end
@ -92,9 +74,11 @@ class API::MembersController < API::ApiController
def export_subscriptions
authorize :export
export = Export.where(category:'users', export_type: 'subscriptions').where('created_at > ?', Subscription.maximum('updated_at')).last
export = Export.where(category: 'users', export_type: 'subscriptions')
.where('created_at > ?', Subscription.maximum('updated_at'))
.last
if export.nil? || !FileTest.exist?(export.file)
@export = Export.new(category:'users', export_type: 'subscriptions', user: current_user)
@export = Export.new(category: 'users', export_type: 'subscriptions', user: current_user)
if @export.save
render json: { export_id: @export.id }, status: :ok
else
@ -111,7 +95,7 @@ class API::MembersController < API::ApiController
def export_reservations
authorize :export
export = Export.where(category:'users', export_type: 'reservations')
export = Export.where(category: 'users', export_type: 'reservations')
.where('created_at > ?', Reservation.maximum('updated_at'))
.last
if export.nil? || !FileTest.exist?(export.file)
@ -131,11 +115,11 @@ class API::MembersController < API::ApiController
def export_members
authorize :export
export = Export.where(category:'users', export_type: 'members')
export = Export.where(category: 'users', export_type: 'members')
.where('created_at > ?', User.with_role(:member).maximum('updated_at'))
.last
if export.nil? || !FileTest.exist?(export.file)
@export = Export.new(category:'users', export_type: 'members', user: current_user)
@export = Export.new(category: 'users', export_type: 'members', user: current_user)
if @export.save
render json: { export_id: @export.id }, status: :ok
else
@ -148,16 +132,15 @@ class API::MembersController < API::ApiController
end
end
# the user is querying to be mapped to his already existing account
def merge
authorize @member
# here the user query to be mapped to his already existing account
token = params.require(:user).permit(:auth_token)[:auth_token]
token = token_param
@account = User.find_by(auth_token: token)
if @account
members_service = MembersService.new(@account)
members_service = Members::MembersService.new(@account)
begin
if members_service.merge_from_sso(@member)
@member = @account
@ -168,7 +151,8 @@ class API::MembersController < API::ApiController
render json: @member.errors, status: :unprocessable_entity
end
rescue DuplicateIndexError => error
render json: { error: t('members.please_input_the_authentication_code_sent_to_the_address', EMAIL: error.message) }, status: :unprocessable_entity
render json: { error: t('members.please_input_the_authentication_code_sent_to_the_address', EMAIL: error.message) },
status: :unprocessable_entity
end
else
render json: { error: t('members.your_authentication_code_is_not_valid') }, status: :unprocessable_entity
@ -178,67 +162,17 @@ class API::MembersController < API::ApiController
def list
authorize User
p = params.require(:query).permit(:search, :order_by, :page, :size)
render json: { error: 'page must be an integer' }, status: :unprocessable_entity and return unless query_params[:page].is_a? Integer
render json: { error: 'size must be an integer' }, status: :unprocessable_entity and return unless query_params[:size].is_a? Integer
render json: {error: 'page must be an integer'}, status: :unprocessable_entity unless p[:page].is_a? Integer
render json: {error: 'size must be an integer'}, status: :unprocessable_entity unless p[:size].is_a? Integer
direction = (p[:order_by][0] == '-' ? 'DESC' : 'ASC')
order_key = (p[:order_by][0] == '-' ? p[:order_by][1, p[:order_by].size] : p[:order_by])
order_key = case order_key
when 'last_name'
'profiles.last_name'
when 'first_name'
'profiles.first_name'
when 'email'
'users.email'
when 'phone'
'profiles.phone'
when 'group'
'groups.name'
when 'plan'
'plans.base_name'
else
'users.id'
end
@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}")
.page(p[:page])
.per(p[:size])
# ILIKE => PostgreSQL case-insensitive LIKE
@query = @query.where('profiles.first_name ILIKE :search OR profiles.last_name ILIKE :search OR profiles.phone ILIKE :search OR email ILIKE :search OR groups.name ILIKE :search OR plans.base_name ILIKE :search', search: "%#{p[:search]}%") if p[:search].size > 0
@members = @query.to_a
query = Members::ListService.list(query_params)
@max_members = query.except(:offset, :limit, :order).count
@members = query.to_a
end
def search
@members = User.includes(:profile)
.joins(:profile, :roles, 'LEFT JOIN "subscriptions" ON "subscriptions"."user_id" = "users"."id"')
.where("users.is_active = 'true' AND roles.name = 'member'")
.where("lower(f_unaccent(profiles.first_name)) ~ regexp_replace(:search, E'\\\\s+', '|') OR lower(f_unaccent(profiles.last_name)) ~ regexp_replace(:search, E'\\\\s+', '|')", search: params[:query].downcase)
if current_user.member?
# non-admin can only retrieve users with "public profiles"
@members = @members.where("users.is_allow_contact = 'true'")
else
# only admins have the ability to filter by subscription
if params[:subscription] === 'true'
@members = @members.where('subscriptions.id IS NOT NULL AND subscriptions.expired_at >= :now', now: Date.today.to_s)
elsif params[:subscription] === 'false'
@members = @members.where('subscriptions.id IS NULL OR subscriptions.expired_at < :now', now: Date.today.to_s)
end
end
@members = @members.to_a
@members = Members::ListService.search(current_user, params[:query], params[:subscription])
end
def mapping
@ -281,4 +215,12 @@ class API::MembersController < API::ApiController
end
end
def token_param
params.require(:user).permit(:auth_token)[:auth_token]
end
def query_params
params.require(:query).permit(:search, :order_by, :page, :size)
end
end

View File

@ -1,3 +1,7 @@
# frozen_string_literal: true
# API Controller for resources of type Notification
# Notifications are scoped by user
class API::NotificationsController < API::ApiController
include NotifyWith::NotificationsApi
before_action :authenticate_user!
@ -12,8 +16,8 @@ class API::NotificationsController < API::ApiController
break unless delete_obsoletes(@notifications)
end
@totals = {
total: current_user.notifications.count,
unread: current_user.notifications.where(is_read: false).count
total: current_user.notifications.count,
unread: current_user.notifications.where(is_read: false).count
}
render :index
end
@ -25,17 +29,19 @@ class API::NotificationsController < API::ApiController
break unless delete_obsoletes(@notifications)
end
@totals = {
total: current_user.notifications.count,
unread: current_user.notifications.where(is_read: false).count
total: current_user.notifications.count,
unread: current_user.notifications.where(is_read: false).count
}
render :index
end
def polling
@notifications = current_user.notifications.where('is_read = false AND created_at >= :date', date: params[:last_poll]).order('created_at DESC')
@notifications = current_user.notifications
.where('is_read = false AND created_at >= :date', date: params[:last_poll])
.order('created_at DESC')
@totals = {
total: current_user.notifications.count,
unread: current_user.notifications.where(is_read: false).count
total: current_user.notifications.count,
unread: current_user.notifications.where(is_read: false).count
}
render :index
end
@ -45,7 +51,7 @@ class API::NotificationsController < API::ApiController
def delete_obsoletes(notifications)
cleaned = false
notifications.each do |n|
if !Module.const_get(n.attached_object_type) or !n.attached_object
if !Module.const_get(n.attached_object_type) || !n.attached_object
n.destroy!
cleaned = true
end

View File

@ -1,3 +1,7 @@
# frozen_string_literal: true
# API Controller for resources of type OpenAPI::Client
# OpenAPI::Clients are used to allow access to the public API
class API::OpenAPIClientsController < API::ApiController
before_action :authenticate_user!
@ -5,7 +9,7 @@ class API::OpenAPIClientsController < API::ApiController
authorize OpenAPI::Client
@clients = OpenAPI::Client.order(:created_at)
end
# add authorization
def create
@client = OpenAPI::Client.new(client_params)
authorize @client
@ -40,7 +44,8 @@ class API::OpenAPIClientsController < API::ApiController
end
private
def client_params
params.require(:open_api_client).permit(:name)
end
def client_params
params.require(:open_api_client).permit(:name)
end
end

View File

@ -1,11 +1,13 @@
# frozen_string_literal: true
# API Controller for resources of type Openlab::Projects
# Openlab::Projects are Projects shared between different instances
class API::OpenlabProjectsController < API::ApiController
PROJECTS = Openlab::Projects.new
def index
begin
render json: PROJECTS.search(params[:q], page: params[:page], per_page: params[:per_page]).response.body
rescue StandardError
render json: { errors: ['service unavailable'] }
end
render json: PROJECTS.search(params[:q], page: params[:page], per_page: params[:per_page]).response.body
rescue StandardError
render json: { errors: ['service unavailable'] }
end
end

View File

@ -1,4 +1,9 @@
class API::PlansController < API::ApiController
# frozen_string_literal: true
# API Controller for resources of type Plan and PartnerPlan.
# Plan are used to define subscription's characteristics.
# PartnerPlan is a special kind of plan which send notifications to an external user
class API::PlansController < API::ApiController
before_action :authenticate_user!, except: [:index]
def index
@ -13,48 +18,19 @@
def create
authorize Plan
begin
if plan_params[:type] and plan_params[:type] == 'PartnerPlan'
partner = User.find(params[:plan][:partner_id])
unless %w[PartnerPlan Plan].include? plan_params[:type]
render json: { error: 'unhandled plan type' }, status: :unprocessable_entity and return
end
if plan_params[:group_id] == 'all'
plans = PartnerPlan.create_for_all_groups(plan_params)
if plans
plans.each { |plan| partner.add_role :partner, plan }
render json: { plan_ids: plans.map(&:id) }, status: :created
else
render status: :unprocessable_entity
end
type = plan_params[:type]
partner = params[:plan][:partner_id].empty? ? nil : User.find(params[:plan][:partner_id])
else
@plan = PartnerPlan.new(plan_params)
if @plan.save
partner.add_role :partner, @plan
render :show, status: :created
else
render json: @plan.errors, status: :unprocessable_entity
end
end
else
if plan_params[:group_id] == 'all'
plans = Plan.create_for_all_groups(plan_params)
if plans
render json: { plan_ids: plans.map(&:id) }, status: :created
else
render status: :unprocessable_entity
end
else
@plan = Plan.new(plan_params)
if @plan.save
render :show, status: :created, location: @plan
else
render json: @plan.errors, status: :unprocessable_entity
end
end
end
rescue Stripe::InvalidRequestError => e
render json: {error: e.message}, status: :unprocessable_entity
res = PlansService.create(type, partner, plan_params)
if res[:errors]
render res[:errors], status: :unprocessable_entity
else
render json: res, status: :created
end
end
@ -76,21 +52,25 @@
end
private
def plan_params
if @parameters
@parameters
else
@parameters = params
@parameters[:plan][:amount] = @parameters[:plan][:amount].to_f * 100.0 if @parameters[:plan][:amount]
def plan_params
# parameters caching for performance
if @parameters
@parameters
else
@parameters = params
@parameters[:plan][:amount] = @parameters[:plan][:amount].to_f * 100.0 if @parameters[:plan][:amount]
if @parameters[:plan][:prices_attributes]
@parameters[:plan][:prices_attributes] = @parameters[:plan][:prices_attributes].map do |price|
{ amount: price[:amount].to_f * 100.0, id: price[:id] }
end if @parameters[:plan][:prices_attributes]
@parameters = @parameters.require(:plan).permit(:base_name, :type, :group_id, :amount, :interval, :interval_count, :is_rolling,
:training_credit_nb, :ui_weight, :disabled,
plan_file_attributes: [:id, :attachment, :_destroy],
prices_attributes: [:id, :amount]
)
end
end
@parameters = @parameters.require(:plan)
.permit(:base_name, :type, :group_id, :amount, :interval, :interval_count, :is_rolling,
:training_credit_nb, :ui_weight, :disabled,
plan_file_attributes: %i[id attachment _destroy],
prices_attributes: %i[id amount])
end
end
end

View File

@ -1,6 +1,10 @@
# frozen_string_literal: true
# API Controller for resources of type PriceCategory
# PriceCategories are used in Events
class API::PriceCategoriesController < API::ApiController
before_action :authenticate_user!, only: [:update, :show, :create, :destroy]
before_action :set_price_category, only: [:show, :update, :destroy]
before_action :authenticate_user!, only: %i[update show create destroy]
before_action :set_price_category, only: %i[show update destroy]
def index
@price_categories = PriceCategory.all
@ -15,8 +19,7 @@ class API::PriceCategoriesController < API::ApiController
end
end
def show
end
def show; end
def create
authorize PriceCategory
@ -38,6 +41,7 @@ class API::PriceCategoriesController < API::ApiController
end
private
def set_price_category
@price_category = PriceCategory.find(params[:id])
end
@ -45,4 +49,4 @@ class API::PriceCategoriesController < API::ApiController
def price_category_params
params.require(:price_category).permit(:name, :conditions)
end
end
end

View File

@ -1,3 +1,7 @@
# frozen_string_literal: true
# API Controller for resources of type Price
# Prices are used in reservations (Machine, Space)
class API::PricesController < API::ApiController
before_action :authenticate_user!
@ -6,29 +10,26 @@ class API::PricesController < API::ApiController
@prices = Price.all
if params[:priceable_type]
@prices = @prices.where(priceable_type: params[:priceable_type])
if params[:priceable_id]
@prices = @prices.where(priceable_id: params[:priceable_id])
end
@prices = @prices.where(priceable_id: params[:priceable_id]) if params[:priceable_id]
end
if params[:plan_id]
if params[:plan_id] =~ /no|nil|null|undefined/i
plan_id = nil
else
plan_id = params[:plan_id]
end
plan_id = if params[:plan_id] =~ /no|nil|null|undefined/i
nil
else
params[:plan_id]
end
@prices = @prices.where(plan_id: plan_id)
end
if params[:group_id]
@prices = @prices.where(group_id: params[:group_id])
end
@prices = @prices.where(group_id: params[:group_id]) if params[:group_id]
end
def update
authorize Price
@price = Price.find(params[:id])
_price_params = price_params
_price_params[:amount] = _price_params[:amount] * 100
if @price.update(_price_params)
price_parameters = price_params
price_parameters[:amount] = price_parameters[:amount] * 100
if @price.update(price_parameters)
render status: :ok
else
render status: :unprocessable_entity
@ -36,15 +37,22 @@ class API::PricesController < API::ApiController
end
def compute
_price_params = compute_price_params
price_parameters = compute_price_params
# user
_user = User.find(_price_params[:user_id])
user = User.find(price_parameters[:user_id])
# reservable
if _price_params[:reservable_id].nil?
if price_parameters[:reservable_id].nil?
@amount = {elements: nil, total: 0, before_coupon: 0}
else
_reservable = _price_params[:reservable_type].constantize.find(_price_params[:reservable_id])
@amount = Price.compute(current_user.admin?, _user, _reservable, _price_params[:slots_attributes] || [], _price_params[:plan_id], _price_params[:nb_reserve_places], _price_params[:tickets_attributes], coupon_params[:coupon_code])
reservable = price_parameters[:reservable_type].constantize.find(price_parameters[:reservable_id])
@amount = Price.compute(current_user.admin?,
user,
reservable,
price_parameters[:slots_attributes] || [],
price_parameters[:plan_id],
price_parameters[:nb_reserve_places],
price_parameters[:tickets_attributes],
coupon_params[:coupon_code])
end
@ -56,14 +64,15 @@ class API::PricesController < API::ApiController
end
private
def price_params
params.require(:price).permit(:amount)
end
def compute_price_params
params.require(:reservation).permit(:reservable_id, :reservable_type, :plan_id, :user_id, :nb_reserve_places,
tickets_attributes: [:event_price_category_id, :booked],
slots_attributes: [:id, :start_at, :end_at, :availability_id, :offered])
tickets_attributes: %i[event_price_category_id booked],
slots_attributes: %i[id start_at end_at availability_id offered])
end
def coupon_params

View File

@ -1,5 +1,8 @@
# frozen_string_literal: true
# API Controller for managing Plans prices
class API::PricingController < API::ApiController
before_action :authenticate_user!, except: [:index, :show]
before_action :authenticate_user!, except: %i[index show]
def index
@group_pricing = Group.includes(:plans, :trainings_pricings)
@ -10,14 +13,14 @@ class API::PricingController < API::ApiController
if params[:training].present?
training = Training.find params[:training]
params[:group_pricing].each do |group_id, amount|
if training
group = Group.includes(:plans).find(group_id)
if group
training_pricing = group.trainings_pricings.find_or_initialize_by(training_id: training.id)
training_pricing.amount = amount * 100
training_pricing.save
end
end
next unless training
group = Group.includes(:plans).find(group_id)
next unless group
training_pricing = group.trainings_pricings.find_or_initialize_by(training_id: training.id)
training_pricing.amount = amount * 100
training_pricing.save
end
end
head 200

View File

@ -1,3 +1,6 @@
# frozen_string_literal: true
# API Controller for resources of type Project
class API::ProjectsController < API::ApiController
before_action :authenticate_user!, except: %i[index show last_published search]
before_action :set_project, only: %i[update destroy]

View File

@ -1,3 +1,7 @@
# frozen_string_literal: true
# API Controller for resources of type Reservation
# Reservations are used for Training, Machine, Space and Event
class API::ReservationsController < API::ApiController
before_action :authenticate_user!
before_action :set_reservation, only: %i[show update]
@ -46,6 +50,7 @@ class API::ReservationsController < API::ApiController
end
private
def set_reservation
@reservation = Reservation.find(params[:id])
end

View File

@ -1,3 +1,6 @@
# frozen_string_literal: true
# API Controller for resources of type Setting
class API::SettingsController < API::ApiController
before_action :authenticate_user!, only: :update

View File

@ -1,6 +1,10 @@
# frozen_string_literal: true
# API Controller for resources of type Slot
# Slots are used to cut Availabilities into reservable slots of ApplicationHelper::SLOT_DURATION minutes
class API::SlotsController < API::ApiController
before_action :authenticate_user!
before_action :set_slot, only: [:update, :cancel]
before_action :set_slot, only: %i[update cancel]
respond_to :json
def update
@ -15,10 +19,11 @@ class API::SlotsController < API::ApiController
def cancel
authorize @slot
@slot.update_attributes(:canceled_at => DateTime.now)
@slot.update_attributes(canceled_at: DateTime.now)
end
private
def set_slot
@slot = Slot.find(params[:id])
end

View File

@ -1,5 +1,8 @@
# frozen_string_literal: true
# API Controller for resources of type Space
class API::SpacesController < API::ApiController
before_action :authenticate_user!, except: [:index, :show]
before_action :authenticate_user!, except: %i[index show]
respond_to :json
def index
@ -38,12 +41,14 @@ class API::SpacesController < API::ApiController
end
private
def get_space
Space.friendly.find(params[:id])
end
def space_params
params.require(:space).permit(:name, :description, :characteristics, :default_places, :disabled, space_image_attributes: [:attachment],
space_files_attributes: [:id, :attachment, :_destroy])
end
def get_space
Space.friendly.find(params[:id])
end
def space_params
params.require(:space).permit(:name, :description, :characteristics, :default_places, :disabled,
space_image_attributes: [:attachment],
space_files_attributes: %i[id attachment _destroy])
end
end

View File

@ -1,3 +1,6 @@
# frozen_string_literal: true
# API Controller for resources of type Space
class API::StatisticsController < API::ApiController
before_action :authenticate_user!
@ -6,7 +9,7 @@ class API::StatisticsController < API::ApiController
@statistics = StatisticIndex.all
end
%w(account event machine project subscription training user space).each do |path|
%w[account event machine project subscription training user space].each do |path|
class_eval %{
def #{path}
authorize :statistic, :#{path}?
@ -27,38 +30,50 @@ class API::StatisticsController < API::ApiController
# return result
render json: results
end
}, __FILE__, __LINE__ - 20
end
%w[account event machine project subscription training user space].each do |path|
class_eval %{
def export_#{path}
authorize :statistic, :export_#{path}?
export = Export.where({category:'statistics', export_type: '#{path}', query: params[:body], key: params[:type_key]}).last
export = Export.where(category:'statistics', export_type: '#{path}', query: params[:body], key: params[:type_key]).last
if export.nil? || !FileTest.exist?(export.file)
@export = Export.new({category:'statistics', export_type: '#{path}', user: current_user, query: params[:body], key: params[:type_key]})
@export = Export.new(category:'statistics',
export_type: '#{path}',
user: current_user,
query: params[:body],
key: params[:type_key])
if @export.save
render json: {export_id: @export.id}, status: :ok
else
render json: @export.errors, status: :unprocessable_entity
end
else
send_file File.join(Rails.root, export.file), :type => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', :disposition => 'attachment'
send_file File.join(Rails.root, export.file),
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
disposition: 'attachment'
end
end
}
}, __FILE__, __LINE__ - 22
end
def export_global
authorize :statistic, :export_global?
export = Export.where({category:'statistics', export_type: 'global', query: params[:body]}).last
export = Export.where(category: 'statistics', export_type: 'global', query: params[:body]).last
if export.nil? || !FileTest.exist?(export.file)
@export = Export.new({category:'statistics', export_type: 'global', user: current_user, query: params[:body]})
@export = Export.new(category: 'statistics', export_type: 'global', user: current_user, query: params[:body])
if @export.save
render json: {export_id: @export.id}, status: :ok
render json: { export_id: @export.id }, status: :ok
else
render json: @export.errors, status: :unprocessable_entity
end
else
send_file File.join(Rails.root, export.file), :type => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', :disposition => 'attachment'
send_file File.join(Rails.root, export.file),
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
disposition: 'attachment'
end
end
@ -68,5 +83,4 @@ class API::StatisticsController < API::ApiController
results = Elasticsearch::Model.client.scroll scroll: params[:scroll], scroll_id: params[:scrollId]
render json: results
end
end

View File

@ -1,3 +1,7 @@
# frozen_string_literal: true
# API Controller for resources of type Stylesheet
# Stylesheets are used to customize the appearance of fab-manager
class API::StylesheetsController < API::ApiController
caches_page :show # magic happens here
@ -5,7 +9,7 @@ class API::StylesheetsController < API::ApiController
@stylesheet = Stylesheet.find(params[:id])
respond_to do |format|
format.html # regular ERB template
format.css { render :text => @stylesheet.contents, :content_type => 'text/css' }
format.css { render text: @stylesheet.contents, content_type: 'text/css' }
end
end
end
end

View File

@ -1,3 +1,6 @@
# frozen_string_literal: true
# API Controller for resources of type Subscription
class API::SubscriptionsController < API::ApiController
include FablabConfiguration
@ -64,7 +67,7 @@ class API::SubscriptionsController < API::ApiController
params.require(:subscription).permit(:expired_at)
end
# TODO refactor subscriptions logic and move this in model/validator
# TODO, refactor subscriptions logic and move this in model/validator
def valid_card_token?(token)
Stripe::Token.retrieve(token)
rescue Stripe::InvalidRequestError => e

View File

@ -1,3 +1,7 @@
# frozen_string_literal: true
# API Controller for resources of type Tag
# Tags are used to restrict access to Availabilities
class API::TagsController < API::ApiController
before_action :authenticate_user!, except: %i[index show]
@ -35,6 +39,7 @@ class API::TagsController < API::ApiController
end
private
def set_tag
@tag = Tag.find(params[:id])
end

View File

@ -1,3 +1,7 @@
# frozen_string_literal: true
# API Controller for resources of type Theme
# Themes are used in Projects
class API::ThemesController < API::ApiController
before_action :authenticate_user!, except: %i[index show]
before_action :set_theme, only: %i[show update destroy]

View File

@ -1,3 +1,6 @@
# frozen_string_literal: true
# API Controller for resources of type Training
class API::TrainingsController < API::ApiController
include ApplicationHelper
@ -7,14 +10,12 @@ class API::TrainingsController < API::ApiController
def index
@requested_attributes = params[:requested_attributes]
@trainings = policy_scope(Training)
if params[:public_page]
@trainings = @trainings.where(public_page: true)
end
@trainings = @trainings.where(public_page: true) if params[:public_page]
if attribute_requested?(@requested_attributes, 'availabilities')
@trainings = @trainings.includes(availabilities: [slots: [reservation: [user: %i[profile trainings]]]])
.order('availabilities.start_at DESC')
end
return unless attribute_requested?(@requested_attributes, 'availabilities')
@trainings = @trainings.includes(availabilities: [slots: [reservation: [user: %i[profile trainings]]]])
.order('availabilities.start_at DESC')
end
def show

View File

@ -1,3 +1,6 @@
# frozen_string_literal: true
# API Controller for managing Training prices
class API::TrainingsPricingsController < API::ApiController
before_action :authenticate_user!
@ -8,9 +11,9 @@ class API::TrainingsPricingsController < API::ApiController
def update
if current_user.admin?
@trainings_pricing = TrainingsPricing.find(params[:id])
_trainings_pricing_params = trainings_pricing_params
_trainings_pricing_params[:amount] = _trainings_pricing_params[:amount] * 100
if @trainings_pricing.update(_trainings_pricing_params)
trainings_pricing_parameters = trainings_pricing_params
trainings_pricing_parameters[:amount] = trainings_pricing_parameters[:amount] * 100
if @trainings_pricing.update(trainings_pricing_parameters)
render status: :ok
else
render status: :unprocessable_entity

View File

@ -1,3 +1,6 @@
# frozen_string_literal: true
# API Controller for managing front-end translations
class API::TranslationsController < API::ApiController
before_action :set_locale

View File

@ -1,3 +1,6 @@
# frozen_string_literal: true
# API Controller for resources of type Users with role :partner
class API::UsersController < API::ApiController
before_action :authenticate_user!

View File

@ -1,3 +1,6 @@
# frozen_string_literal: true
# API Controller to get the fab-manager version
class API::VersionController < API::ApiController
before_action :authenticate_user!

View File

@ -1,3 +1,6 @@
# frozen_string_literal: true
# API Controller for resources of type Wallet
class API::WalletController < API::ApiController
before_action :authenticate_user!
@ -19,9 +22,7 @@ class API::WalletController < API::ApiController
service = WalletService.new(user: current_user, wallet: @wallet)
transaction = service.credit(credit_params[:amount].to_f)
if transaction
if credit_params[:avoir]
service.create_avoir(transaction, credit_params[:avoir_date], credit_params[:avoir_description])
end
service.create_avoir(transaction, credit_params[:avoir_date], credit_params[:avoir_description]) if credit_params[:avoir]
render :show
else
head 422

View File

@ -51,4 +51,9 @@ class ApplicationController < ActionController::Base
def current_user
super
end
# This is a placeholder for Devise's authenticate_user! method.
def authenticate_user!
super
end
end

View File

@ -1,3 +1,6 @@
# frozen_string_literal: true
# Coupon is a textual code associated with a discount rate or an amount of discount
class Coupon < ActiveRecord::Base
has_many :invoices
@ -6,7 +9,7 @@ class Coupon < ActiveRecord::Base
validates :name, presence: true
validates :code, presence: true
validates :code, format: { with: /\A[A-Z0-9\-]+\z/, message: 'only caps letters, numbers, and dashes'}
validates :code, format: { with: /\A[A-Z0-9\-]+\z/, message: 'only caps letters, numbers, and dashes' }
validates :code, uniqueness: true
validates :validity_per_user, presence: true
validates :validity_per_user, inclusion: { in: %w[once forever] }
@ -52,9 +55,9 @@ class Coupon < ActiveRecord::Base
end
def type
if amount_off.nil? and !percent_off.nil?
if amount_off.nil? && !percent_off.nil?
'percent_off'
elsif percent_off.nil? and !amount_off.nil?
elsif percent_off.nil? && !amount_off.nil?
'amount_off'
end
end

View File

@ -27,8 +27,8 @@ class Plan < ActiveRecord::Base
validates :amount, :group, :base_name, presence: true
validates :interval_count, numericality: { only_integer: true, greater_than_or_equal_to: 1 }
validates :interval_count, numericality: { less_than: 12 }, if: proc { |plan| plan.interval == 'month' }
validates :interval_count, numericality: { less_than: 52 }, if: proc { |plan| plan.interval == 'week' }
validates :interval_count, numericality: { less_than: 13 }, if: proc { |plan| plan.interval == 'month' }
validates :interval_count, numericality: { less_than: 53 }, if: proc { |plan| plan.interval == 'week' }
validates :interval, inclusion: { in: %w[year month week] }
validates :base_name, :slug, presence: true

View File

@ -54,6 +54,8 @@ class User < ActiveRecord::Base
has_many :exports, dependent: :destroy
has_many :history_values, dependent: :nullify
# fix for create admin user
before_save do
email&.downcase!

View File

@ -2,9 +2,11 @@ class UserPolicy < ApplicationPolicy
class Scope < Scope
def resolve
if user.admin?
scope.includes(:group, :training_credits, :machine_credits, :subscriptions => [:plan => [:credits]], :profile => [:user_avatar]).joins(:roles).where("users.is_active = 'true' AND roles.name = 'member'").order('users.created_at desc')
scope.includes(:group, :training_credits, :machine_credits, subscriptions: [plan: [:credits]], profile: [:user_avatar])
.joins(:roles).where("users.is_active = 'true' AND roles.name = 'member'").order('users.created_at desc')
else
scope.includes(:profile => [:user_avatar]).joins(:roles).where("users.is_active = 'true' AND roles.name = 'member'").where(is_allow_contact: true).order('users.created_at desc')
scope.includes(profile: [:user_avatar]).joins(:roles).where("users.is_active = 'true' AND roles.name = 'member'")
.where(is_allow_contact: true).order('users.created_at desc')
end
end
end
@ -25,7 +27,7 @@ class UserPolicy < ApplicationPolicy
user.id == record.id
end
%w(list create mapping).each do |action|
%w[list create mapping].each do |action|
define_method "#{action}?" do
user.admin?
end

View File

@ -0,0 +1,141 @@
# frozen_string_literal: true
# Provides helper methods for Availability resources and properties
class Availabilities::AvailabilitiesService
def initialize(current_user, maximum_visibility = {})
@current_user = current_user
@maximum_visibility = maximum_visibility
@service = Availabilities::StatusService.new(current_user.admin? ? 'admin' : 'user')
end
# list all slots for the given machine, with reservations info, relatives to the given user
def machines(machine_id, user)
machine = Machine.friendly.find(machine_id)
reservations = reservations(machine)
availabilities = availabilities(machine, 'machines', user)
slots = []
availabilities.each do |a|
((a.end_at - a.start_at) / ApplicationHelper::SLOT_DURATION.minutes).to_i.times do |i|
next unless (a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes) > Time.now
slot = Slot.new(
start_at: a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes,
end_at: a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes + ApplicationHelper::SLOT_DURATION.minutes,
availability_id: a.id,
availability: a,
machine: machine,
title: ''
)
slot = @service.machine_reserved_status(slot, reservations, @current_user)
slots << slot
end
end
slots
end
# list all slots for the given space, with reservations info, relatives to the given user
def spaces(space_id, user)
space = Space.friendly.find(space_id)
reservations = reservations(space)
availabilities = availabilities(space, 'space', user)
slots = []
availabilities.each do |a|
((a.end_at - a.start_at) / ApplicationHelper::SLOT_DURATION.minutes).to_i.times do |i|
next unless (a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes) > Time.now
slot = Slot.new(
start_at: a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes,
end_at: a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes + ApplicationHelper::SLOT_DURATION.minutes,
availability_id: a.id,
availability: a,
space: space,
title: ''
)
slot = @service.space_reserved_status(slot, reservations, user)
slots << slot
end
end
slots.each do |s|
s.title = t('availabilities.not_available') if s.is_complete? && !s.is_reserved
end
slots
end
# list all slots for the given training, with reservations info, relatives to the given user
def trainings(training_id, user)
# first, we get the already-made reservations
reservations = user.reservations.where("reservable_type = 'Training'")
reservations = reservations.where('reservable_id = :id', id: training_id.to_i) if training_id.is_number?
reservations = reservations.joins(:slots).where('slots.start_at > ?', Time.now)
# visible availabilities depends on multiple parameters
availabilities = training_availabilities(training_id, user)
# finally, we merge the availabilities with the reservations
availabilities.each do |a|
a = @service.training_event_reserved_status(a, reservations, user)
end
end
private
def subscription_year?(user)
user.subscription && user.subscription.plan.interval == 'year' && user.subscription.expired_at >= Time.now
end
# member must have validated at least 1 training and must have a valid yearly subscription.
def show_extended_slots?(user)
user.trainings.size.positive? && subscription_year?(user)
end
def reservations(reservable)
Reservation.where('reservable_type = ? and reservable_id = ?', reservable.class.name, reservable.id)
.includes(:slots, user: [:profile])
.references(:slots, :user)
.where('slots.start_at > ?', Time.now)
end
def availabilities(reservable, type, user)
if user.admin?
reservable.availabilities
.includes(:tags)
.where('end_at > ? AND available_type = ?', Time.now, type)
.where(lock: false)
else
end_at = @maximum_visibility[:other]
end_at = @maximum_visibility[:year] if subscription_year?(user)
reservable.availabilities
.includes(:tags)
.where('end_at > ? AND end_at < ? AND available_type = ?', Time.now, end_at, type)
.where('availability_tags.tag_id' => user.tag_ids.concat([nil]))
.where(lock: false)
end
end
def training_availabilities(training_id, user)
availabilities = if training_id.is_number? || (training_id.length.positive? && training_id != 'all')
Training.friendly.find(training_id).availabilities
else
Availability.trainings
end
# who made the request?
# 1) an admin (he can see all future availabilities)
if @current_user.admin?
availabilities.includes(:tags, :slots, trainings: [:machines])
.where('availabilities.start_at > ?', Time.now)
.where(lock: false)
# 2) an user (he cannot see availabilities further than 1 (or 3) months)
else
end_at = @maximum_visibility[:other]
end_at = @maximum_visibility[:year] if show_extended_slots?(user)
availabilities.includes(:tags, :slots, :availability_tags, trainings: [:machines])
.where('availabilities.start_at > ? AND availabilities.start_at < ?', Time.now, end_at)
.where('availability_tags.tag_id' => user.tag_ids.concat([nil]))
.where(lock: false)
end
end
end

View File

@ -0,0 +1,105 @@
# frozen_string_literal: true
# Provides helper methods for public calendar of Availability
class Availabilities::PublicAvailabilitiesService
def initialize(current_user)
@current_user = current_user
@service = Availabilities::StatusService.new('')
end
# provides a list of slots and availabilities for the machines, between the given dates
def machines(start_date, end_date, reservations, machine_ids)
availabilities = Availability.includes(:tags, :machines)
.where(available_type: 'machines')
.where('start_at >= ? AND end_at <= ?', start_date, end_date)
.where(lock: false)
slots = []
availabilities.each do |a|
a.machines.each do |machine|
next unless machine_ids&.include?(machine.id.to_s)
((a.end_at - a.start_at) / ApplicationHelper::SLOT_DURATION.minutes).to_i.times do |i|
slot = Slot.new(
start_at: a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes,
end_at: a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes + ApplicationHelper::SLOT_DURATION.minutes,
availability_id: a.id,
availability: a,
machine: machine,
title: machine.name
)
slot = @service.machine_reserved_status(slot, reservations, @current_user)
slots << slot
end
end
end
{ availabilities: availabilities, slots: slots }
end
# provides a list of slots and availabilities for the spaces, between the given dates
def spaces(start_date, end_date, reservations, available_id)
availabilities = Availability.includes(:tags, :spaces).where(available_type: 'space')
.where('start_at >= ? AND end_at <= ?', start_date, end_date)
.where(lock: false)
availabilities.where(available_id: available_id) if available_id
slots = []
availabilities.each do |a|
space = a.spaces.first
((a.end_at - a.start_at) / ApplicationHelper::SLOT_DURATION.minutes).to_i.times do |i|
next unless (a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes) > Time.now
slot = Slot.new(
start_at: a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes,
end_at: a.start_at + (i * ApplicationHelper::SLOT_DURATION).minutes + ApplicationHelper::SLOT_DURATION.minutes,
availability_id: a.id,
availability: a,
space: space,
title: space.name
)
slot = @service.space_reserved_status(slot, reservations, @current_user)
slots << slot
end
end
{ availabilities: availabilities, slots: slots }
end
def public_availabilities(start_date, end_date, reservations, ids)
if in_same_day(start_date, end_date)
# request for 1 single day
# trainings, events
training_event_availabilities = Availability.includes(:tags, :trainings, :slots)
.where(available_type: %w[training event])
.where('start_at >= ? AND end_at <= ?', start_date, end_date)
.where(lock: false)
# machines
machines_avail = machines(start_date, end_date, reservations, ids[:machines])
machine_slots = machines_avail[:slots]
# spaces
spaces_avail = spaces(start_date, end_date, reservations, ids[:spaces])
space_slots = spaces_avail[:slots]
[].concat(training_event_availabilities).concat(machine_slots).concat(space_slots)
else
# request for many days (week or month)
avails = Availability.includes(:tags, :machines, :trainings, :spaces, :event, :slots)
.where('start_at >= ? AND end_at <= ?', start_date, end_date)
.where(lock: false)
avails.each do |a|
if a.available_type == 'training' || a.available_type == 'event'
a = @service.training_event_reserved_status(a, reservations, @current_user)
elsif a.available_type == 'space'
a.is_reserved = @service.reserved_availability?(a, @current_user)
end
end
avails
end
end
private
def in_same_day(start_date, end_date)
(end_date.to_date - start_date.to_date).to_i == 1
end
end

View File

@ -0,0 +1,87 @@
# frozen_string_literal: true
# Provides helper methods checking reservation status of any availabilities
class Availabilities::StatusService
def initialize(current_user_role)
@current_user_role = current_user_role
end
# check that the provided machine slot is reserved or not and modify it accordingly
def machine_reserved_status(slot, reservations, user)
show_name = (@current_user_role == 'admin' || Setting.find_by(name: 'display_name_enable').value == 'true')
reservations.each do |r|
r.slots.each do |s|
next unless slot.machine.id == r.reservable_id
next unless s.start_at == slot.start_at && s.canceled_at.nil?
slot.id = s.id
slot.is_reserved = true
slot.title = "#{slot.machine.name} - #{show_name ? r.user.profile.full_name : t('availabilities.not_available')}"
slot.can_modify = true if @current_user_role == 'admin'
slot.reservations.push r
next unless r.user == user
slot.title = "#{slot.machine.name} - #{t('availabilities.i_ve_reserved')}"
slot.can_modify = true
slot.is_reserved_by_current_user = true
end
end
slot
end
# check that the provided space slot is reserved or not and modify it accordingly
def space_reserved_status(slot, reservations, user)
reservations.each do |r|
r.slots.each do |s|
next unless slot.space.id == r.reservable_id
next unless s.start_at == slot.start_at && s.canceled_at.nil?
slot.can_modify = true if @current_user_role == 'admin'
slot.reservations.push r
next unless r.user == user
slot.id = s.id
slot.title = t('availabilities.i_ve_reserved')
slot.can_modify = true
slot.is_reserved = true
end
end
slot
end
# check that the provided availability (training or event) is reserved or not and modify it accordingly
def training_event_reserved_status(availability, reservations, user)
reservations.each do |r|
r.slots.each do |s|
next unless (
(availability.available_type == 'training' && availability.trainings.first.id == r.reservable_id) ||
(availability.available_type == 'event' && availability.event.id == r.reservable_id)
) && s.start_at == availability.start_at && s.canceled_at.nil?
availability.slot_id = s.id
if r.user == user
availability.is_reserved = true
availability.can_modify = true
end
end
end
availability
end
# check that the provided ability is reserved by the given user
def reserved_availability?(availability, user)
if user
reserved_slots = []
availability.slots.each do |s|
reserved_slots << s if s.canceled_at.nil?
end
reserved_slots.map(&:reservations).flatten.map(&:user_id).include? user.id
else
false
end
end
end

View File

@ -0,0 +1,44 @@
# frozen_string_literal: true
# Provides helper methods for Events resources and properties
class EventService
def self.process_params(params)
# handle dates & times (whole-day events or not, maybe during many days)
range = EventService.date_range({ date: params[:start_date], time: params[:start_time] },
{ date: params[:end_date], time: params[:end_time] },
params[:all_day] == 'true')
params.merge!(availability_attributes: { id: params[:availability_id],
start_at: range[:start_at],
end_at: range[:end_at],
available_type: 'event' })
.except!(:start_date, :end_date, :start_time, :end_time, :all_day)
# convert main price to centimes
params[:amount] = (params[:amount].to_f * 100 if params[:amount].present?)
# delete non-complete "other" prices and convert them to centimes
unless params[:event_price_categories_attributes].nil?
params[:event_price_categories_attributes].delete_if do |price_cat|
price_cat[:price_category_id].empty? || price_cat[:amount].empty?
end
params[:event_price_categories_attributes].each do |price_cat|
price_cat[:amount] = price_cat[:amount].to_f * 100
end
end
# return the resulting params object
params
end
def self.date_range(starting, ending, all_day)
start_date = Time.zone.parse(starting[:date])
end_date = Time.zone.parse(ending[:date])
start_time = Time.parse(starting[:time]) if starting[:time]
end_time = Time.parse(ending[:time]) if ending[:time]
if all_day == 'true'
start_at = DateTime.new(start_date.year, start_date.month, start_date.day, 0, 0, 0, start_date.zone)
end_at = DateTime.new(end_date.year, end_date.month, end_date.day, 23, 59, 59, end_date.zone)
else
start_at = DateTime.new(start_date.year, start_date.month, start_date.day, start_time.hour, start_time.min, start_time.sec, start_date.zone)
end_at = DateTime.new(end_date.year, end_date.month, end_date.day, end_time.hour, end_time.min, end_time.sec, end_date.zone)
end
{ start_at: start_at, end_at: end_at }
end
end

View File

@ -0,0 +1,87 @@
# frozen_string_literal: true
# Provides helper methods for listing Users
class Members::ListService
class << self
def list(params)
@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(list_order(params))
.page(params[:page])
.per(params[:size])
# ILIKE => PostgreSQL case-insensitive LIKE
if params[:search].size.positive?
@query = @query.where('profiles.first_name ILIKE :search OR ' \
'profiles.last_name ILIKE :search OR ' \
'profiles.phone ILIKE :search OR ' \
'email ILIKE :search OR ' \
'groups.name ILIKE :search OR ' \
'plans.base_name ILIKE :search', search: "%#{params[:search]}%")
end
@query
end
def search(current_user, query, subscription)
members = User.includes(:profile)
.joins(:profile,
:roles,
'LEFT JOIN "subscriptions" ON "subscriptions"."user_id" = "users"."id" AND ' \
'"subscriptions"."created_at" = ( ' \
'SELECT max("created_at") ' \
'FROM "subscriptions" ' \
'WHERE "user_id" = "users"."id")')
.where("users.is_active = 'true' AND roles.name = 'member'")
query.downcase.split(' ').each do |word|
members = members.where('lower(f_unaccent(profiles.first_name)) ~ :search OR ' \
'lower(f_unaccent(profiles.last_name)) ~ :search',
search: word)
end
if current_user.member?
# non-admin can only retrieve users with "public profiles"
members = members.where("users.is_allow_contact = 'true'")
elsif subscription == 'true'
# only admins have the ability to filter by subscription
members = members.where('subscriptions.id IS NOT NULL AND subscriptions.expiration_date >= :now', now: Date.today.to_s)
elsif subscription == 'false'
members = members.where('subscriptions.id IS NULL OR subscriptions.expiration_date < :now', now: Date.today.to_s)
end
members.to_a
end
private
def list_order(params)
direction = (params[:order_by][0] == '-' ? 'DESC' : 'ASC')
order_key = (params[:order_by][0] == '-' ? params[:order_by][1, params[:order_by].size] : params[:order_by])
order_key = case order_key
when 'last_name'
'profiles.last_name'
when 'first_name'
'profiles.first_name'
when 'email'
'users.email'
when 'phone'
'profiles.phone'
when 'group'
'groups.name'
when 'plan'
'plans.base_name'
else
'users.id'
end
"#{order_key} #{direction}"
end
end
end

View File

@ -0,0 +1,77 @@
# frozen_string_literal: true
# Provides helper methods for User actions
class Members::MembersService
attr_accessor :member
def initialize(member)
@member = member
end
def update(params)
if params[:group_id] && @member.group_id != params[:group_id].to_i && !@member.subscribed_plan.nil?
# here a group change is requested but unprocessable, handle the exception
@member.errors[:group_id] = I18n.t('members.unable_to_change_the_group_while_a_subscription_is_running')
return false
end
not_complete = member.need_completion?
up_result = member.update(params)
notify_user_profile_complete(not_complete) if up_result
up_result
end
def create(current_user, params)
@member.password = password(params)
# if the user is created by an admin and the authentication is made through an SSO, generate a migration token
@member.generate_auth_migration_token if current_user.admin? && AuthProvider.active.providable_type != DatabaseProvider.name
if @member.save
@member.generate_subscription_invoice
@member.send_confirmation_instructions
UsersMailer.delay.notify_user_account_created(@member, @member.password)
true
else
false
end
end
def merge_from_sso(user)
merge_result = member.merge_from_sso(user)
notify_admin_user_merged if merge_result
merge_result
end
private
def notify_user_profile_complete(previous_state)
return unless previous_state && !member.need_completion?
NotificationCenter.call type: :notify_user_profile_complete,
receiver: member,
attached_object: member
NotificationCenter.call type: :notify_admin_profile_complete,
receiver: User.admins,
attached_object: member
end
def notify_admin_user_merged
NotificationCenter.call type: :notify_admin_user_merged,
receiver: User.admins,
attached_object: member
end
def password(params)
if !params[:password] && !params[:password_confirmation]
Devise.friendly_token.first(8)
else
params[:password]
end
end
end

View File

@ -1,45 +0,0 @@
class MembersService
attr_accessor :member
def initialize(member)
@member = member
end
def update(params)
not_complete = self.member.need_completion?
up_result = self.member.update(params)
if up_result
notify_user_profile_complete(not_complete)
end
up_result
end
def merge_from_sso(user)
merge_result = self.member.merge_from_sso(user)
if merge_result
notify_admin_user_merged
end
merge_result
end
private
def notify_user_profile_complete(previous_state)
if previous_state and not self.member.need_completion?
NotificationCenter.call type: :notify_user_profile_complete,
receiver: self.member,
attached_object: self.member
NotificationCenter.call type: :notify_admin_profile_complete,
receiver: User.admins,
attached_object: self.member
end
end
def notify_admin_user_merged
NotificationCenter.call type: :notify_admin_user_merged,
receiver: User.admins,
attached_object: self.member
end
end

View File

@ -0,0 +1,26 @@
# frozen_string_literal: true
# Provides methods for Plan & PartnerPlan actions
class PlansService
class << self
def create(type, partner, params)
if params[:group_id] == 'all'
plans = type.constantize.create_for_all_groups(params)
return false unless plans
plans.each { |plan| partner.add_role :partner, plan } unless partner.nil?
{ plan_ids: plans.map(&:id) }
else
plan = type.constantize.new(params)
if plan.save
partner&.add_role :partner, plan
plan
else
{ errors: @plan.errors }
end
end
rescue Stripe::InvalidRequestError => e
{ errors: e.message }
end
end
end

View File

@ -1,7 +1,5 @@
max_members = @query.except(:offset, :limit, :order).count
json.array!(@members) do |member|
json.maxMembers max_members
json.maxMembers @max_members
json.id member.id
json.email member.email if current_user
json.profile do
@ -13,7 +11,9 @@ json.array!(@members) do |member|
json.group do
json.name member.group.name
end
json.subscribed_plan do
json.partial! 'api/shared/plan', plan: member.subscribed_plan
end if member.subscribed_plan
end
if member.subscribed_plan
json.subscribed_plan do
json.partial! 'api/shared/plan', plan: member.subscribed_plan
end
end
end

View File

@ -3,9 +3,11 @@ json.setting do
if @show_history
json.history @setting.history_values do |value|
json.extract! value, :value, :created_at
json.user do
json.id value.user_id
json.name "#{value.user.first_name} #{value.user.last_name}"
unless value.user_id.nil?
json.user do
json.id value.user_id
json.name "#{value.user.first_name} #{value.user.last_name}"
end
end
end
end

View File

@ -367,6 +367,7 @@ en:
VAT_history: "VAT rates history"
changed_at: "Changed at"
changed_by: "By"
deleted_user: "Deleted user"
refund_invoice_successfully_created: "Refund invoice successfully created."
create_a_refund_on_this_invoice: "Create a refund on this invoice"
creation_date_for_the_refund: "Creation date for the refund"

View File

@ -367,6 +367,7 @@ es:
VAT_history: "Historial de ratios de IVA"
changed_at: "Cambiado en"
changed_by: "Por"
deleted_user: "Usario eliminado"
refund_invoice_successfully_created: "Factura de reembolso creada correctamente."
create_a_refund_on_this_invoice: "Crear un reembolso en esta factura"
creation_date_for_the_refund: "Fecha de creación del reembolso"

View File

@ -367,6 +367,7 @@ fr:
VAT_history: "Historique des taux de TVA"
changed_at: "Changé le"
changed_by: "Par"
deleted_user: "Utilisateur supprimé"
refund_invoice_successfully_created: "La facture d'avoir a bien été créée."
create_a_refund_on_this_invoice: "Générer un avoir sur cette facture"
creation_date_for_the_refund: "Date d'émission de l'avoir"

View File

@ -367,6 +367,7 @@ pt:
VAT_history: "VAT rates history" #translation_missing
changed_at: "Changed at" #translation_missing
changed_by: "By" #translation_missing
deleted_user: "Deleted user" #translation_missing
refund_invoice_successfully_created: "Restituição de fatura criada com sucesso."
create_a_refund_on_this_invoice: "Criar restituição de fatura"
creation_date_for_the_refund: "Criação de data de restituição"

View File

@ -211,6 +211,7 @@ en:
plan_form:
general_information: "General information"
name: "Name"
name_is_required: "Name is required"
name_length_must_be_less_than_24_characters: "Name length must be less than 24 characters."
type: "Type"
type_is_required: "Type is required."

View File

@ -211,6 +211,7 @@ es:
plan_form:
general_information: "Información general"
name: "Nombre"
name_is_required: "Se requiere un nombre."
name_length_must_be_less_than_24_characters: "el nombre debe contener menos de 24 caracteres."
type: "Tipo"
type_is_required: "Se requiere un tipo."

View File

@ -211,6 +211,7 @@ fr:
plan_form:
general_information: "Informations générales"
name: "Nom"
name_is_required: "Le nom est requis"
name_length_must_be_less_than_24_characters: "Le nom doit faire moins de 24 caractères."
type: "Type"
type_is_required: "Le type est requis."

View File

@ -211,6 +211,7 @@ pt:
plan_form:
general_information: "Informação geral"
name: "Nome"
name_is_required: "Nome é obrigatório."
name_length_must_be_less_than_24_characters: "O nome deve conter no máximo 24 caracteres."
type: "Tipo"
type_is_required: "Tipo é obrigatório."

View File

@ -15,8 +15,8 @@ ActiveRecord::Schema.define(version: 20190110150532) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
enable_extension "pg_trgm"
enable_extension "unaccent"
enable_extension "pg_trgm"
create_table "abuses", force: :cascade do |t|
t.integer "signaled_id"
@ -31,24 +31,15 @@ ActiveRecord::Schema.define(version: 20190110150532) do
add_index "abuses", ["signaled_type", "signaled_id"], name: "index_abuses_on_signaled_type_and_signaled_id", using: :btree
create_table "accounting_periods", force: :cascade do |t|
t.date "start_at"
t.date "end_at"
t.datetime "closed_at"
t.integer "closed_by"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "addresses", force: :cascade do |t|
t.string "address", limit: 255
t.string "street_number", limit: 255
t.string "route", limit: 255
t.string "locality", limit: 255
t.string "country", limit: 255
t.string "postal_code", limit: 255
t.string "address"
t.string "street_number"
t.string "route"
t.string "locality"
t.string "country"
t.string "postal_code"
t.integer "placeable_id"
t.string "placeable_type", limit: 255
t.string "placeable_type"
t.datetime "created_at"
t.datetime "updated_at"
end
@ -64,9 +55,9 @@ ActiveRecord::Schema.define(version: 20190110150532) do
create_table "assets", force: :cascade do |t|
t.integer "viewable_id"
t.string "viewable_type", limit: 255
t.string "attachment", limit: 255
t.string "type", limit: 255
t.string "viewable_type"
t.string "attachment"
t.string "type"
t.datetime "created_at"
t.datetime "updated_at"
end
@ -83,12 +74,12 @@ ActiveRecord::Schema.define(version: 20190110150532) do
create_table "availabilities", force: :cascade do |t|
t.datetime "start_at"
t.datetime "end_at"
t.string "available_type", limit: 255
t.string "available_type"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "nb_total_places"
t.boolean "destroying", default: false
t.boolean "lock", default: false
t.boolean "destroying", default: false
t.boolean "lock", default: false
end
create_table "availability_tags", force: :cascade do |t|
@ -102,7 +93,7 @@ ActiveRecord::Schema.define(version: 20190110150532) do
add_index "availability_tags", ["tag_id"], name: "index_availability_tags_on_tag_id", using: :btree
create_table "categories", force: :cascade do |t|
t.string "name", limit: 255
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
t.string "slug"
@ -111,7 +102,7 @@ ActiveRecord::Schema.define(version: 20190110150532) do
add_index "categories", ["slug"], name: "index_categories_on_slug", unique: true, using: :btree
create_table "components", force: :cascade do |t|
t.string "name", limit: 255, null: false
t.string "name", null: false
end
create_table "coupons", force: :cascade do |t|
@ -129,7 +120,7 @@ ActiveRecord::Schema.define(version: 20190110150532) do
create_table "credits", force: :cascade do |t|
t.integer "creditable_id"
t.string "creditable_type", limit: 255
t.string "creditable_type"
t.integer "plan_id"
t.integer "hours"
t.datetime "created_at"
@ -170,7 +161,7 @@ ActiveRecord::Schema.define(version: 20190110150532) do
add_index "event_themes", ["slug"], name: "index_event_themes_on_slug", unique: true, using: :btree
create_table "events", force: :cascade do |t|
t.string "title", limit: 255
t.string "title"
t.text "description"
t.datetime "created_at"
t.datetime "updated_at"
@ -208,10 +199,10 @@ ActiveRecord::Schema.define(version: 20190110150532) do
add_index "exports", ["user_id"], name: "index_exports_on_user_id", using: :btree
create_table "friendly_id_slugs", force: :cascade do |t|
t.string "slug", limit: 255, null: false
t.integer "sluggable_id", null: false
t.string "slug", null: false
t.integer "sluggable_id", null: false
t.string "sluggable_type", limit: 50
t.string "scope", limit: 255
t.string "scope"
t.datetime "created_at"
end
@ -221,10 +212,10 @@ ActiveRecord::Schema.define(version: 20190110150532) do
add_index "friendly_id_slugs", ["sluggable_type"], name: "index_friendly_id_slugs_on_sluggable_type", using: :btree
create_table "groups", force: :cascade do |t|
t.string "name", limit: 255
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
t.string "slug", limit: 255
t.string "slug"
t.boolean "disabled"
end
@ -243,7 +234,7 @@ ActiveRecord::Schema.define(version: 20190110150532) do
create_table "invoice_items", force: :cascade do |t|
t.integer "invoice_id"
t.string "stp_invoice_item_id", limit: 255
t.string "stp_invoice_item_id"
t.integer "amount"
t.datetime "created_at"
t.datetime "updated_at"
@ -256,17 +247,17 @@ ActiveRecord::Schema.define(version: 20190110150532) do
create_table "invoices", force: :cascade do |t|
t.integer "invoiced_id"
t.string "invoiced_type", limit: 255
t.string "stp_invoice_id", limit: 255
t.string "invoiced_type"
t.string "stp_invoice_id"
t.integer "total"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "user_id"
t.string "reference", limit: 255
t.string "avoir_mode", limit: 255
t.string "reference"
t.string "avoir_mode"
t.datetime "avoir_date"
t.integer "invoice_id"
t.string "type", limit: 255
t.string "type"
t.boolean "subscription_to_expire"
t.text "description"
t.integer "wallet_amount"
@ -280,17 +271,17 @@ ActiveRecord::Schema.define(version: 20190110150532) do
add_index "invoices", ["wallet_transaction_id"], name: "index_invoices_on_wallet_transaction_id", using: :btree
create_table "licences", force: :cascade do |t|
t.string "name", limit: 255, null: false
t.string "name", null: false
t.text "description"
end
create_table "machines", force: :cascade do |t|
t.string "name", limit: 255, null: false
t.string "name", null: false
t.text "description"
t.text "spec"
t.datetime "created_at"
t.datetime "updated_at"
t.string "slug", limit: 255
t.string "slug"
t.boolean "disabled"
end
@ -307,14 +298,14 @@ ActiveRecord::Schema.define(version: 20190110150532) do
create_table "notifications", force: :cascade do |t|
t.integer "receiver_id"
t.integer "attached_object_id"
t.string "attached_object_type", limit: 255
t.string "attached_object_type"
t.integer "notification_type_id"
t.boolean "is_read", default: false
t.boolean "is_read", default: false
t.datetime "created_at"
t.datetime "updated_at"
t.string "receiver_type"
t.boolean "is_send", default: false
t.jsonb "meta_data", default: {}
t.boolean "is_send", default: false
t.jsonb "meta_data", default: {}
end
add_index "notifications", ["notification_type_id"], name: "index_notifications_on_notification_type_id", using: :btree
@ -383,20 +374,20 @@ ActiveRecord::Schema.define(version: 20190110150532) do
add_index "organizations", ["profile_id"], name: "index_organizations_on_profile_id", using: :btree
create_table "plans", force: :cascade do |t|
t.string "name", limit: 255
t.string "name"
t.integer "amount"
t.string "interval", limit: 255
t.string "interval"
t.integer "group_id"
t.string "stp_plan_id", limit: 255
t.string "stp_plan_id"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "training_credit_nb", default: 0
t.boolean "is_rolling", default: true
t.integer "training_credit_nb", default: 0
t.boolean "is_rolling", default: true
t.text "description"
t.string "type"
t.string "base_name"
t.integer "ui_weight", default: 0
t.integer "interval_count", default: 1
t.integer "ui_weight", default: 0
t.integer "interval_count", default: 1
t.string "slug"
t.boolean "disabled"
end
@ -426,11 +417,11 @@ ActiveRecord::Schema.define(version: 20190110150532) do
create_table "profiles", force: :cascade do |t|
t.integer "user_id"
t.string "first_name", limit: 255
t.string "last_name", limit: 255
t.string "first_name"
t.string "last_name"
t.boolean "gender"
t.date "birthday"
t.string "phone", limit: 255
t.string "phone"
t.text "interest"
t.text "software_mastered"
t.datetime "created_at"
@ -460,7 +451,7 @@ ActiveRecord::Schema.define(version: 20190110150532) do
t.integer "project_id"
t.datetime "created_at"
t.datetime "updated_at"
t.string "title", limit: 255
t.string "title"
t.integer "step_nb"
end
@ -471,27 +462,27 @@ ActiveRecord::Schema.define(version: 20190110150532) do
t.integer "user_id"
t.datetime "created_at"
t.datetime "updated_at"
t.boolean "is_valid", default: false
t.string "valid_token", limit: 255
t.boolean "is_valid", default: false
t.string "valid_token"
end
add_index "project_users", ["project_id"], name: "index_project_users_on_project_id", using: :btree
add_index "project_users", ["user_id"], name: "index_project_users_on_user_id", using: :btree
create_table "projects", force: :cascade do |t|
t.string "name", limit: 255
t.string "name"
t.text "description"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "author_id"
t.text "tags"
t.integer "licence_id"
t.string "state", limit: 255
t.string "slug", limit: 255
t.string "state"
t.string "slug"
t.datetime "published_at"
end
add_index "projects", ["slug"], name: "index_projects_on_slug", using: :btree
add_index "projects", ["slug"], name: "index_projects_on_slug", unique: true, using: :btree
create_table "projects_components", force: :cascade do |t|
t.integer "project_id"
@ -531,19 +522,19 @@ ActiveRecord::Schema.define(version: 20190110150532) do
t.datetime "created_at"
t.datetime "updated_at"
t.integer "reservable_id"
t.string "reservable_type", limit: 255
t.string "stp_invoice_id", limit: 255
t.string "reservable_type"
t.string "stp_invoice_id"
t.integer "nb_reserve_places"
end
add_index "reservations", ["reservable_id", "reservable_type"], name: "index_reservations_on_reservable_id_and_reservable_type", using: :btree
add_index "reservations", ["reservable_type", "reservable_id"], name: "index_reservations_on_reservable_type_and_reservable_id", using: :btree
add_index "reservations", ["stp_invoice_id"], name: "index_reservations_on_stp_invoice_id", using: :btree
add_index "reservations", ["user_id"], name: "index_reservations_on_user_id", using: :btree
create_table "roles", force: :cascade do |t|
t.string "name", limit: 255
t.string "name"
t.integer "resource_id"
t.string "resource_type", limit: 255
t.string "resource_type"
t.datetime "created_at"
t.datetime "updated_at"
end
@ -617,18 +608,18 @@ ActiveRecord::Schema.define(version: 20190110150532) do
create_table "statistic_fields", force: :cascade do |t|
t.integer "statistic_index_id"
t.string "key", limit: 255
t.string "label", limit: 255
t.string "key"
t.string "label"
t.datetime "created_at"
t.datetime "updated_at"
t.string "data_type", limit: 255
t.string "data_type"
end
add_index "statistic_fields", ["statistic_index_id"], name: "index_statistic_fields_on_statistic_index_id", using: :btree
create_table "statistic_graphs", force: :cascade do |t|
t.integer "statistic_index_id"
t.string "chart_type", limit: 255
t.string "chart_type"
t.integer "limit"
t.datetime "created_at"
t.datetime "updated_at"
@ -637,17 +628,17 @@ ActiveRecord::Schema.define(version: 20190110150532) do
add_index "statistic_graphs", ["statistic_index_id"], name: "index_statistic_graphs_on_statistic_index_id", using: :btree
create_table "statistic_indices", force: :cascade do |t|
t.string "es_type_key", limit: 255
t.string "label", limit: 255
t.string "es_type_key"
t.string "label"
t.datetime "created_at"
t.datetime "updated_at"
t.boolean "table", default: true
t.boolean "ca", default: true
t.boolean "table", default: true
t.boolean "ca", default: true
end
create_table "statistic_sub_types", force: :cascade do |t|
t.string "key", limit: 255
t.string "label", limit: 255
t.string "key"
t.string "label"
t.datetime "created_at"
t.datetime "updated_at"
end
@ -664,8 +655,8 @@ ActiveRecord::Schema.define(version: 20190110150532) do
create_table "statistic_types", force: :cascade do |t|
t.integer "statistic_index_id"
t.string "key", limit: 255
t.string "label", limit: 255
t.string "key"
t.string "label"
t.boolean "graph"
t.datetime "created_at"
t.datetime "updated_at"
@ -683,7 +674,7 @@ ActiveRecord::Schema.define(version: 20190110150532) do
create_table "subscriptions", force: :cascade do |t|
t.integer "plan_id"
t.integer "user_id"
t.string "stp_subscription_id", limit: 255
t.string "stp_subscription_id"
t.datetime "created_at"
t.datetime "updated_at"
t.datetime "expiration_date"
@ -702,7 +693,7 @@ ActiveRecord::Schema.define(version: 20190110150532) do
add_index "tags", ["name"], name: "index_tags_on_name", unique: true, using: :btree
create_table "themes", force: :cascade do |t|
t.string "name", limit: 255, null: false
t.string "name", null: false
end
create_table "tickets", force: :cascade do |t|
@ -717,13 +708,13 @@ ActiveRecord::Schema.define(version: 20190110150532) do
add_index "tickets", ["reservation_id"], name: "index_tickets_on_reservation_id", using: :btree
create_table "trainings", force: :cascade do |t|
t.string "name", limit: 255
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "nb_total_places"
t.string "slug", limit: 255
t.string "slug"
t.text "description"
t.boolean "public_page", default: true
t.boolean "public_page", default: true
t.boolean "disabled"
end
@ -779,31 +770,31 @@ ActiveRecord::Schema.define(version: 20190110150532) do
add_index "user_trainings", ["user_id"], name: "index_user_trainings_on_user_id", using: :btree
create_table "users", force: :cascade do |t|
t.string "email", limit: 255, default: "", null: false
t.string "encrypted_password", limit: 255, default: "", null: false
t.string "reset_password_token", limit: 255
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip", limit: 255
t.string "last_sign_in_ip", limit: 255
t.string "confirmation_token", limit: 255
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.string "confirmation_token"
t.datetime "confirmed_at"
t.datetime "confirmation_sent_at"
t.string "unconfirmed_email", limit: 255
t.integer "failed_attempts", default: 0, null: false
t.string "unlock_token", limit: 255
t.string "unconfirmed_email"
t.integer "failed_attempts", default: 0, null: false
t.string "unlock_token"
t.datetime "locked_at"
t.datetime "created_at"
t.datetime "updated_at"
t.boolean "is_allow_contact", default: true
t.boolean "is_allow_contact", default: true
t.integer "group_id"
t.string "stp_customer_id", limit: 255
t.string "username", limit: 255
t.string "slug", limit: 255
t.boolean "is_active", default: true
t.string "stp_customer_id"
t.string "username"
t.string "slug"
t.boolean "is_active", default: true
t.string "provider"
t.string "uid"
t.string "auth_token"
@ -864,7 +855,6 @@ ActiveRecord::Schema.define(version: 20190110150532) do
add_index "wallets", ["user_id"], name: "index_wallets_on_user_id", using: :btree
add_foreign_key "accounting_periods", "users", column: "closed_by"
add_foreign_key "availability_tags", "availabilities"
add_foreign_key "availability_tags", "tags"
add_foreign_key "event_price_categories", "events"

View File

@ -1,6 +1,6 @@
{
"name": "fab-manager",
"version": "2.8.1",
"version": "2.8.2-dev",
"description": "FabManager is the FabLab management solution. It is web-based, open-source and totally free.",
"keywords": [
"fablab",

View File

@ -1,3 +1,4 @@
# frozen_string_literal: true
module Events
class AsAdminTest < ActionDispatch::IntegrationTest
@ -11,16 +12,16 @@ module Events
# First, we create a new event
post '/api/events',
{
event: {
title: 'OpenLab discovery day',
description: 'A day to discover the Fablab and try its machines and possibilities.',
start_date: 1.week.from_now.utc,
start_time: 1.week.from_now.utc.change({hour: 16}),
end_date: 1.week.from_now.utc,
end_time: 1.week.from_now.utc.change({hour: 20}),
category_id: Category.first.id,
amount: 0
}
event: {
title: 'OpenLab discovery day',
description: 'A day to discover the Fablab and try its machines and possibilities.',
start_date: 1.week.from_now.utc,
start_time: 1.week.from_now.utc.change(hour: 16),
end_date: 1.week.from_now.utc,
end_time: 1.week.from_now.utc.change(hour: 20),
category_id: Category.first.id,
amount: 0
}
}.to_json,
default_headers
@ -38,18 +39,16 @@ module Events
# Then, modify the event to set a nb of places
put "/api/events/#{e.id}",
{
event: {
title: 'OpenLab discovery day',
description: 'A day to discover the Fablab and try its machines and possibilities.',
start_date: 1.week.from_now.utc,
start_time: 1.week.from_now.utc.change({hour: 16}),
end_date: 1.week.from_now.utc,
end_time: 1.week.from_now.utc.change({hour: 20}),
category_id: Category.first.id,
amount: 0,
nb_total_places: 10
}
event: {
title: 'OpenLab discovery day',
description: 'A day to discover the Fablab and try its machines and possibilities.',
start_date: 1.week.from_now.utc,
start_time: 1.week.from_now.utc.change(hour: 16),
end_date: 1.week.from_now.utc,
end_time: 1.week.from_now.utc.change(hour: 20),
category_id: Category.first.id,
amount: 0,
nb_total_places: 10
}
# Check response format & status
@ -63,23 +62,23 @@ module Events
# Now, let's make a reservation on this event
post '/api/reservations',
{
reservation: {
user_id: User.find_by(username: 'pdurand').id,
reservable_id: e.id,
reservable_type: 'Event',
nb_reserve_places: 2,
slots_attributes: [
{
start_at: e.availability.start_at,
end_at: e.availability.end_at,
availability_id: e.availability.id,
offered: false
}
]
}
}.to_json,
default_headers
{
reservation: {
user_id: User.find_by(username: 'pdurand').id,
reservable_id: e.id,
reservable_type: 'Event',
nb_reserve_places: 2,
slots_attributes: [
{
start_at: e.availability.start_at,
end_at: e.availability.end_at,
availability_id: e.availability.id,
offered: false
}
]
}
}.to_json,
default_headers
# Check response format & status
assert_equal 201, response.status, response.body
@ -91,18 +90,16 @@ module Events
# Finally, modify the event to add some places
put "/api/events/#{e.id}",
{
event: {
title: 'OpenLab discovery day',
description: 'A day to discover the Fablab and try its machines and possibilities.',
start_date: 1.week.from_now.utc,
start_time: 1.week.from_now.utc.change({hour: 16}),
end_date: 1.week.from_now.utc,
end_time: 1.week.from_now.utc.change({hour: 20}),
category_id: Category.first.id,
amount: 0,
nb_total_places: 20
}
event: {
title: 'OpenLab discovery day',
description: 'A day to discover the Fablab and try its machines and possibilities.',
start_date: 1.week.from_now.utc,
start_time: 1.week.from_now.utc.change(hour: 16),
end_date: 1.week.from_now.utc,
end_time: 1.week.from_now.utc.change(hour: 20),
category_id: Category.first.id,
amount: 0,
nb_total_places: 20
}
# Check response format & status
@ -122,23 +119,23 @@ module Events
# First, we create a new event
post '/api/events',
{
event: {
title: 'Electronics initiation',
description: 'A workshop about electronics and the abilities to create robots.',
start_date: 1.week.from_now.utc + 2.days,
start_time: 1.week.from_now.utc.change({hour: 18}) + 2.days,
end_date: 1.week.from_now.utc + 2.days,
end_time: 1.week.from_now.utc.change({hour: 22}) + 2.days,
category_id: Category.last.id,
amount: 20,
nb_total_places: 10,
event_price_categories_attributes: [
{
price_category_id: price_category.id.to_s,
amount: 16.to_s
}
]
}
event: {
title: 'Electronics initiation',
description: 'A workshop about electronics and the abilities to create robots.',
start_date: 1.week.from_now.utc + 2.days,
start_time: 1.week.from_now.utc.change(hour: 18) + 2.days,
end_date: 1.week.from_now.utc + 2.days,
end_time: 1.week.from_now.utc.change(hour: 22) + 2.days,
category_id: Category.last.id,
amount: 20,
nb_total_places: 10,
event_price_categories_attributes: [
{
price_category_id: price_category.id.to_s,
amount: 16.to_s
}
]
}
}.to_json,
default_headers
@ -159,26 +156,26 @@ module Events
# Now, let's make a reservation on this event
post '/api/reservations',
{
reservation: {
user_id: User.find_by(username: 'lseguin').id,
reservable_id: e.id,
reservable_type: 'Event',
nb_reserve_places: 4,
slots_attributes: [
{
start_at: e.availability.start_at,
end_at: e.availability.end_at,
availability_id: e.availability.id,
offered: false
}
],
tickets_attributes: [
{
event_price_category_id: e.event_price_categories.first.id,
booked: 4
}
]
}
reservation: {
user_id: User.find_by(username: 'lseguin').id,
reservable_id: e.id,
reservable_type: 'Event',
nb_reserve_places: 4,
slots_attributes: [
{
start_at: e.availability.start_at,
end_at: e.availability.end_at,
availability_id: e.availability.id,
offered: false
}
],
tickets_attributes: [
{
event_price_category_id: e.event_price_categories.first.id,
booked: 4
}
]
}
}.to_json,
default_headers
@ -203,4 +200,4 @@ module Events
end
end
end
end

View File

@ -84,4 +84,18 @@ class MembersTest < ActionDispatch::IntegrationTest
assert_equal 2, res[:group_id], "user's group does not match"
assert_equal instagram, res[:profile][:instagram], "user's social network not updated"
end
test 'admin search for autocompletion of a member s name' do
get '/api/members/search/kevin?subscription=true'
# Check response format & status
assert_equal 200, response.status, response.body
assert_equal Mime::JSON, response.content_type
# Check search result
res = json_response(response.body)
assert_equal 1, res.length
assert_match /Kevin/, res[0][:name]
end
end

View File

@ -1,4 +1,4 @@
module Events
module Prices
class AsAdminTest < ActionDispatch::IntegrationTest
setup do
@ -13,19 +13,19 @@ module Events
post '/api/prices/compute',
{
reservation: {
user_id: user.id,
reservable_id: printer_training.id,
reservable_type: printer_training.class.name,
slots_attributes: [
{
availability_id: availability.id,
end_at: availability.end_at,
offered: false,
start_at: availability.start_at
}
]
}
reservation: {
user_id: user.id,
reservable_id: printer_training.id,
reservable_type: printer_training.class.name,
slots_attributes: [
{
availability_id: availability.id,
end_at: availability.end_at,
offered: false,
start_at: availability.start_at
}
]
}
}.to_json,
default_headers
@ -35,7 +35,9 @@ module Events
# Check the price was computed correctly
price = json_response(response.body)
assert_equal (printer_training.trainings_pricings.where(group_id: user.group_id).first.amount / 100.0), price[:price], 'Computed price did not match training price'
assert_equal (printer_training.trainings_pricings.where(group_id: user.group_id).first.amount / 100.0),
price[:price],
'Computed price did not match training price'
end
@ -47,26 +49,26 @@ module Events
post '/api/prices/compute',
{
reservation: {
user_id: user.id,
reservable_id: laser.id,
reservable_type: laser.class.name,
plan_id: plan.id,
slots_attributes: [
{
availability_id: availability.id,
end_at: (availability.start_at + 1.hour).strftime('%Y-%m-%d %H:%M:%S.%9N Z'),
offered: true,
start_at: availability.start_at.strftime('%Y-%m-%d %H:%M:%S.%9N Z')
},
{
availability_id: availability.id,
end_at: (availability.start_at + 2.hour).strftime('%Y-%m-%d %H:%M:%S.%9N Z'),
offered: false,
start_at: (availability.start_at + 1.hour).strftime('%Y-%m-%d %H:%M:%S.%9N Z')
}
]
}
reservation: {
user_id: user.id,
reservable_id: laser.id,
reservable_type: laser.class.name,
plan_id: plan.id,
slots_attributes: [
{
availability_id: availability.id,
end_at: (availability.start_at + 1.hour).strftime('%Y-%m-%d %H:%M:%S.%9N Z'),
offered: true,
start_at: availability.start_at.strftime('%Y-%m-%d %H:%M:%S.%9N Z')
},
{
availability_id: availability.id,
end_at: (availability.start_at + 2.hour).strftime('%Y-%m-%d %H:%M:%S.%9N Z'),
offered: false,
start_at: (availability.start_at + 1.hour).strftime('%Y-%m-%d %H:%M:%S.%9N Z')
}
]
}
}.to_json,
default_headers
@ -76,7 +78,9 @@ module Events
# Check the event was created correctly
price = json_response(response.body)
assert_equal ((laser.prices.where(group_id: user.group_id, plan_id: plan.id).first.amount + plan.amount) / 100.0), price[:price], 'Computed price did not match machine + subscription price'
assert_equal ((laser.prices.where(group_id: user.group_id, plan_id: plan.id).first.amount + plan.amount) / 100.0),
price[:price],
'Computed price did not match machine + subscription price'
end
end
end