mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-01-17 06:52:27 +01:00
Merge branch 'release-2.1.0'
This commit is contained in:
commit
8864863343
10
CHANGELOG.md
Normal file
10
CHANGELOG.md
Normal file
@ -0,0 +1,10 @@
|
||||
# Changelog Fab Manager
|
||||
|
||||
## v2.1.0 2016 mai 2
|
||||
- Add search feature on openlab projects : [Openlab-projects](https://github.com/LaCasemate/openlab-projects)
|
||||
- Add integration tests for main features
|
||||
- Credits logic has been extracted into microservice
|
||||
- Improuve UI list of projects
|
||||
- Refactor interface for SSO profile completion
|
||||
- Change interface for SSO/email already used
|
||||
- Fix bug custom asset favicon-file favicon file is not set
|
13
Gemfile
13
Gemfile
@ -34,12 +34,6 @@ group :development, :test do
|
||||
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
|
||||
gem 'spring'
|
||||
|
||||
gem 'factory_girl_rails'
|
||||
gem 'rspec-rails'
|
||||
gem 'spring-commands-rspec'
|
||||
|
||||
gem 'guard-rspec', require: false
|
||||
|
||||
gem 'railroady'
|
||||
end
|
||||
|
||||
@ -62,6 +56,11 @@ end
|
||||
group :test do
|
||||
gem 'database_cleaner'
|
||||
gem 'faker'
|
||||
gem 'test_after_commit'
|
||||
gem 'minitest-reporters'
|
||||
gem 'webmock'
|
||||
gem 'vcr'
|
||||
gem 'byebug'
|
||||
end
|
||||
|
||||
group :production do
|
||||
@ -138,3 +137,5 @@ gem 'chroma'
|
||||
gem 'protected_attributes'
|
||||
|
||||
gem 'message_format'
|
||||
|
||||
gem 'openlab_ruby'
|
||||
|
97
Gemfile.lock
97
Gemfile.lock
@ -41,6 +41,7 @@ GEM
|
||||
thread_safe (~> 0.3, >= 0.3.4)
|
||||
tzinfo (~> 1.1)
|
||||
addressable (2.3.8)
|
||||
ansi (1.5.0)
|
||||
arel (6.0.3)
|
||||
autoprefixer-rails (5.1.8)
|
||||
execjs
|
||||
@ -58,6 +59,7 @@ GEM
|
||||
sass (>= 3.2.19)
|
||||
buftok (0.2.0)
|
||||
builder (3.2.2)
|
||||
byebug (8.2.3)
|
||||
camertron-eprun (1.1.0)
|
||||
capistrano (2.15.5)
|
||||
highline
|
||||
@ -80,7 +82,6 @@ GEM
|
||||
chroma (0.0.1)
|
||||
chunky_png (1.3.4)
|
||||
cldr-plurals-runtime-rb (1.0.1)
|
||||
coderay (1.1.0)
|
||||
coercible (1.0.0)
|
||||
descendants_tracker (~> 0.0.1)
|
||||
coffee-rails (4.1.0)
|
||||
@ -107,6 +108,8 @@ GEM
|
||||
sass-rails (<= 5.0.1)
|
||||
sprockets (< 2.13)
|
||||
connection_pool (2.2.0)
|
||||
crack (0.4.3)
|
||||
safe_yaml (~> 1.0.0)
|
||||
database_cleaner (1.4.1)
|
||||
debug_inspector (0.0.2)
|
||||
descendants_tracker (0.0.4)
|
||||
@ -120,7 +123,6 @@ GEM
|
||||
warden (~> 1.2.3)
|
||||
devise-async (0.9.0)
|
||||
devise (~> 3.2)
|
||||
diff-lcs (1.2.5)
|
||||
domain_name (0.5.25)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
elasticsearch (1.0.12)
|
||||
@ -146,11 +148,6 @@ GEM
|
||||
equalizer (0.0.11)
|
||||
erubis (2.7.0)
|
||||
execjs (2.4.0)
|
||||
factory_girl (4.5.0)
|
||||
activesupport (>= 3.0.0)
|
||||
factory_girl_rails (4.5.0)
|
||||
factory_girl (~> 4.5.0)
|
||||
railties (>= 3.0.0)
|
||||
faker (1.4.3)
|
||||
i18n (~> 0.5)
|
||||
faraday (0.9.1)
|
||||
@ -163,25 +160,11 @@ GEM
|
||||
foreman (0.78.0)
|
||||
thor (~> 0.19.1)
|
||||
forgery (0.6.0)
|
||||
formatador (0.2.5)
|
||||
friendly_id (5.1.0)
|
||||
activerecord (>= 4.0.0)
|
||||
globalid (0.3.6)
|
||||
activesupport (>= 4.1.0)
|
||||
guard (2.12.5)
|
||||
formatador (>= 0.2.4)
|
||||
listen (~> 2.7)
|
||||
lumberjack (~> 1.0)
|
||||
nenv (~> 0.1)
|
||||
notiffany (~> 0.0)
|
||||
pry (>= 0.9.12)
|
||||
shellany (~> 0.0)
|
||||
thor (>= 0.18.1)
|
||||
guard-compat (1.2.1)
|
||||
guard-rspec (4.5.0)
|
||||
guard (~> 2.1)
|
||||
guard-compat (~> 1.1)
|
||||
rspec (>= 2.99.0, < 4.0)
|
||||
hashdiff (0.3.0)
|
||||
hashie (3.4.2)
|
||||
highline (1.7.1)
|
||||
hike (1.2.3)
|
||||
@ -191,6 +174,9 @@ GEM
|
||||
http-cookie (1.0.2)
|
||||
domain_name (~> 0.5)
|
||||
http_parser.rb (0.6.0)
|
||||
httparty (0.13.7)
|
||||
json (~> 1.8)
|
||||
multi_xml (>= 0.5.2)
|
||||
i18n (0.7.0)
|
||||
ice_nine (0.11.1)
|
||||
jbuilder (2.2.12)
|
||||
@ -211,29 +197,27 @@ GEM
|
||||
letter_opener (1.3.0)
|
||||
launchy (~> 2.2)
|
||||
libv8 (3.16.14.11)
|
||||
listen (2.10.0)
|
||||
celluloid (~> 0.16.0)
|
||||
rb-fsevent (>= 0.9.3)
|
||||
rb-inotify (>= 0.9)
|
||||
loofah (2.0.3)
|
||||
nokogiri (>= 1.5.9)
|
||||
lumberjack (1.0.9)
|
||||
mail (2.6.3)
|
||||
mime-types (>= 1.16, < 3)
|
||||
memoizable (0.4.2)
|
||||
thread_safe (~> 0.3, >= 0.3.1)
|
||||
message_format (0.0.3)
|
||||
twitter_cldr (~> 3.1)
|
||||
method_source (0.8.2)
|
||||
mime-types (2.99)
|
||||
mini_magick (4.2.0)
|
||||
mini_portile (0.6.2)
|
||||
minitest (5.8.3)
|
||||
minitest-reporters (1.1.8)
|
||||
ansi
|
||||
builder
|
||||
minitest (>= 5.0)
|
||||
ruby-progressbar
|
||||
multi_json (1.11.2)
|
||||
multi_xml (0.5.5)
|
||||
multipart-post (2.0.0)
|
||||
naught (1.0.0)
|
||||
nenv (0.2.0)
|
||||
net-scp (1.2.1)
|
||||
net-ssh (>= 2.6.5)
|
||||
net-sftp (2.1.2)
|
||||
@ -244,9 +228,6 @@ GEM
|
||||
netrc (0.10.3)
|
||||
nokogiri (1.6.6.4)
|
||||
mini_portile (~> 0.6.0)
|
||||
notiffany (0.0.6)
|
||||
nenv (~> 0.1)
|
||||
shellany (~> 0.0)
|
||||
notify_with (0.0.2)
|
||||
jbuilder (~> 2.0)
|
||||
rails (>= 4.2.0)
|
||||
@ -264,6 +245,8 @@ GEM
|
||||
omniauth-oauth2 (1.3.1)
|
||||
oauth2 (~> 1.0)
|
||||
omniauth (~> 1.2)
|
||||
openlab_ruby (0.0.3)
|
||||
httparty (~> 0.13)
|
||||
orm_adapter (0.5.0)
|
||||
pdf-core (0.5.1)
|
||||
pg (0.18.1)
|
||||
@ -273,10 +256,6 @@ GEM
|
||||
prawn-table (0.2.1)
|
||||
protected_attributes (1.1.3)
|
||||
activemodel (>= 4.0.1, < 5.0)
|
||||
pry (0.10.1)
|
||||
coderay (~> 1.1.0)
|
||||
method_source (~> 0.8.1)
|
||||
slop (~> 3.4)
|
||||
puma (2.11.1)
|
||||
rack (>= 1.1, < 2.0)
|
||||
pundit (1.0.0)
|
||||
@ -338,31 +317,12 @@ GEM
|
||||
mime-types (>= 1.16, < 3.0)
|
||||
netrc (~> 0.7)
|
||||
rolify (4.0.0)
|
||||
rspec (3.2.0)
|
||||
rspec-core (~> 3.2.0)
|
||||
rspec-expectations (~> 3.2.0)
|
||||
rspec-mocks (~> 3.2.0)
|
||||
rspec-core (3.2.2)
|
||||
rspec-support (~> 3.2.0)
|
||||
rspec-expectations (3.2.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.2.0)
|
||||
rspec-mocks (3.2.1)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.2.0)
|
||||
rspec-rails (3.2.1)
|
||||
actionpack (>= 3.0, < 4.3)
|
||||
activesupport (>= 3.0, < 4.3)
|
||||
railties (>= 3.0, < 4.3)
|
||||
rspec-core (~> 3.2.0)
|
||||
rspec-expectations (~> 3.2.0)
|
||||
rspec-mocks (~> 3.2.0)
|
||||
rspec-support (~> 3.2.0)
|
||||
rspec-support (3.2.2)
|
||||
ruby-progressbar (1.7.5)
|
||||
rufus-scheduler (3.0.9)
|
||||
tzinfo
|
||||
rvm-capistrano (1.5.6)
|
||||
capistrano (~> 2.15.4)
|
||||
safe_yaml (1.0.4)
|
||||
sass (3.4.13)
|
||||
sass-rails (5.0.1)
|
||||
railties (>= 4.0.0, < 5.0)
|
||||
@ -376,7 +336,6 @@ GEM
|
||||
seed_dump (3.2.2)
|
||||
activerecord (~> 4)
|
||||
activesupport (~> 4)
|
||||
shellany (0.0.1)
|
||||
sidekiq (3.3.4)
|
||||
celluloid (>= 0.16.0)
|
||||
connection_pool (>= 2.1.1)
|
||||
@ -392,10 +351,7 @@ GEM
|
||||
rack (~> 1.4)
|
||||
rack-protection (~> 1.4)
|
||||
tilt (>= 1.3, < 3)
|
||||
slop (3.6.0)
|
||||
spring (1.3.5)
|
||||
spring-commands-rspec (1.0.4)
|
||||
spring (>= 0.9.1)
|
||||
sprockets (2.12.4)
|
||||
hike (~> 1.2)
|
||||
multi_json (~> 1.0)
|
||||
@ -408,6 +364,8 @@ GEM
|
||||
stripe (1.30.2)
|
||||
json (~> 1.8.1)
|
||||
rest-client (~> 1.4)
|
||||
test_after_commit (1.0.0)
|
||||
activerecord (>= 3.2)
|
||||
therubyracer (0.12.0)
|
||||
libv8 (~> 3.16.14.0)
|
||||
ref
|
||||
@ -447,6 +405,7 @@ GEM
|
||||
kgio (~> 2.6)
|
||||
rack
|
||||
raindrops (~> 0.7)
|
||||
vcr (3.0.1)
|
||||
virtus (1.0.5)
|
||||
axiom-types (~> 0.1)
|
||||
coercible (~> 1.0)
|
||||
@ -459,6 +418,10 @@ GEM
|
||||
binding_of_caller (>= 0.7.2)
|
||||
railties (>= 4.0)
|
||||
sprockets-rails (>= 2.0, < 4.0)
|
||||
webmock (1.24.2)
|
||||
addressable (>= 2.3.6)
|
||||
crack (>= 0.3.2)
|
||||
hashdiff
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
@ -469,6 +432,7 @@ DEPENDENCIES
|
||||
active_record_query_trace
|
||||
awesome_print
|
||||
bootstrap-sass
|
||||
byebug
|
||||
capistrano
|
||||
capistrano-maintenance (= 0.0.5)
|
||||
capistrano-sidekiq
|
||||
@ -482,24 +446,24 @@ DEPENDENCIES
|
||||
elasticsearch-model
|
||||
elasticsearch-persistence
|
||||
elasticsearch-rails
|
||||
factory_girl_rails
|
||||
faker
|
||||
figaro
|
||||
font-awesome-rails
|
||||
foreman
|
||||
forgery
|
||||
friendly_id (~> 5.1.0)
|
||||
guard-rspec
|
||||
jbuilder (~> 2.0)
|
||||
jquery-rails
|
||||
kaminari
|
||||
letter_opener
|
||||
message_format
|
||||
mini_magick
|
||||
minitest-reporters
|
||||
notify_with
|
||||
oj
|
||||
omniauth
|
||||
omniauth-oauth2
|
||||
openlab_ruby
|
||||
pg
|
||||
prawn
|
||||
prawn-table
|
||||
@ -513,7 +477,6 @@ DEPENDENCIES
|
||||
recurrence
|
||||
responders (~> 2.0)
|
||||
rolify
|
||||
rspec-rails
|
||||
rvm-capistrano
|
||||
sass-rails (= 5.0.1)
|
||||
sdoc (~> 0.4.0)
|
||||
@ -522,14 +485,16 @@ DEPENDENCIES
|
||||
sidekiq-cron
|
||||
sinatra
|
||||
spring
|
||||
spring-commands-rspec
|
||||
stripe (= 1.30.2)
|
||||
test_after_commit
|
||||
therubyracer (= 0.12.0)
|
||||
twitter
|
||||
twitter-text
|
||||
uglifier (>= 1.3.0)
|
||||
unicorn
|
||||
vcr
|
||||
web-console (~> 2.0)
|
||||
webmock
|
||||
|
||||
BUNDLED WITH
|
||||
1.10.6
|
||||
1.11.2
|
||||
|
22
README.md
22
README.md
@ -530,7 +530,7 @@ After modifying any values concerning the localisation, restart the application
|
||||
## Known issues
|
||||
|
||||
- When browsing a machine page, you may encounter an "InterceptError" in the console and the loading bar will stop loading before reaching its ending.
|
||||
This may append if the machine was created through a seed file without any image.
|
||||
This may happen if the machine was created through a seed file without any image.
|
||||
To solve this, simply add an image to the machine's profile and refresh the web page.
|
||||
|
||||
- When starting the Ruby on Rails server (eg. `foreman s`) you may receive the following error:
|
||||
@ -539,12 +539,30 @@ After modifying any values concerning the localisation, restart the application
|
||||
web.1 | Exiting
|
||||
worker.1 | ...lib/redis/client.rb...:in `_parse_options'
|
||||
|
||||
This may happens when the `application.yml` file is missing.
|
||||
This may happen when the `application.yml` file is missing.
|
||||
To solve this issue copy `config/application.yml.default` to `config/application.yml`.
|
||||
This is required before the first start.
|
||||
|
||||
- Due to a stripe limitation, you won't be ble to create plans longer than one year.
|
||||
|
||||
- When running the tests suite with `rake test`, all tests may fail with errors similar to the following:
|
||||
|
||||
Error:
|
||||
...
|
||||
ActiveRecord::InvalidForeignKey: PG::ForeignKeyViolation: ERROR: insert or update on table "..." violates foreign key constraint "fk_rails_..."
|
||||
DETAIL: Key (group_id)=(1) is not present in table "groups".
|
||||
: ...
|
||||
test_after_commit (1.0.0) lib/test_after_commit/database_statements.rb:11:in `block in transaction'
|
||||
test_after_commit (1.0.0) lib/test_after_commit/database_statements.rb:5:in `transaction'
|
||||
|
||||
This is due to an ActiveRecord behavior witch disable referential integrity in PostgreSQL to load the fixtures.
|
||||
PostgreSQL will prevent any users to disable referential integrity on the fly if they doesn't have the `SUPERUSER` role.
|
||||
To fix that, logon as the `postgres` user and run the PostgreSQL shell (see [Setup the FabManager database in PostgreSQL](#setup-fabmanager-in-postgresql) for an example).
|
||||
Then, run the following command (replace `sleede` with your test database user, as specified in your database.yml):
|
||||
|
||||
ALTER ROLE sleede WITH SUPERUSER;
|
||||
|
||||
DO NOT do this in a production environment, as this would lead to a serious security issue.
|
||||
|
||||
<a name="related-documentation"></a>
|
||||
## Related Documentation
|
||||
|
@ -1,8 +1,8 @@
|
||||
|
||||
'use strict'
|
||||
|
||||
Application.Controllers.controller "CompleteProfileController", ["$scope", "$rootScope", "$state", "_t", "growl", "CSRF", "Auth", "Member", "settingsPromise", "activeProviderPromise", "groupsPromise", "cguFile", "memberPromise"
|
||||
, ($scope, $rootScope, $state, _t, growl, CSRF, Auth, Member, settingsPromise, activeProviderPromise, groupsPromise, cguFile, memberPromise) ->
|
||||
Application.Controllers.controller "CompleteProfileController", ["$scope", "$rootScope", "$state", "_t", "growl", "CSRF", "Auth", "Member", "settingsPromise", "activeProviderPromise", "groupsPromise", "cguFile", "memberPromise", "Session"
|
||||
, ($scope, $rootScope, $state, _t, growl, CSRF, Auth, Member, settingsPromise, activeProviderPromise, groupsPromise, cguFile, memberPromise, Session) ->
|
||||
|
||||
|
||||
|
||||
@ -83,6 +83,8 @@ Application.Controllers.controller "CompleteProfileController", ["$scope", "$roo
|
||||
$rootScope.currentUser = content
|
||||
$state.go('app.public.home')
|
||||
|
||||
|
||||
|
||||
##
|
||||
# For use with 'ng-class', returns the CSS class name for the uploads previews.
|
||||
# The preview may show a placeholder or the content of the file depending on the upload state.
|
||||
@ -112,6 +114,8 @@ Application.Controllers.controller "CompleteProfileController", ["$scope", "$roo
|
||||
growl.error(_t('an_unexpected_error_occurred_check_your_authentication_code'))
|
||||
console.error(err)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Return the email given by the SSO provider, parsed if needed
|
||||
# @return {String} E-mail of the current user
|
||||
@ -126,6 +130,30 @@ Application.Controllers.controller "CompleteProfileController", ["$scope", "$roo
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Test if the user's mail is marked as duplicate
|
||||
# @return {boolean}
|
||||
##
|
||||
$scope.hasDuplicate = ->
|
||||
email = memberPromise.email
|
||||
if email
|
||||
return !(email.match(/^<([^>]+)>.{20}-duplicate$/) == null)
|
||||
|
||||
|
||||
|
||||
##
|
||||
# Disconnect and re-connect the user to the SSO to force the synchronisation of the profile's data
|
||||
##
|
||||
$scope.syncProfile = ->
|
||||
Auth.logout().then (oldUser) ->
|
||||
Session.destroy()
|
||||
$rootScope.currentUser = null
|
||||
$rootScope.toCheckNotifications = false
|
||||
$scope.notifications = []
|
||||
$window.location.href = activeProviderPromise.link_to_sso_connect
|
||||
|
||||
|
||||
|
||||
### PRIVATE SCOPE ###
|
||||
|
||||
|
||||
|
@ -144,16 +144,18 @@ class ProjectsController
|
||||
##
|
||||
# Controller used on projects listing page
|
||||
##
|
||||
Application.Controllers.controller "ProjectsController", ["$scope", "$state", 'Project', 'machinesPromise', 'themesPromise', 'componentsPromise'
|
||||
, ($scope, $state, Project, machinesPromise, themesPromise, componentsPromise) ->
|
||||
Application.Controllers.controller "ProjectsController", ["$scope", "$state", 'Project', 'machinesPromise', 'themesPromise', 'componentsPromise', 'paginationService', 'OpenlabProject', '$window', 'growl', '_t', '$location', '$timeout'
|
||||
, ($scope, $state, Project, machinesPromise, themesPromise, componentsPromise, paginationService, OpenlabProject, $window, growl, _t, $location, $timeout) ->
|
||||
|
||||
### PRIVATE STATIC CONSTANTS ###
|
||||
|
||||
# Number of notifications added to the page when the user clicks on 'load next notifications'
|
||||
PROJECTS_PER_PAGE = 12
|
||||
# Number of projects added to the page when the user clicks on 'load more projects'
|
||||
PROJECTS_PER_PAGE = 16
|
||||
|
||||
$scope.openlabAppId = Fablab.openlabAppId
|
||||
|
||||
### PUBLIC SCOPE ###
|
||||
$scope.search = { q: "", from: undefined, machine_id: undefined, component_id: undefined, theme_id: undefined }
|
||||
$scope.search = { q: ($location.$$search.q || ""), from: ($location.$$search.from || undefined), machine_id: (parseInt($location.$$search.machine_id) || undefined), component_id: (parseInt($location.$$search.component_id) || undefined), theme_id: (parseInt($location.$$search.theme_id) || undefined) }
|
||||
|
||||
## list of projects to display
|
||||
$scope.projects = []
|
||||
@ -167,47 +169,101 @@ Application.Controllers.controller "ProjectsController", ["$scope", "$state", 'P
|
||||
## list of components / used for filtering
|
||||
$scope.components = componentsPromise
|
||||
|
||||
## By default, the pagination mode is activated to limit the page size
|
||||
$scope.paginateActive = true
|
||||
$scope.openlab = {}
|
||||
$scope.openlab.projectsActive = Fablab.openlabProjectsActive
|
||||
|
||||
## The currently displayed page number
|
||||
$scope.page = 1
|
||||
if $location.$$search.whole_network is 'f'
|
||||
$scope.openlab.searchOverWholeNetwork = false
|
||||
else
|
||||
$scope.openlab.searchOverWholeNetwork = $scope.openlab.projectsActive || false
|
||||
|
||||
$scope.resetFilters = ->
|
||||
normalizeProjectsAttrs = (projects)->
|
||||
projects.map((project)->
|
||||
project.project_image = project.image_url
|
||||
return project
|
||||
)
|
||||
|
||||
$scope.searchOverWholeNetworkChanged = ->
|
||||
setTimeout ->
|
||||
$scope.resetFiltersAndTriggerSearch()
|
||||
, 150
|
||||
|
||||
loadMoreCallback = (projectsPromise)->
|
||||
$scope.projects = $scope.projects.concat(projectsPromise.projects)
|
||||
updateUrlParam('page', $scope.projectsPagination.currentPage)
|
||||
|
||||
loadMoreOpenlabCallback = (projectsPromise)->
|
||||
$scope.projects = $scope.projects.concat(normalizeProjectsAttrs(projectsPromise.projects))
|
||||
updateUrlParam('page', $scope.projectsPagination.currentPage)
|
||||
|
||||
$scope.loadMore = ->
|
||||
if $scope.openlab.searchOverWholeNetwork is true
|
||||
$scope.projectsPagination.loadMore(q: $scope.search.q)
|
||||
else
|
||||
$scope.projectsPagination.loadMore(search: $scope.search)
|
||||
|
||||
|
||||
$scope.resetFiltersAndTriggerSearch = ->
|
||||
$scope.search.q = ""
|
||||
$scope.search.from = undefined
|
||||
$scope.search.machine_id = undefined
|
||||
$scope.search.component_id = undefined
|
||||
$scope.search.theme_id = undefined
|
||||
$scope.setUrlQueryParams($scope.search)
|
||||
$scope.triggerSearch()
|
||||
|
||||
$scope.triggerSearch = ->
|
||||
Project.search { search: $scope.search, page: 1 }, (projects)->
|
||||
$scope.projects = projects
|
||||
if projects.length < PROJECTS_PER_PAGE
|
||||
$scope.paginateActive = false
|
||||
currentPage = parseInt($location.$$search.page) || 1
|
||||
if $scope.openlab.searchOverWholeNetwork is true
|
||||
updateUrlParam('whole_network', 't')
|
||||
$scope.projectsPagination = new paginationService.Instance(OpenlabProject, currentPage, PROJECTS_PER_PAGE, null, { }, loadMoreOpenlabCallback)
|
||||
OpenlabProject.query { q: $scope.search.q, page: currentPage, per_page: PROJECTS_PER_PAGE }, (projectsPromise)->
|
||||
if projectsPromise.errors?
|
||||
growl.error(_t('openlab_search_not_available_at_the_moment'))
|
||||
$scope.openlab.searchOverWholeNetwork = false
|
||||
$scope.triggerSearch()
|
||||
else
|
||||
$scope.paginateActive = true
|
||||
$scope.page = 2
|
||||
|
||||
$scope.loadMoreProjects = ->
|
||||
# Project.query {page: $scope.page}, (projects) ->
|
||||
# $scope.projects = $scope.projects.concat projects
|
||||
# $scope.paginateActive = false if projects.length < PROJECTS_PER_PAGE
|
||||
Project.search { search: $scope.search, page: $scope.page }, (projects)->
|
||||
$scope.projects = $scope.projects.concat projects
|
||||
$scope.paginateActive = false if projects.length < PROJECTS_PER_PAGE
|
||||
$scope.page += 1
|
||||
$scope.projectsPagination.totalCount = projectsPromise.meta.total
|
||||
$scope.projects = normalizeProjectsAttrs(projectsPromise.projects)
|
||||
|
||||
else
|
||||
updateUrlParam('whole_network', 'f')
|
||||
$scope.projectsPagination = new paginationService.Instance(Project, currentPage, PROJECTS_PER_PAGE, null, { }, loadMoreCallback, 'search')
|
||||
Project.search { search: $scope.search, page: currentPage, per_page: PROJECTS_PER_PAGE }, (projectsPromise)->
|
||||
$scope.projectsPagination.totalCount = projectsPromise.meta.total
|
||||
$scope.projects = projectsPromise.projects
|
||||
|
||||
##
|
||||
# Callback to switch the user's view to the detailled project page
|
||||
# @param project {{slug:string}} The project to display
|
||||
##
|
||||
$scope.showProject = (project) ->
|
||||
if ($scope.openlab.searchOverWholeNetwork is true) and (project.app_id isnt Fablab.openlabAppId)
|
||||
$window.open(project.project_url, '_blank')
|
||||
return true
|
||||
else
|
||||
$state.go('app.public.projects_show', {id: project.slug})
|
||||
|
||||
##
|
||||
# function to set all url query search parameters from search object
|
||||
##
|
||||
$scope.setUrlQueryParams = (search)->
|
||||
updateUrlParam('page', 1)
|
||||
updateUrlParam('q', search.q)
|
||||
updateUrlParam('from', search.from)
|
||||
updateUrlParam('theme_id', search.theme_id)
|
||||
updateUrlParam('component_id', search.component_id)
|
||||
updateUrlParam('machine_id', search.machine_id)
|
||||
|
||||
##
|
||||
# function to update url query param, little hack to turn off reloadOnSearch and re-enable it after setting the params
|
||||
# params example: 'q' , 'presse-purée'
|
||||
##
|
||||
updateUrlParam = (name, value) ->
|
||||
$state.current.reloadOnSearch = false
|
||||
$location.search(name, value)
|
||||
$timeout ->
|
||||
$state.current.reloadOnSearch = undefined
|
||||
|
||||
## initialization
|
||||
$scope.triggerSearch()
|
||||
|
@ -219,7 +219,7 @@ angular.module('application.router', ['ui.router']).
|
||||
|
||||
# projects
|
||||
.state 'app.public.projects_list',
|
||||
url: '/projects'
|
||||
url: '/projects?q&page&theme_id&component_id&machine_id&from&whole_network'
|
||||
views:
|
||||
'main@':
|
||||
templateUrl: '<%= asset_path "projects/index.html" %>'
|
||||
|
9
app/assets/javascripts/services/openlab_project.coffee
Normal file
9
app/assets/javascripts/services/openlab_project.coffee
Normal file
@ -0,0 +1,9 @@
|
||||
'use strict'
|
||||
|
||||
Application.Services.factory 'OpenlabProject', ["$resource", ($resource)->
|
||||
$resource "/api/openlab_projects/:id",
|
||||
{id: "@id"},
|
||||
query:
|
||||
method: 'GET'
|
||||
isArray: false
|
||||
]
|
50
app/assets/javascripts/services/pagination_service.coffee
Normal file
50
app/assets/javascripts/services/pagination_service.coffee
Normal file
@ -0,0 +1,50 @@
|
||||
'use strict'
|
||||
|
||||
Application.Services.factory("paginationService", [->
|
||||
helpers = {}
|
||||
|
||||
helpers.pageCount = (totalCount, perPage)->
|
||||
Math.ceil(totalCount/perPage)
|
||||
|
||||
helpers.hasNextPage = (currentPage, totalCount, perPage)->
|
||||
_pageCount = helpers.pageCount(totalCount, perPage)
|
||||
(_pageCount != currentPage) and (_pageCount != 0)
|
||||
|
||||
Instance = (resourceService, currentPage, perPage, totalCount, defaultQueryParams, callback, functionName)->
|
||||
@resourceService = resourceService
|
||||
@currentPage = currentPage
|
||||
@perPage = perPage
|
||||
@totalCount = totalCount
|
||||
@defaultQueryParams = defaultQueryParams
|
||||
@callback = callback
|
||||
@functionName = functionName || 'query'
|
||||
@loading = false
|
||||
|
||||
@pageCount = ->
|
||||
helpers.pageCount(@totalCount, @perPage)
|
||||
|
||||
@hasNextPage = ->
|
||||
helpers.hasNextPage(@currentPage, @totalCount, @perPage)
|
||||
|
||||
@loadMore = (queryParams)->
|
||||
@currentPage += 1
|
||||
@loading = true
|
||||
|
||||
_queryParams = { page: @currentPage, per_page: @perPage }
|
||||
|
||||
if queryParams
|
||||
for k,v of queryParams
|
||||
_queryParams[k] = v
|
||||
|
||||
for k,v of @defaultQueryParams
|
||||
_queryParams[k] = v
|
||||
|
||||
@resourceService[@functionName](_queryParams, (dataPromise)=>
|
||||
@callback(dataPromise)
|
||||
@loading = false
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
return { Instance: Instance }
|
||||
])
|
@ -10,5 +10,5 @@ Application.Services.factory 'Project', ["$resource", ($resource)->
|
||||
search:
|
||||
method: 'GET'
|
||||
url: '/api/projects/search'
|
||||
isArray: true
|
||||
isArray: false
|
||||
]
|
||||
|
@ -65,49 +65,73 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.box-thumb {
|
||||
opacity: 0.9;
|
||||
&:hover { opacity: 1;}
|
||||
&:hover .box-footer { opacity: 1; }
|
||||
// component card
|
||||
.card {
|
||||
position: relative;
|
||||
display: block;
|
||||
margin-bottom: .75rem;
|
||||
background-color: #fff;
|
||||
border: 1px solid #e5e5e5;
|
||||
border-radius: .25rem;
|
||||
height: 325px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 3rem;
|
||||
cursor: pointer;
|
||||
|
||||
.card-overlay {
|
||||
margin: 1.25rem;
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
z-index: 2;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: rgba(43,46,56,0.8);
|
||||
text-align: center;
|
||||
opacity: 0;
|
||||
-webkit-transition: all 0.1s ease-out;
|
||||
transition: all 0.1s ease-out;
|
||||
height: 180px;
|
||||
cursor: pointer;
|
||||
.btn-group { margin-top: 70px;}
|
||||
.btn {
|
||||
background: rgba(255,255,255,0.1);
|
||||
border: 1px solid #fff;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.card-overlay {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.card-header {
|
||||
width: 100%;
|
||||
display: block;
|
||||
padding: 1.25rem;
|
||||
.card-header-bg {
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 30px;
|
||||
cursor: pointer;
|
||||
// todo
|
||||
overflow: hidden;
|
||||
height: 280px;
|
||||
img {
|
||||
opacity: 0.9;
|
||||
|
||||
background-position: center;
|
||||
height: 180px;
|
||||
}
|
||||
.project-caption {
|
||||
text-shadow: rgba(29, 29, 29, 0.5) 0 -1px, rgba(29, 29, 29, 0.5) -1px 0,
|
||||
rgba(29, 29, 29, 0.5) 1px 0, rgba(29, 29, 29, 0.5) 0 1px;
|
||||
}
|
||||
.box-content {
|
||||
position: absolute;
|
||||
top: 45px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
h1 {
|
||||
padding: 0 20px;
|
||||
color: white;
|
||||
text-transform: uppercase;
|
||||
font-size: rem-calc(24);
|
||||
font-weight: 900;
|
||||
line-height: rem-calc(30);
|
||||
.card-block {
|
||||
padding: 0 1.25rem 1.25rem 1.25rem;
|
||||
.card-title {
|
||||
font-size: 1.5rem;
|
||||
line-height: 2rem;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
margin-top: 5px;
|
||||
}
|
||||
.card-meta {
|
||||
font-size: 1.2rem;
|
||||
margin: 0;
|
||||
text-align: center
|
||||
}
|
||||
.box-footer {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
@ -464,3 +488,17 @@ padding: 10px;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.disabling-overlay {
|
||||
&:before {
|
||||
content: "";
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: rgba(230, 230, 230, 0.7);
|
||||
position: absolute;
|
||||
z-index:10;
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
@ -33,6 +33,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
.m-t-xxl-on-md { margin-top: 60px; }
|
||||
.m-t-3xl-on-md { margin-top: 80px; }
|
||||
.m-t-4xl-on-md { margin-top: 100px; }
|
||||
}
|
||||
|
||||
|
||||
|
@ -17,12 +17,28 @@ p, .widget p {
|
||||
&.fleche-left {
|
||||
position: relative;
|
||||
padding-left: 5px;
|
||||
span.or {
|
||||
border: 2px solid $yellow;
|
||||
border-radius: 50%;
|
||||
padding: 10px 17px 10px 17px;
|
||||
}
|
||||
}
|
||||
img.fleche-left {
|
||||
position: absolute;
|
||||
left: -35px;
|
||||
top: 15px;
|
||||
}
|
||||
img.fleche-right {
|
||||
position: absolute;
|
||||
right: -35px;
|
||||
top: 15px;
|
||||
}
|
||||
img.fleche-left-from-top {
|
||||
transform: rotate(90deg) scaleX(-1);
|
||||
}
|
||||
img.fleche-right-from-top {
|
||||
transform: rotate(270deg)
|
||||
}
|
||||
}
|
||||
|
||||
.pos-rlt{position: relative;}
|
||||
@ -82,6 +98,8 @@ p, .widget p {
|
||||
.text-u-l, .underline {text-decoration: underline;}
|
||||
.text-c { text-transform: capitalize; }
|
||||
|
||||
.text-italic { font-style: italic; }
|
||||
|
||||
.text-center { text-align: center; }
|
||||
|
||||
.text-active, .active > .text, .active > .auto .text{display: none !important;}
|
||||
|
@ -1,10 +1,11 @@
|
||||
<section class="panel panel-default bg-token m-lg">
|
||||
<div class="panel-body m-r">
|
||||
<h3 translate>{{ 'do_you_already_have_an_account' }}</h3>
|
||||
<p translate>{{ 'do_not_fill_the_form_below_but_specify_here_the_code_you_ve_received_by_email_to_recover_your_access' }}</p>
|
||||
<p ng-hide="hasDuplicate()" translate>{{ 'do_not_fill_the_form_beside_but_specify_here_the_code_you_ve_received_by_email_to_recover_your_access' }}</p>
|
||||
<p ng-show="hasDuplicate()" translate>{{ 'just_specify_code_here_to_recover_access' }}</p>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 col-sm-offset-1"></div>
|
||||
<div class="col-sm-offset-1 col-sm-6">
|
||||
<div class="col-lg-3 col-lg-offset-1 hidden-md col-sm-3 col-sm-offset-1"></div>
|
||||
<div class="col-lg-offset-1 col-lg-6 col-md-12 col-sm-offset-1 col-sm-6">
|
||||
<div class="form-group" ng-class="{'has-error': userForm['user[auth_token]'].$dirty && userForm['user[auth_token]'].$invalid}">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-key"></i></span>
|
||||
|
@ -19,36 +19,47 @@
|
||||
</section>
|
||||
|
||||
|
||||
<div class="row no-gutter ">
|
||||
<div class="row no-gutter">
|
||||
<div class="col-sm-12 col-md-12 b-r">
|
||||
<div class="col-md-8 m-t-md">
|
||||
<div class="row">
|
||||
<div class="col-md-offset-2 col-md-8 m-t-md">
|
||||
<section class="panel panel-default bg-light m-lg">
|
||||
<div class="panel-body m-r">
|
||||
{{ 'you_ve_just_created_a_new_account_on_the_fablab_by_logging_from' | translate:{ GENDER: nameGenre, NAME: fablabName }:"messageformat" }}<br/>
|
||||
<img class="m-l v-middle" height="16" width="16" src='https://www.google.com/s2/favicons?domain={{activeProvider.domain}}' />
|
||||
<strong class="v-middle">{{activeProvider.name}} <span ng-if="ssoEmail()">({{ssoEmail()}})</span></strong><br/>
|
||||
<p class="m-t-md">{{ 'before_letting_you_use_the_application_we_need_some_more_details' | translate }}.<br/>
|
||||
{{ 'please_fill_the_following_form' | translate }}.
|
||||
{{ 'some_data_may_have_already_been_provided_by_provider_and_cannot_be_modified' | translate:{NAME:activeProvider.name} }}.
|
||||
{{ 'then_click_on_' | translate }} <strong translate>{{ 'confirm_changes' }}</strong> {{ '_to_start_using_the_application' | translate }}.</p>
|
||||
<p class="m-t-md" ng-hide="hasDuplicate()" translate>{{ 'before_letting_you_use_the_application_we_need_some_more_details' }}.</p>
|
||||
<p class="m-t-md" ng-show="hasDuplicate()" translate>{{ 'your_email_is_already_used_by_another_account_on_the_platform' }}</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="col-md-8" ng-hide="user.merged_at">
|
||||
<ng-include src="'<%= asset_path 'profile/_token.html' %>'"></ng-include>
|
||||
</div>
|
||||
|
||||
<div class="row col-md-2 col-md-offset-5 hidden-sm hidden-xs">
|
||||
<p class="font-felt fleche-left text-lg upper text-center">
|
||||
<%= image_tag("fleche-left.png", class: 'fleche-left visible-lg visible-md fleche-left-from-top') %>
|
||||
<span class="or" translate>{{ 'or' }}</span>
|
||||
<%= image_tag("fleche-left.png", class: 'fleche-right visible-lg visible-md fleche-right-from-top') %>
|
||||
</p>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="m-lg panel panel-default bg-light pos-rlt" ng-hide="hasDuplicate()">
|
||||
<div ng-class="{'disabling-overlay' : !!user.auth_token}">
|
||||
<div class="panel-body">
|
||||
<h3 translate>{{ 'new_on_this_platform' }}</h3>
|
||||
<p translate>{{ 'please_fill_the_following_form'}}.</p>
|
||||
<p class="text-italic">{{ 'some_data_may_have_already_been_provided_by_provider_and_cannot_be_modified' | translate:{NAME:activeProvider.name} }}.<br/>
|
||||
{{ 'then_click_on_' | translate }} <strong translate>{{ 'confirm_changes' }}</strong> {{ '_to_start_using_the_application' | translate }}.</p>
|
||||
</div>
|
||||
<form role="form"
|
||||
name="userForm"
|
||||
class="form-horizontal col-md-8 m-t"
|
||||
class="form-horizontal"
|
||||
action="{{ actionUrl }}"
|
||||
ng-upload="submited(content)"
|
||||
upload-options-enable-rails-csrf="true"
|
||||
ng-if="!user.auth_token"
|
||||
novalidate>
|
||||
|
||||
<section class="panel panel-default bg-light m-lg m-t-xs">
|
||||
<section>
|
||||
|
||||
<div class="panel-body m-r">
|
||||
<!-- common fields -->
|
||||
@ -99,7 +110,42 @@
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<section class="m-lg panel panel-default bg-light pos-rlt" ng-show="hasDuplicate()">
|
||||
<div ng-class="{'disabling-overlay' : !!user.auth_token}">
|
||||
<div class="panel-body">
|
||||
<h3 translate>{{ 'new_on_this_platform' }}</h3>
|
||||
<p class="text-italic">
|
||||
{{ 'your_email_' | translate }} <strong>({{ssoEmail()}})</strong> {{ '_is_currently_associated_with_another_account_on_this_platform' | translate }}
|
||||
{{ 'please_click_to_change_email_associated_with_your_PROVIDER_account' | translate:{PROVDER: activeProvider.name} }}
|
||||
</p>
|
||||
<div class="row">
|
||||
<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
|
||||
<a class="btn btn-default" ng-href="{{activeProvider.link_to_sso_profile}}" target="_blank">
|
||||
<i class="fa fa-edit"></i> {{ 'change_my_data' | translate }}
|
||||
</a>
|
||||
<p class="text-italic">{{ 'once_your_data_are_up_to_date_' | translate }} <strong translate>{{ '_click_on_the_synchronization_button_opposite_' }}</strong> {{ 'or' | translate}} <strong translate>{{ '_disconnect_then_reconnect_' }}</strong> {{ '_for_your_changes_to_take_effect' | translate }}</p>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
|
||||
<a class="btn btn-default" ng-click="syncProfile()">
|
||||
<i class="fa fa-refresh"></i> {{ 'sync_my_profile' | translate }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div class="row col-xs-2 col-xs-offset-5 hidden-md hidden-lg">
|
||||
<p class="font-felt fleche-left text-lg upper text-center">
|
||||
<span class="or" translate>{{ 'or' }}</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-md-6 m-t-3xl-on-md" ng-hide="user.merged_at">
|
||||
<ng-include src="'<%= asset_path 'profile/_token.html' %>'"></ng-include>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -22,25 +22,37 @@
|
||||
|
||||
<section class="m-lg">
|
||||
<div class="row m-b-md">
|
||||
<div class="col-md-12"><h3 class="m-t-xs">{{ 'filter_projects' | translate }}
|
||||
<a href="" class="text-sm pull-right" name="button" ng-click="resetFilters()"><i class="fa fa-refresh"></i> {{ 'reset_all_filters' | translate }}</a></h3>
|
||||
</div>
|
||||
<div class="col-md-12 m-b">
|
||||
<form class="form-inline">
|
||||
<a href="javascript:void(0);" class="text-sm pull-right" name="button" ng-click="resetFiltersAndTriggerSearch()" ng-show="!openlab.searchOverWholeNetwork"><i class="fa fa-refresh"></i> {{ 'reset_all_filters' | translate }}</a>
|
||||
|
||||
<span ng-if="openlab.projectsActive" uib-tooltip="{{ 'tooltip_openlab_projects_switch' | translate }}" tooltip-trigger="mouseenter">
|
||||
<label for="searchOverWholeNetwork" class="control-label m-r text-sm" translate>{{ 'search_over_the_whole_network' }}</label>
|
||||
<input bs-switch
|
||||
ng-model="openlab.searchOverWholeNetwork"
|
||||
type="checkbox"
|
||||
class="form-control"
|
||||
switch-on-text="{{ 'yes' | translate }}"
|
||||
switch-off-text="{{ 'no' | translate }}"
|
||||
switch-animate="true"
|
||||
ng-change="searchOverWholeNetworkChanged()"
|
||||
/>
|
||||
{{ searchOverWholeNetwork }}
|
||||
</span>
|
||||
<form class="form-inline m-t text-center" role="form" ng-submit="setUrlQueryParams(search) && triggerSearch()">
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon"><i class="fa fa-search"></i></div>
|
||||
<input type="search" class="form-control" placeholder="Mots-clés" ng-model="search.q"/>
|
||||
<div class="input-group-btn">
|
||||
<button ng-click="triggerSearch()" type="button" class="btn btn-warning" translate>{{ 'search' }}</button>
|
||||
<button type="submit" class="btn btn-warning" translate>{{ 'search' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<span ng-if="!openlab.searchOverWholeNetwork">
|
||||
<div class="col-md-3 m-b" ng-show="isAuthenticated()">
|
||||
<select ng-model="search.from" ng-change="triggerSearch()" class="form-control">
|
||||
<select ng-model="search.from" ng-change="setUrlQueryParams(search) && triggerSearch()" class="form-control">
|
||||
<option value="" translate>{{ 'all_projects' }}</option>
|
||||
<option value="mine" translate>{{ 'my_projects' }}</option>
|
||||
<option value="collaboration" translate>{{ 'projects_to_whom_i_take_part_in' }}</option>
|
||||
@ -48,51 +60,62 @@
|
||||
</div>
|
||||
|
||||
<div class="col-md-3 m-b">
|
||||
<select ng-model="search.machine_id" ng-change="triggerSearch()" class="form-control" ng-options="m.id as m.name for m in machines">
|
||||
<select ng-model="search.machine_id" ng-change="setUrlQueryParams(search) && triggerSearch()" class="form-control" ng-options="m.id as m.name for m in machines">
|
||||
<option value="" translate>{{ 'all_machines' }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3 m-b">
|
||||
<select ng-model="search.theme_id" ng-change="triggerSearch()" class="form-control" ng-options="t.id as t.name for t in themes">
|
||||
<select ng-model="search.theme_id" ng-change="setUrlQueryParams(search) && triggerSearch()" class="form-control" ng-options="t.id as t.name for t in themes">
|
||||
<option value="" translate>{{ 'all_themes' }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3 m-b">
|
||||
<select ng-model="search.component_id" ng-change="triggerSearch()" class="form-control" ng-options="t.id as t.name for t in components">
|
||||
<select ng-model="search.component_id" ng-change="setUrlQueryParams(search) && triggerSearch()" class="form-control" ng-options="t.id as t.name for t in components">
|
||||
<option value="" translate>{{ 'all_materials' }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-sm-6 col-md-4" ng-repeat="project in projects" ng-click="showProject(project)">
|
||||
<div class="box-thumb box-thumb-project" style="background-image: url({{project.project_image}});">
|
||||
<span class="col-md-12" ng-show="projects && (projects.length == 0)"> {{ 'project_search_result_is_empty' | translate }} </span>
|
||||
<div class="col-xs-12 col-sm-6 col-md-3" ng-repeat="project in projects" ng-click="showProject(project)">
|
||||
|
||||
<div class="card card-project">
|
||||
|
||||
<div class="card-header">
|
||||
<div class="card-header-bg" style="background-image: url({{project.project_image}});">
|
||||
<img src="data:image/png;base64," data-src="holder.js/100%x100%/text:/font:FontAwesome/icon" bs-holder ng-if="!project.project_image">
|
||||
</div>
|
||||
|
||||
<div class="box-content project-caption">
|
||||
<h1>{{project.name}}</h1>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
|
||||
<div class="card-block">
|
||||
<h3 class="card-meta" ng-if="openlab.searchOverWholeNetwork && project.app_id != openlabAppId"><i class="fa fa-tag"></i> {{ project.app_name }}</h3>
|
||||
<h1 class="card-title">{{project.name}}</h1>
|
||||
</div>
|
||||
|
||||
<div class="card-overlay">
|
||||
<div class="btn-group">
|
||||
<div class="btn btn-default" ui-sref="app.logged.projects_edit({id:project.id})" ng-if="projectEditableBy(currentUser) || isAuthorized('admin')">
|
||||
<i class="fa fa-edit"></i> {{ 'edit' | translate }}
|
||||
</div>
|
||||
<div class="btn btn-default" ng-click="showProject(project)">
|
||||
<i class="fa fa-eye"></i> {{ 'consult' | translate }}
|
||||
<i ng-class="{'fa fa-external-link' : (openlab.searchOverWholeNetwork && project.app_id != openlabAppId) }"></i> {{ 'consult' | translate }}
|
||||
</div>
|
||||
<div class="btn btn-default" ui-sref="app.logged.projects_edit({id:project.id})" ng-if="isAuthorized('admin') && !openlab.searchOverWholeNetwork">
|
||||
<i class="fa fa-edit"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 text-center">
|
||||
<a class="btn btn-warning" ng-click="loadMoreProjects()" ng-if="paginateActive" translate>{{ 'load_next_projects' }}</a>
|
||||
<a class="btn btn-warning" ng-click="loadMore()" ng-if="projectsPagination.hasNextPage()" translate>{{ 'load_next_projects' }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
11
app/controllers/api/openlab_projects_controller.rb
Normal file
11
app/controllers/api/openlab_projects_controller.rb
Normal file
@ -0,0 +1,11 @@
|
||||
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
|
||||
end
|
||||
end
|
@ -51,7 +51,9 @@ class API::ProjectsController < API::ApiController
|
||||
|
||||
def search
|
||||
query_params = JSON.parse(params[:search])
|
||||
@projects = Project.search(query_params, current_user).page(params[:page]).records
|
||||
records = Project.search(query_params, current_user).page(params[:page]).records
|
||||
@total = records.total
|
||||
@projects = records.includes(:users, :project_image)
|
||||
render :index
|
||||
end
|
||||
|
||||
|
@ -28,10 +28,8 @@ class API::ReservationsController < API::ApiController
|
||||
is_reserve = @reservation.save_with_payment
|
||||
end
|
||||
if is_reserve
|
||||
reservation_user = @reservation.user
|
||||
if @reservation.reservable_type == 'Training' and is_first_training_and_active_subscription(reservation_user)
|
||||
reservation_user.subscription.update_expired_date_with_first_training(@reservation.slots.first.start_at)
|
||||
end
|
||||
SubscriptionExtensionAfterReservation.new(@reservation).extend_subscription_if_eligible
|
||||
|
||||
render :show, status: :created, location: @reservation
|
||||
else
|
||||
render json: @reservation.errors, status: :unprocessable_entity
|
||||
@ -57,8 +55,4 @@ class API::ReservationsController < API::ApiController
|
||||
:nb_reserve_places, :nb_reserve_reduced_places,
|
||||
slots_attributes: [:id, :start_at, :end_at, :availability_id, :offered])
|
||||
end
|
||||
|
||||
def is_first_training_and_active_subscription(user)
|
||||
user.reservations.where(reservable_type: 'Training').size == 1 and user.subscription and !user.subscription.is_expired?
|
||||
end
|
||||
end
|
||||
|
@ -6,10 +6,7 @@ class API::SlotsController < API::ApiController
|
||||
def update
|
||||
authorize @slot
|
||||
if @slot.update(slot_params)
|
||||
reservation_user = @slot.reservation.user
|
||||
if @slot.reservation.reservable_type == 'Training' and is_first_training_and_active_subscription(reservation_user)
|
||||
reservation_user.subscription.update_expired_date_with_first_training(@slot.start_at)
|
||||
end
|
||||
SubscriptionExtensionAfterReservation.new(@slot.reservation).extend_subscription_if_eligible
|
||||
render :show, status: :created, location: @slot
|
||||
else
|
||||
render json: @slot.errors, status: :unprocessable_entity
|
||||
@ -29,8 +26,4 @@ class API::SlotsController < API::ApiController
|
||||
def slot_params
|
||||
params.require(:slot).permit(:start_at, :end_at, :availability_id)
|
||||
end
|
||||
|
||||
def is_first_training_and_active_subscription(user)
|
||||
user.reservations.where(reservable_type: 'Training').size == 1 and user.subscription and !user.subscription.is_expired?
|
||||
end
|
||||
end
|
||||
|
@ -14,16 +14,18 @@ class API::SubscriptionsController < API::ApiController
|
||||
else
|
||||
if current_user.is_admin?
|
||||
@subscription = Subscription.find_or_initialize_by(user_id: subscription_params[:user_id])
|
||||
# @subscription.expired_at = nil
|
||||
@subscription.update_column(:expired_at, nil) unless @subscription.new_record? # very important
|
||||
@subscription.attributes = subscription_params
|
||||
is_subscribe = @subscription.save_with_local_payment(!User.find(subscription_params[:user_id]).invoicing_disabled?)
|
||||
else
|
||||
@subscription = Subscription.find_or_initialize_by(user_id: current_user.id)
|
||||
# @subscription.expired_at = nil
|
||||
if valid_card_token?(subscription_params[:card_token])
|
||||
@subscription.update_column(:expired_at, nil) unless @subscription.new_record? # very important
|
||||
@subscription.attributes = subscription_params.merge(user_id: current_user.id)
|
||||
is_subscribe = @subscription.save_with_payment
|
||||
else
|
||||
is_subscribe = false
|
||||
end
|
||||
end
|
||||
if is_subscribe
|
||||
render :show, status: :created, location: @subscription
|
||||
@ -61,4 +63,14 @@ class API::SubscriptionsController < API::ApiController
|
||||
def subscription_update_params
|
||||
params.require(:subscription).permit(:expired_at)
|
||||
end
|
||||
|
||||
# TODO refactor subscriptions logic and move this in model/validator
|
||||
def valid_card_token?(token)
|
||||
begin
|
||||
Stripe::Token.retrieve(token)
|
||||
rescue Stripe::InvalidRequestError => e
|
||||
@subscription.errors[:card_token] << e.message
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -5,6 +5,8 @@ class Abuse < ActiveRecord::Base
|
||||
|
||||
after_create :notify_admins_abuse_reported
|
||||
|
||||
validates :first_name, :last_name, :email, :message, :presence => true
|
||||
|
||||
|
||||
private
|
||||
def notify_admins_abuse_reported
|
||||
|
@ -20,7 +20,8 @@ class Availability < ActiveRecord::Base
|
||||
|
||||
attr_accessor :is_reserved, :slot_id, :can_modify
|
||||
|
||||
validate :length_must_be_1h_minimum
|
||||
validates :start_at, :end_at, presence: true
|
||||
validate :length_must_be_1h_minimum, unless: proc { end_at.blank? or start_at.blank? }
|
||||
validate :should_be_associated
|
||||
|
||||
def safe_destroy
|
||||
@ -64,13 +65,13 @@ class Availability < ActiveRecord::Base
|
||||
private
|
||||
def length_must_be_1h_minimum
|
||||
if end_at < (start_at + 1.hour)
|
||||
errors.add(:end_at, t('availabilities.must_be_at_least_1_hour_after_the_start_date'))
|
||||
errors.add(:end_at, I18n.t('availabilities.must_be_at_least_1_hour_after_the_start_date'))
|
||||
end
|
||||
end
|
||||
|
||||
def should_be_associated
|
||||
if available_type == 'machines' and machine_ids.count == 0
|
||||
errors.add(:machine_ids, t('availabilities.must_be_associated_with_at_least_1_machine'))
|
||||
errors.add(:machine_ids, I18n.t('availabilities.must_be_associated_with_at_least_1_machine'))
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -2,5 +2,5 @@ class InvoiceItem < ActiveRecord::Base
|
||||
belongs_to :invoice
|
||||
belongs_to :subscription
|
||||
|
||||
has_one :invoice_item
|
||||
has_one :invoice_item # to associated invoice_items of an invoice to invoice_items of an avoir
|
||||
end
|
||||
|
@ -1,9 +0,0 @@
|
||||
##DEPRECATED, this class is not used anymore from the migration to the Pricing model
|
||||
##TODO remove in future update, in conjunction with the following migrations:
|
||||
# - 20140606133116_create_machines_pricings.rb
|
||||
# - 20150520133409_migrate_data_from_machines_pricings_to_prices.rb
|
||||
# - 20150603133050_drop_machines_pricings.rb
|
||||
class MachinesPricing < ActiveRecord::Base
|
||||
belongs_to :machine
|
||||
belongs_to :group
|
||||
end
|
@ -1,6 +1,7 @@
|
||||
class Project < ActiveRecord::Base
|
||||
include AASM
|
||||
include NotifyWith::NotificationAttachedObject
|
||||
include OpenlabSync
|
||||
|
||||
# elastic initialisations
|
||||
include Elasticsearch::Model
|
||||
@ -32,6 +33,9 @@ class Project < ActiveRecord::Base
|
||||
has_many :project_steps, dependent: :destroy
|
||||
accepts_nested_attributes_for :project_steps, allow_destroy: true
|
||||
|
||||
# validations
|
||||
validates :author, :name, presence: true
|
||||
|
||||
after_save :after_save_and_publish
|
||||
|
||||
aasm :column => 'state' do
|
||||
@ -104,7 +108,7 @@ class Project < ActiveRecord::Base
|
||||
}
|
||||
}
|
||||
|
||||
if params['q'].empty? # we sort by created_at if there isn't a query
|
||||
if params['q'].blank? # we sort by created_at if there isn't a query
|
||||
search[:sort] = { created_at: { order: :desc } }
|
||||
else # otherwise we search for the word (q) in various fields
|
||||
search[:query][:filtered][:query] = {
|
||||
|
65
app/models/project/openlab_sync.rb
Normal file
65
app/models/project/openlab_sync.rb
Normal file
@ -0,0 +1,65 @@
|
||||
module Project::OpenlabSync
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
include ActionView::Helpers::SanitizeHelper
|
||||
|
||||
after_create :openlab_create, if: :openlab_sync_active?
|
||||
after_update :openlab_update, if: :openlab_sync_active?
|
||||
after_destroy :openlab_destroy, if: :openlab_sync_active?
|
||||
|
||||
def openlab_create
|
||||
OpenlabWorker.delay_for(2.seconds).perform_async(:create, self.id) if self.published?
|
||||
end
|
||||
|
||||
def openlab_update
|
||||
if self.published?
|
||||
if self.state_was == 'draft'
|
||||
OpenlabWorker.perform_async(:create, self.id)
|
||||
else
|
||||
OpenlabWorker.perform_async(:update, self.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def openlab_destroy
|
||||
OpenlabWorker.perform_async(:destroy, self.id)
|
||||
end
|
||||
|
||||
def openlab_attributes
|
||||
{
|
||||
id: id, slug: slug, name: name, description: description, tags: tags,
|
||||
machines: machines.map(&:name),
|
||||
components: components.map(&:name),
|
||||
themes: themes.map(&:name),
|
||||
author: author&.profile&.full_name,
|
||||
collaborators: users.map { |u| u.profile.full_name },
|
||||
steps_body: steps_body,
|
||||
image_path: project_image&.attachment&.medium&.url,
|
||||
project_path: "/#!/projects/#{slug}",
|
||||
updated_at: updated_at.to_s(:iso8601),
|
||||
created_at: created_at.to_s(:iso8601),
|
||||
published_at: published_at.to_s(:iso8601)
|
||||
}
|
||||
end
|
||||
|
||||
def steps_body
|
||||
concatenated_steps = project_steps.map { |s| "#{s.title} #{s.description}" }
|
||||
.join(' ').gsub('</p>', ' </p>')
|
||||
.gsub("\r\n", ' ').gsub("\n\r", ' ')
|
||||
.gsub("\n", ' ').gsub("\r", ' ').gsub("\t", ' ')
|
||||
|
||||
strip_tags(concatenated_steps).strip
|
||||
end
|
||||
|
||||
def openlab_sync_active?
|
||||
self.class.openlab_sync_active?
|
||||
end
|
||||
end
|
||||
|
||||
class_methods do
|
||||
def openlab_sync_active?
|
||||
Rails.application.secrets.openlab_app_secret.present?
|
||||
end
|
||||
end
|
||||
end
|
@ -45,21 +45,20 @@ class Reservation < ActiveRecord::Base
|
||||
# === Machine reservation ===
|
||||
when Machine
|
||||
base_amount = reservable.prices.find_by(group_id: user.group_id, plan_id: plan.try(:id)).amount
|
||||
if plan
|
||||
machine_credit = plan.machine_credits.select {|credit| credit.creditable_id == reservable_id}.first
|
||||
if machine_credit
|
||||
hours_available = machine_credit.hours
|
||||
if !new_plan_being_bought
|
||||
user_credit = user.users_credits.find_by_credit_id(machine_credit.id)
|
||||
if user_credit
|
||||
hours_available = machine_credit.hours - user_credit.hours_used
|
||||
end
|
||||
end
|
||||
users_credits_manager = UsersCredits::Manager.new(reservation: self, plan: plan)
|
||||
|
||||
slots.each_with_index do |slot, index|
|
||||
description = reservable.name + " #{I18n.l slot.start_at, format: :long} - #{I18n.l slot.end_at, format: :hour_minute}"
|
||||
ii_amount = (index < hours_available ? 0 : base_amount)
|
||||
ii_amount = 0 if (slot.offered and on_site)
|
||||
unless on_site
|
||||
|
||||
ii_amount = base_amount # ii_amount default to base_amount
|
||||
|
||||
if users_credits_manager.will_use_credits?
|
||||
ii_amount = (index < users_credits_manager.free_hours_count) ? 0 : base_amount
|
||||
end
|
||||
|
||||
ii_amount = 0 if slot.offered and on_site # if it's a local payment and slot is offered free
|
||||
|
||||
unless on_site # if it's local payment then do not create Stripe::InvoiceItem
|
||||
ii = Stripe::InvoiceItem.create(
|
||||
customer: user.stp_customer_id,
|
||||
amount: ii_amount,
|
||||
@ -70,63 +69,16 @@ class Reservation < ActiveRecord::Base
|
||||
end
|
||||
self.invoice.invoice_items.push InvoiceItem.new(amount: ii_amount, stp_invoice_item_id: (ii.id if ii), description: description)
|
||||
end
|
||||
else
|
||||
slots.each do |slot|
|
||||
description = reservable.name + " #{I18n.l slot.start_at, format: :long} - #{I18n.l slot.end_at, format: :hour_minute}"
|
||||
ii_amount = base_amount
|
||||
ii_amount = 0 if (slot.offered and on_site)
|
||||
unless on_site
|
||||
ii = Stripe::InvoiceItem.create(
|
||||
customer: user.stp_customer_id,
|
||||
amount: ii_amount,
|
||||
currency: Rails.application.secrets.stripe_currency,
|
||||
description: description
|
||||
)
|
||||
invoice_items << ii
|
||||
end
|
||||
self.invoice.invoice_items.push InvoiceItem.new(amount: ii_amount, stp_invoice_item_id: (ii.id if ii), description: description)
|
||||
end
|
||||
end
|
||||
else
|
||||
slots.each do |slot|
|
||||
description = reservable.name + " #{I18n.l slot.start_at, format: :long} - #{I18n.l slot.end_at, format: :hour_minute}"
|
||||
ii_amount = base_amount
|
||||
ii_amount = 0 if (slot.offered and on_site)
|
||||
unless on_site
|
||||
ii = Stripe::InvoiceItem.create(
|
||||
customer: user.stp_customer_id,
|
||||
amount: ii_amount,
|
||||
currency: Rails.application.secrets.stripe_currency,
|
||||
description: description
|
||||
)
|
||||
invoice_items << ii
|
||||
end
|
||||
self.invoice.invoice_items.push InvoiceItem.new(amount: ii_amount, stp_invoice_item_id: (ii.id if ii), description: description)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# === Training reservation ===
|
||||
when Training
|
||||
base_amount = reservable.amount_by_group(user.group_id).amount
|
||||
if plan
|
||||
# Return True if the subscription link a training credit for training reserved by the user
|
||||
training_is_creditable = plan.training_credits.select {|credit| credit.creditable_id == reservable.id}.size > 0
|
||||
|
||||
# Training reserved by the user is free when :
|
||||
# be careful, variable plan can be the user's plan OR the plan user is currently purchasing
|
||||
users_credits_manager = UsersCredits::Manager.new(reservation: self, plan: plan)
|
||||
base_amount = 0 if users_credits_manager.will_use_credits?
|
||||
|
||||
# |-> the user already has a current subscription and if training_is_creditable is true and has at least one credit available.
|
||||
if !new_plan_being_bought
|
||||
if user.training_credits.size < plan.training_credit_nb and training_is_creditable
|
||||
base_amount = 0
|
||||
end
|
||||
# |-> the user buys a new subscription and if training_is_creditable is true.
|
||||
else
|
||||
if training_is_creditable
|
||||
base_amount = 0
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
slots.each do |slot|
|
||||
description = reservable.name + " #{I18n.l slot.start_at, format: :long} - #{I18n.l slot.end_at, format: :hour_minute}"
|
||||
ii_amount = base_amount
|
||||
@ -176,32 +128,6 @@ class Reservation < ActiveRecord::Base
|
||||
invoice_items
|
||||
end
|
||||
|
||||
def update_users_credits
|
||||
if user.subscribed_plan
|
||||
if reservable_type == 'Machine'
|
||||
machine_credit = user.subscribed_plan.machine_credits.select {|credit| credit.creditable_id == reservable_id}.first
|
||||
if machine_credit
|
||||
hours_available = machine_credit.hours
|
||||
user_credit = user.users_credits.find_or_initialize_by(credit_id: machine_credit.id)
|
||||
user_credit.hours_used ||= 0
|
||||
hours_available = machine_credit.hours - user_credit.hours_used
|
||||
if hours_available >= slots.size
|
||||
user_credit.hours_used = user_credit.hours_used + slots.size
|
||||
else
|
||||
user_credit.hours_used = machine_credit.hours
|
||||
end
|
||||
user_credit.save
|
||||
end
|
||||
elsif reservable_type == 'Training'
|
||||
training_credit = user.subscribed_plan.training_credits.select {|credit| credit.creditable_id == reservable_id}.first
|
||||
if user.training_credits.size < user.subscribed_plan.training_credit_nb and training_credit
|
||||
user.credits << training_credit
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
def save_with_payment
|
||||
build_invoice(user: user)
|
||||
invoice_items = generate_invoice_items
|
||||
@ -218,6 +144,10 @@ class Reservation < ActiveRecord::Base
|
||||
total = invoice.invoice_items.map(&:amount).map(&:to_i).reduce(:+)
|
||||
self.invoice.total = total
|
||||
save!
|
||||
#
|
||||
# IMPORTANT NOTE: here, we don't have to create a stripe::invoice and pay it
|
||||
# because subscription.create (in subscription.rb) will pay all waiting stripe invoice items
|
||||
#
|
||||
else
|
||||
# error handling
|
||||
invoice_items.each(&:delete)
|
||||
@ -237,6 +167,9 @@ class Reservation < ActiveRecord::Base
|
||||
customer.save
|
||||
end
|
||||
end
|
||||
#
|
||||
# IMPORTANT NOTE: here, we have to create an invoice manually and pay it to pay all waiting stripe invoice items
|
||||
#
|
||||
invoice = Stripe::Invoice.create(
|
||||
customer: user.stp_customer_id,
|
||||
)
|
||||
@ -286,7 +219,8 @@ class Reservation < ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
|
||||
update_users_credits
|
||||
UsersCredits::Manager.new(reservation: self).update_credits
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
@ -318,7 +252,7 @@ class Reservation < ActiveRecord::Base
|
||||
if user.invoicing_disabled?
|
||||
if valid?
|
||||
save!
|
||||
update_users_credits
|
||||
UsersCredits::Manager.new(reservation: self).update_credits
|
||||
return true
|
||||
end
|
||||
else
|
||||
@ -345,7 +279,8 @@ class Reservation < ActiveRecord::Base
|
||||
save!
|
||||
end
|
||||
|
||||
update_users_credits
|
||||
UsersCredits::Manager.new(reservation: self).update_credits
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -8,6 +8,7 @@ class Subscription < ActiveRecord::Base
|
||||
has_many :offer_days, dependent: :destroy
|
||||
|
||||
validates_presence_of :plan_id
|
||||
validates_with SubscriptionGroupValidator
|
||||
|
||||
attr_accessor :card_token
|
||||
|
||||
@ -27,7 +28,7 @@ class Subscription < ActiveRecord::Base
|
||||
self.expired_at = Time.at(new_subscription.current_period_end)
|
||||
save!
|
||||
|
||||
reset_users_credits if expired_date_changed
|
||||
UsersCredits::Manager.new(user: self.user).reset_credits if expired_date_changed
|
||||
|
||||
# generate invoice
|
||||
stp_invoice = Stripe::Invoice.all(customer: user.stp_customer_id, limit: 1).data.first
|
||||
@ -76,7 +77,7 @@ class Subscription < ActiveRecord::Base
|
||||
self.canceled_at = nil
|
||||
set_expired_at
|
||||
save!
|
||||
reset_users_credits if expired_date_changed
|
||||
UsersCredits::Manager.new(user: self.user).reset_credits if expired_date_changed
|
||||
generate_invoice.save if invoice
|
||||
return true
|
||||
else
|
||||
@ -101,12 +102,6 @@ class Subscription < ActiveRecord::Base
|
||||
invoice.save
|
||||
end
|
||||
|
||||
def update_expired_date_with_first_training(training_start_at)
|
||||
if plan.is_rolling?
|
||||
update_columns(expired_at: training_start_at + plan.duration)
|
||||
end
|
||||
end
|
||||
|
||||
def cancel
|
||||
if stp_subscription_id.present?
|
||||
stp_subscription = stripe_subscription
|
||||
@ -139,7 +134,7 @@ class Subscription < ActiveRecord::Base
|
||||
|
||||
self.expired_at = expired_at
|
||||
if save
|
||||
reset_users_credits if !free_days
|
||||
UsersCredits::Manager.new(user: self.user).reset_credits if !free_days
|
||||
notify_subscription_extended(free_days)
|
||||
return true
|
||||
end
|
||||
@ -147,18 +142,6 @@ class Subscription < ActiveRecord::Base
|
||||
end
|
||||
|
||||
private
|
||||
def update_payment
|
||||
if stp_subscription_id.present?
|
||||
stp_subscription = stripe_subscription
|
||||
stp_subscription.plan = plan.stp_plan_id
|
||||
stp_subscription.prorate = false
|
||||
stp_subscription.save
|
||||
else
|
||||
# TODO: implement stripe payment if member update subscription
|
||||
# must have a card
|
||||
end
|
||||
end
|
||||
|
||||
def notify_member_subscribed_plan
|
||||
NotificationCenter.call type: 'notify_member_subscribed_plan',
|
||||
receiver: user,
|
||||
@ -214,10 +197,6 @@ class Subscription < ActiveRecord::Base
|
||||
p_value.to_date != expired_at.to_date and expired_at > p_value
|
||||
end
|
||||
|
||||
def reset_users_credits
|
||||
user.users_credits.destroy_all
|
||||
end
|
||||
|
||||
# def is_being_extended?
|
||||
# !expired_at_was.nil? and expired_at_changed?
|
||||
# end
|
||||
|
@ -65,6 +65,8 @@ class User < ActiveRecord::Base
|
||||
validates :username, presence: true, uniqueness: true, length: { maximum: 30 }
|
||||
|
||||
scope :active, -> { where(is_active: true) }
|
||||
scope :without_subscription, -> { includes(:subscriptions).where(subscriptions: { user_id: nil }) }
|
||||
scope :with_subscription, -> { joins(:subscriptions) }
|
||||
|
||||
def to_builder
|
||||
Jbuilder.new do |json|
|
||||
|
26
app/services/subscription_extension_after_reservation.rb
Normal file
26
app/services/subscription_extension_after_reservation.rb
Normal file
@ -0,0 +1,26 @@
|
||||
class SubscriptionExtensionAfterReservation
|
||||
attr_accessor :user, :reservation
|
||||
|
||||
def initialize(reservation)
|
||||
@user, @reservation = reservation.user, reservation
|
||||
end
|
||||
|
||||
def extend_subscription_if_eligible
|
||||
extend_subscription if eligible_to_extension?
|
||||
end
|
||||
|
||||
def eligible_to_extension?
|
||||
return false unless reservation.reservable_type == 'Training'
|
||||
return false if user.reservations.where(reservable_type: 'Training').count != 1
|
||||
return false unless user.subscription
|
||||
return false if user.subscription.is_expired?
|
||||
return false unless user.subscribed_plan.is_rolling
|
||||
return true
|
||||
end
|
||||
|
||||
def extend_subscription
|
||||
user.subscription.update_columns(
|
||||
expired_at: reservation.slots.first.start_at + user.subscribed_plan.duration
|
||||
)
|
||||
end
|
||||
end
|
156
app/services/users_credits/manager.rb
Normal file
156
app/services/users_credits/manager.rb
Normal file
@ -0,0 +1,156 @@
|
||||
require 'forwardable'
|
||||
|
||||
module UsersCredits
|
||||
class AlreadyUpdatedError < StandardError;end;
|
||||
|
||||
class Manager
|
||||
extend Forwardable
|
||||
attr_reader :manager
|
||||
|
||||
def initialize(reservation: nil, user: nil, plan: nil)
|
||||
if user
|
||||
@manager = Managers::User.new(user)
|
||||
elsif reservation
|
||||
if reservation.reservable_type == "Training"
|
||||
@manager = Managers::Training.new(reservation, plan)
|
||||
elsif reservation.reservable_type == "Machine"
|
||||
@manager = Managers::Machine.new(reservation, plan)
|
||||
elsif reservation.reservable_type == "Event"
|
||||
@manager = Managers::Event.new(reservation, plan)
|
||||
else
|
||||
raise ArgumentError, "reservation.reservable_type must be Training, Machine or Event"
|
||||
end
|
||||
else
|
||||
raise ArgumentError, "you have to pass either a reservation or a user to initialize a UsersCredits::Manager"
|
||||
end
|
||||
end
|
||||
|
||||
def_delegators :@manager, :will_use_credits?, :free_hours_count, :update_credits, :reset_credits
|
||||
end
|
||||
|
||||
module Managers
|
||||
class User # that class is responsible for reseting users_credits of a user
|
||||
attr_reader :user
|
||||
|
||||
def initialize(user)
|
||||
@user = user
|
||||
end
|
||||
|
||||
def reset_credits
|
||||
user.users_credits.destroy_all
|
||||
end
|
||||
end
|
||||
|
||||
class Reservation
|
||||
attr_reader :reservation
|
||||
|
||||
def initialize(reservation, plan) # a plan can be passed to do a simulation (if user didn't have a subscription YET)
|
||||
@reservation = reservation
|
||||
@already_updated = false
|
||||
@plan = plan
|
||||
end
|
||||
|
||||
def plan
|
||||
@plan || user.subscribed_plan
|
||||
end
|
||||
|
||||
def user
|
||||
reservation.user
|
||||
end
|
||||
|
||||
def update_credits
|
||||
if @already_updated
|
||||
raise AlreadyUpdatedError, "update credit is not idempotent ! you can't invoke update_credits method twice."
|
||||
else
|
||||
@already_updated = true
|
||||
end
|
||||
end
|
||||
end
|
||||
private_constant :Reservation
|
||||
|
||||
|
||||
class Machine < Reservation # that class is responsible for knowing how to update users_credit of a given user for a given reservation
|
||||
def will_use_credits? # to known if a credit will be used in the context of the given reservation
|
||||
_will_use_credits?[0]
|
||||
end
|
||||
|
||||
def free_hours_count
|
||||
_will_use_credits?[1]
|
||||
end
|
||||
|
||||
def update_credits
|
||||
super
|
||||
|
||||
will_use_credits, free_hours_count, machine_credit = _will_use_credits?
|
||||
if will_use_credits
|
||||
users_credit = user.users_credits.find_or_initialize_by(credit_id: machine_credit.id)
|
||||
|
||||
if users_credit.new_record?
|
||||
users_credit.hours_used = free_hours_count
|
||||
else
|
||||
users_credit.hours_used += free_hours_count
|
||||
end
|
||||
users_credit.save!
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def _will_use_credits?
|
||||
return false, 0 unless plan
|
||||
|
||||
if machine_credit = plan.machine_credits.find_by(creditable_id: reservation.reservable_id)
|
||||
users_credit = user.users_credits.find_by(credit_id: machine_credit.id)
|
||||
already_used_hours = users_credit ? users_credit.hours_used : 0
|
||||
|
||||
remaining_hours = machine_credit.hours - already_used_hours
|
||||
|
||||
free_hours_count = [remaining_hours, reservation.slots.size].min
|
||||
|
||||
if free_hours_count > 0
|
||||
return true, free_hours_count, machine_credit
|
||||
else
|
||||
return false, free_hours_count, machine_credit
|
||||
end
|
||||
end
|
||||
return false, 0
|
||||
end
|
||||
end
|
||||
|
||||
class Training < Reservation # same as class Machine but for Training reservation
|
||||
def will_use_credits?
|
||||
_will_use_credits?[0]
|
||||
end
|
||||
|
||||
def update_credits
|
||||
super
|
||||
will_use_credits, training_credit = _will_use_credits?
|
||||
if will_use_credits
|
||||
user.credits << training_credit # we create a new UsersCredit object
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def _will_use_credits?
|
||||
return false, nil unless plan
|
||||
|
||||
# if there is a training_credit defined for this plan and this training
|
||||
if training_credit = plan.training_credits.find_by(creditable_id: reservation.reservable_id)
|
||||
# if user has not used all the plan credits
|
||||
if user.training_credits.where(plan: plan).count < plan.training_credit_nb
|
||||
return true, training_credit
|
||||
end
|
||||
end
|
||||
return false, nil
|
||||
end
|
||||
end
|
||||
|
||||
class Event < Reservation
|
||||
def will_use_credits?
|
||||
false
|
||||
end
|
||||
|
||||
def update_credits
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
15
app/validators/stripe_card_token_validator.rb
Normal file
15
app/validators/stripe_card_token_validator.rb
Normal file
@ -0,0 +1,15 @@
|
||||
|
||||
class StripeCardTokenValidator
|
||||
def validate(record)
|
||||
if options[:token]
|
||||
begin
|
||||
res = Stripe::Token.retrieve(options[:token])
|
||||
if res[:id] != options[:token]
|
||||
record.errors[:card_token] << "A problem occurred while retrieving the card with the specified token: #{res.id}"
|
||||
end
|
||||
rescue Stripe::InvalidRequestError => e
|
||||
record.errors[:card_token] << e
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
7
app/validators/subscription_group_validator.rb
Normal file
7
app/validators/subscription_group_validator.rb
Normal file
@ -0,0 +1,7 @@
|
||||
class SubscriptionGroupValidator < ActiveModel::Validator
|
||||
def validate(record)
|
||||
if record.user.group != record.plan.group
|
||||
record.errors[:plan_id] << "This plan is not compatible with the current user's group"
|
||||
end
|
||||
end
|
||||
end
|
@ -1,3 +1,3 @@
|
||||
json.admin do
|
||||
json.reporting do
|
||||
json.extract! @abuse, :id, :signaled_id, :signaled_type
|
||||
end
|
||||
|
@ -1,10 +1,11 @@
|
||||
json.array!(@projects) do |project|
|
||||
json.projects @projects do |project|
|
||||
json.extract! project, :id, :name, :description, :author_id, :licence_id, :slug
|
||||
json.url project_url(project, format: :json)
|
||||
json.project_image project.project_image.attachment.medium.url if project.project_image
|
||||
json.machine_ids project.machine_ids
|
||||
json.author_id project.author_id
|
||||
json.user_ids project.user_ids
|
||||
json.theme_ids project.theme_ids
|
||||
json.component_ids project.component_ids
|
||||
end
|
||||
|
||||
json.meta do
|
||||
json.total @total if @total
|
||||
end
|
||||
|
@ -45,6 +45,12 @@
|
||||
Fablab.weekStartingDay = <%= Date.parse(Rails.application.secrets.week_starting_day).strftime('%w') %>;
|
||||
Fablab.d3DateFormat = "<%= Rails.application.secrets.d3_date_format %>";
|
||||
Fablab.uibDateFormat = "<%= Rails.application.secrets.uib_date_format %>";
|
||||
Fablab.openlabProjectsActive = <%= Rails.application.secrets.openlab_app_secret.present? %>;
|
||||
<% if Rails.application.secrets.openlab_app_id.present? %>
|
||||
Fablab.openlabAppId = "<%= Rails.application.secrets.openlab_app_id %>";
|
||||
<% else %>
|
||||
Fablab.openlabAppId = null;
|
||||
<% end %>
|
||||
</script>
|
||||
|
||||
<%= stylesheet_link_tag 'application', media: 'all' %>
|
||||
@ -54,8 +60,10 @@
|
||||
<% end %>
|
||||
<base href="/"></base>
|
||||
|
||||
<% if CustomAsset.get_url('favicon-file') %>
|
||||
<link rel="shortcut icon" type="image/x-icon" href="<%= CustomAsset.get_url('favicon-file') %>">
|
||||
<link rel="shortcut icon" type="image/ico" href="<%= CustomAsset.get_url('favicon-file') %>">
|
||||
<% end %>
|
||||
|
||||
|
||||
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
||||
|
24
app/workers/openlab_worker.rb
Normal file
24
app/workers/openlab_worker.rb
Normal file
@ -0,0 +1,24 @@
|
||||
class OpenlabWorker
|
||||
include Sidekiq::Worker
|
||||
sidekiq_options queue: 'default', retry: true
|
||||
|
||||
Logger = Sidekiq.logger.level == Logger::DEBUG ? Sidekiq.logger : nil
|
||||
OPENLAB_CLIENT = Openlab::Projects.new
|
||||
|
||||
def perform(action, project_id)
|
||||
logger.debug ["Openlab sync", action, "project ID: #{project_id}"]
|
||||
|
||||
case action.to_s
|
||||
when /create/
|
||||
project = Project.find(project_id)
|
||||
response = OPENLAB_CLIENT.create(project.openlab_attributes)
|
||||
when /update/
|
||||
project = Project.find(project_id)
|
||||
response = OPENLAB_CLIENT.update(project_id, project.openlab_attributes)
|
||||
when /destroy/
|
||||
response = OPENLAB_CLIENT.destroy(project_id)
|
||||
end
|
||||
|
||||
logger.debug ["Openlab sync", "RESPONSE ERROR", response.inspect] unless response.success?
|
||||
end
|
||||
end
|
@ -47,3 +47,7 @@ TIME_ZONE: 'Paris'
|
||||
WEEK_STARTING_DAY: 'monday'
|
||||
D3_DATE_FORMAT: '%d/%m/%y'
|
||||
UIB_DATE_FORMAT: 'dd/MM/yyyy'
|
||||
|
||||
OPENLAB_APP_SECRET:
|
||||
OPENLAB_APP_ID:
|
||||
OPENLAB_BASE_URI: 'https://openprojects.fab-manager.com'
|
||||
|
@ -39,6 +39,6 @@ Rails.application.configure do
|
||||
# Raises error for missing translations
|
||||
# config.action_view.raise_on_missing_translations = true
|
||||
|
||||
|
||||
config.active_support.test_order = :random
|
||||
|
||||
end
|
||||
|
26
config/initializers/active_record_base.rb
Normal file
26
config/initializers/active_record_base.rb
Normal file
@ -0,0 +1,26 @@
|
||||
ActiveRecord::Base.class_eval do
|
||||
def dump_fixture
|
||||
fixture_file = "#{Rails.root}/test/fixtures/#{self.class.table_name}.yml"
|
||||
File.open(fixture_file, "a") do |f|
|
||||
f.puts({ "#{self.class.table_name.singularize}_#{id}" => attributes }.
|
||||
to_yaml.sub!(/---\s?/, "\n"))
|
||||
end
|
||||
end
|
||||
|
||||
def self.dump_fixtures
|
||||
fixture_file = "#{Rails.root}/test/fixtures/#{self.table_name}.yml"
|
||||
mode = (File.exists?(fixture_file) ? 'a' : 'w')
|
||||
File.open(fixture_file, mode) do |f|
|
||||
|
||||
if self.attribute_names.include?("id")
|
||||
self.all.each do |instance|
|
||||
f.puts({ "#{self.table_name.singularize}_#{instance.id}" => instance.attributes }.to_yaml.sub!(/---\s?/, "\n"))
|
||||
end
|
||||
else
|
||||
self.all.each_with_index do |instance, i|
|
||||
f.puts({ "#{self.table_name.singularize}_#{i}" => instance.attributes }.to_yaml.sub!(/---\s?/, "\n"))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
4
config/initializers/openlab_ruby.rb
Normal file
4
config/initializers/openlab_ruby.rb
Normal file
@ -0,0 +1,4 @@
|
||||
Openlab.configure do |config|
|
||||
config.app_secret = Rails.application.secrets.openlab_app_secret
|
||||
config.base_uri = Rails.application.secrets.openlab_base_uri
|
||||
end
|
@ -6,12 +6,18 @@ en:
|
||||
confirm_your_new_account: "Confirm your new account"
|
||||
you_ve_just_created_a_new_account_on_the_fablab_by_logging_from: "You've just created a new account on the {NAME}, by logging from" # messageFormat interpolation
|
||||
before_letting_you_use_the_application_we_need_some_more_details: "Before letting you use the application, we need some more details"
|
||||
your_email_is_already_used_by_another_account_on_the_platform: "But wait, there is a problem! Your email is already used by another account on the platform."
|
||||
please_fill_the_following_form: "Please fill the following form"
|
||||
some_data_may_have_already_been_provided_by_provider_and_cannot_be_modified: "Some data may have already been provided by {{NAME}} and cannot be modified" # angular interpolation
|
||||
then_click_on_: "Then click on"
|
||||
_to_start_using_the_application: "to start using the application"
|
||||
new_on_this_platform: "New on this platform?"
|
||||
your_email_: "Your email"
|
||||
_is_currently_associated_with_another_account_on_this_platform: "is currently associated with another account on this platform."
|
||||
please_click_to_change_email_associated_with_your_PROVIDER_account: "If it is not yours, please click on the following button to change the email associated with your {{PROVIDER}} account." # angular interpolation
|
||||
do_you_already_have_an_account: "Do you already have an account?"
|
||||
do_not_fill_the_form_below_but_specify_here_the_code_you_ve_received_by_email_to_recover_your_access: "Do not fill the form below but specify here the code you've received by email, to recover your access"
|
||||
do_not_fill_the_form_beside_but_specify_here_the_code_you_ve_received_by_email_to_recover_your_access: "Do not fill the form beside but specify here the code you've received by email, to recover your access."
|
||||
just_specify_code_here_to_recover_access: "Just specify here the code you've received by email to recover your access."
|
||||
authentification_code: "Authentification code"
|
||||
confirm_my_code: "Confirm my code"
|
||||
an_unexpected_error_occurred_check_your_authentication_code: "An unexpected error occurred, please check your authentication code."
|
||||
@ -32,13 +38,6 @@ en:
|
||||
no_labels: "No labels"
|
||||
delete_my_account: "Delete my account"
|
||||
edit_my_profile: "Edit my profile"
|
||||
change_my_data: "Change my data"
|
||||
once_your_data_are_up_to_date_: "Once your data are up to date,"
|
||||
_click_on_the_synchronization_button_opposite_: "click on the synchronization button opposite"
|
||||
_or_: 'or'
|
||||
_disconnect_then_reconnect_: "disconnect then reconnect"
|
||||
_for_your_changes_to_take_effect: "for your changes to take effect."
|
||||
sync_my_profile: "Sync my profile"
|
||||
your_group_has_been_successfully_changed: "Your group has been successfully changed."
|
||||
an_unexpected_error_prevented_your_group_from_being_changed: "An unexpected error prevented your group from being changed."
|
||||
do_you_really_want_to_delete_your_account: "Do you really want to delete your account?"
|
||||
|
@ -6,12 +6,18 @@ fr:
|
||||
confirm_your_new_account: "Confirmez votre nouveau compte"
|
||||
you_ve_just_created_a_new_account_on_the_fablab_by_logging_from: "Vous venez de créer un nouveau compte sur {GENDER, select, male{le} female{la} other{les}} {NAME}, en vous connectant depuis" # messageFormat interpolation
|
||||
before_letting_you_use_the_application_we_need_some_more_details: "Avant de vous laisser utiliser l'application, nous avons besoin de quelques renseignements supplémentaires"
|
||||
your_email_is_already_used_by_another_account_on_the_platform: "Mais attendez, il y a un problème ! Votre adresse de courriel est déjà utilisée par un autre compte sur cette plate-forme."
|
||||
please_fill_the_following_form: "Merci de compléter le formulaire suivant"
|
||||
some_data_may_have_already_been_provided_by_provider_and_cannot_be_modified: "Certaines informations peuvent nous avoir été déjà fournies par {{NAME}} et ne sont pas modifiables" # angular interpolation
|
||||
then_click_on_: "Cliquez ensuite sur"
|
||||
_to_start_using_the_application: "pour commencer à utiliser l'application"
|
||||
new_on_this_platform: "Nouveau sur cette plate-forme ?"
|
||||
your_email_: "Votre adresse de courriel"
|
||||
_is_currently_associated_with_another_account_on_this_platform: "est actuellement associée avec un autre compte sur cette plate-forme."
|
||||
please_click_to_change_email_associated_with_your_PROVIDER_account: "Si ce n'est pas le vôtre, merci de cliquer sur le bouton ci-dessous pour changer l'adresse de courriel associée avec votre compte {{PROVIDER}}." # angular interpolation
|
||||
do_you_already_have_an_account: "Vous possédez déjà un compte ?"
|
||||
do_not_fill_the_form_below_but_specify_here_the_code_you_ve_received_by_email_to_recover_your_access: "Ne remplissez pas le formulaire ci-dessous mais indiquez ici le code qui vous a été fourni par e-mail, cela vous permettra de récupérer l'accès à votre compte."
|
||||
do_not_fill_the_form_beside_but_specify_here_the_code_you_ve_received_by_email_to_recover_your_access: "Ne remplissez pas le formulaire à gauche mais indiquez ici le code qui vous a été fourni par e-mail, cela vous permettra de récupérer l'accès à votre compte."
|
||||
just_specify_code_here_to_recover_access: "Indiquez simplement ici le code que vous avez reçu par e-mail, cela vous permettra de récupérer l'accès à votre compte."
|
||||
authentification_code: "Code d'authentification"
|
||||
confirm_my_code: "Valider mon code"
|
||||
an_unexpected_error_occurred_check_your_authentication_code: "Une erreur inattendue est survenue, vérifiez votre code d'authentification."
|
||||
@ -32,13 +38,6 @@ fr:
|
||||
no_labels: "Aucune étiquette"
|
||||
delete_my_account: "Supprimer mon compte"
|
||||
edit_my_profile: "Éditer votre profil"
|
||||
change_my_data: "Modifier mes données"
|
||||
once_your_data_are_up_to_date_: "Une fois vos données à jour,"
|
||||
_click_on_the_synchronization_button_opposite_: "cliquez sur le bouton de synchronisation ci-contre"
|
||||
_or_: 'ou'
|
||||
_disconnect_then_reconnect_: "déconnectez-vous puis re-connectez vous"
|
||||
_for_your_changes_to_take_effect: "pour que les modifications soient prises en compte."
|
||||
sync_my_profile: "Synchroniser mon profil"
|
||||
your_group_has_been_successfully_changed: "Votre groupe a bien été changé."
|
||||
an_unexpected_error_prevented_your_group_from_being_changed: "Une erreur inattendue a empêché votre changement de groupe."
|
||||
do_you_really_want_to_delete_your_account: "Êtes-vous sûr de vouloir supprimer votre compte ?"
|
||||
|
@ -118,8 +118,11 @@ en:
|
||||
projects_list:
|
||||
# projects gallery
|
||||
the_fablab_projects: "The Fab Lab projects"
|
||||
search_over_the_whole_network: "Search over the whole Fab Manager network"
|
||||
tooltip_openlab_projects_switch: "The search over the whole network lets you search over the projects of every Fab-manager using this feature !"
|
||||
openlab_search_not_available_at_the_moment: "Search over the whole network is not available at the moment. You still can search over the projects of this platform."
|
||||
project_search_result_is_empty: "Sorry, we found no results matching your search criteria."
|
||||
add_a_project: "Add a project"
|
||||
filter_projects: "Filter projects:"
|
||||
reset_all_filters: "Reset all filters"
|
||||
search: "Search"
|
||||
all_projects: "All projects"
|
||||
@ -128,7 +131,7 @@ en:
|
||||
all_machines: "All machines"
|
||||
all_themes: "All themes"
|
||||
all_materials: "All materials"
|
||||
load_next_projects: "Load next projects..."
|
||||
load_next_projects: "Load next projects"
|
||||
|
||||
projects_show:
|
||||
# details of a projet
|
||||
|
@ -118,8 +118,11 @@ fr:
|
||||
projects_list:
|
||||
# galerie des projets
|
||||
the_fablab_projects: "Les projets du FabLab"
|
||||
search_over_the_whole_network: "Chercher sur tout le réseau Fab Manager"
|
||||
tooltip_openlab_projects_switch: "La recherche sur tout le réseau vous permet de rechercher parmis les projets de tous les Fab-managers utilisant cette fonctionnalité !"
|
||||
openlab_search_not_available_at_the_moment: "La recherche sur tout le réseau n'est pas disponible pour le moment. Vous pouvez cependant effectuer une recherche parmis les projets de cette plateforme."
|
||||
project_search_result_is_empty: "Il n'y a pas de projets correspondant à vos critères de recherche."
|
||||
add_a_project: "Ajouter un projet"
|
||||
filter_projects: "Filtrer les projets :"
|
||||
reset_all_filters: "Réinitialiser tous les filtres"
|
||||
search: "Rechercher"
|
||||
all_projects: "Tous les projets"
|
||||
@ -128,7 +131,7 @@ fr:
|
||||
all_machines: "Toutes les machines"
|
||||
all_themes: "Toutes les thématiques"
|
||||
all_materials: "Tous les matériaux"
|
||||
load_next_projects: "Charger les projets suivants ..."
|
||||
load_next_projects: "Charger les projets suivants"
|
||||
|
||||
projects_show:
|
||||
# détails d'un projet
|
||||
|
@ -80,6 +80,12 @@ en:
|
||||
to_date: "to" # context: date. eg: "from 01/01 to 01/05"
|
||||
to_time: "to" # context: time. eg. "from 18:00 to 21:00"
|
||||
or: "or"
|
||||
change_my_data: "Change my data"
|
||||
sync_my_profile: "Sync my profile"
|
||||
once_your_data_are_up_to_date_: "Once your data are up to date,"
|
||||
_click_on_the_synchronization_button_opposite_: "click on the synchronization button opposite"
|
||||
_disconnect_then_reconnect_: "disconnect then reconnect"
|
||||
_for_your_changes_to_take_effect: "for your changes to take effect."
|
||||
|
||||
messages:
|
||||
you_will_lose_any_unsaved_modification_if_you_quit_this_page: "You will lose any unsaved modification if you quit this page"
|
||||
|
@ -80,6 +80,12 @@ fr:
|
||||
to_date: "au" # context: date. eg: "from 01/01 to 01/05"
|
||||
to_time: "à" # context: time. eg. "from 18:00 to 21:00"
|
||||
or: "ou"
|
||||
change_my_data: "Modifier mes données"
|
||||
sync_my_profile: "Synchroniser mon profil"
|
||||
once_your_data_are_up_to_date_: "Une fois vos données à jour,"
|
||||
_click_on_the_synchronization_button_opposite_: "cliquez sur le bouton de synchronisation ci-contre"
|
||||
_disconnect_then_reconnect_: "déconnectez-vous puis re-connectez vous"
|
||||
_for_your_changes_to_take_effect: "pour que les modifications soient prises en compte."
|
||||
|
||||
messages:
|
||||
you_will_lose_any_unsaved_modification_if_you_quit_this_page: "Vous perdrez les modifications non enregistrées si vous quittez cette page"
|
||||
|
@ -27,6 +27,7 @@ Rails.application.routes.draw do
|
||||
get :search
|
||||
end
|
||||
end
|
||||
resources :openlab_projects, only: :index
|
||||
resources :machines
|
||||
resources :components
|
||||
resources :themes
|
||||
|
@ -28,26 +28,31 @@ development:
|
||||
messageformat_locale: <%= ENV["MESSAGEFORMAT_LOCALE"] %>
|
||||
fullcalendar_locale: <%= ENV["FULLCALENDAR_LOCALE"] %>
|
||||
elasticsearch_language_analyzer: <%= ENV["ELASTICSEARCH_LANGUAGE_ANALYZER"] %>
|
||||
openlab_app_secret: <%= ENV["OPENLAB_APP_SECRET"] %>
|
||||
openlab_app_id: <%= ENV["OPENLAB_APP_ID"] %>
|
||||
openlab_base_uri: <%= ENV["OPENLAB_BASE_URI"] %>
|
||||
|
||||
test:
|
||||
secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
|
||||
secret_key_base: 83daf5e7b80d990f037407bab78dff9904aaf3c195a50f84fa8695a22287e707dfbd9524b403b1dcf116ae1d8c06844c3d7ed942564e5b46be6ae3ead93a9d30
|
||||
stripe_api_key: <%= ENV["STRIPE_API_KEY"] %>
|
||||
stripe_publishable_key: <%= ENV["STRIPE_PUBLISHABLE_KEY"] %>
|
||||
stripe_currency: <%= ENV["STRIPE_CURRENCY"] %>
|
||||
disqus_shortname: <%= ENV["DISQUS_SHORTNAME"] %>
|
||||
fablab_without_plans: <%= ENV["FABLAB_WITHOUT_PLANS"] %>
|
||||
time_zone: <%= ENV["TIME_ZONE"] %>
|
||||
week_starting_day: <%= ENV["WEEK_STARTING_DAY"] %>
|
||||
d3_date_format: <%= ENV["D3_DATE_FORMAT"].dump %>
|
||||
uib_date_format: <%= ENV["UIB_DATE_FORMAT"] %>
|
||||
rails_locale: <%= ENV["RAILS_LOCALE"] %>
|
||||
moment_locale: <%= ENV["MOMENT_LOCALE"] %>
|
||||
summernote_locale: <%= ENV["SUMMERNOTE_LOCALE"] %>
|
||||
angular_locale: <%= ENV["ANGULAR_LOCALE"] %>
|
||||
messageformat_locale: <%= ENV["MESSAGEFORMAT_LOCALE"] %>
|
||||
fullcalendar_locale: <%= ENV["FULLCALENDAR_LOCALE"] %>
|
||||
elasticsearch_language_analyzer: <%= ENV["ELASTICSEARCH_LANGUAGE_ANALYZER"] %>
|
||||
|
||||
stripe_currency: usd
|
||||
disqus_shortname: fablab-sleede
|
||||
fablab_without_plans: false
|
||||
time_zone: Paris
|
||||
week_starting_day: monday
|
||||
d3_date_format: '%d/%m/%y'
|
||||
uib_date_format: dd/MM/yyyy
|
||||
rails_locale: en
|
||||
moment_locale: en
|
||||
summernote_locale: en-US
|
||||
angular_locale: en-us
|
||||
messageformat_locale: en
|
||||
fullcalendar_locale: en
|
||||
elasticsearch_language_analyzer: french
|
||||
openlab_app_secret:
|
||||
openlab_app_id:
|
||||
openlab_base_uri:
|
||||
|
||||
staging:
|
||||
secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
|
||||
@ -74,6 +79,9 @@ staging:
|
||||
messageformat_locale: <%= ENV["MESSAGEFORMAT_LOCALE"] %>
|
||||
fullcalendar_locale: <%= ENV["FULLCALENDAR_LOCALE"] %>
|
||||
elasticsearch_language_analyzer: <%= ENV["ELASTICSEARCH_LANGUAGE_ANALYZER"] %>
|
||||
openlab_app_secret: <%= ENV["OPENLAB_APP_SECRET"] %>
|
||||
openlab_app_id: <%= ENV["OPENLAB_APP_ID"] %>
|
||||
openlab_base_uri: <%= ENV["OPENLAB_BASE_URI"] %>
|
||||
|
||||
# Do not keep production secrets in the repository,
|
||||
# instead read values from the environment.
|
||||
@ -102,3 +110,6 @@ production:
|
||||
messageformat_locale: <%= ENV["MESSAGEFORMAT_LOCALE"] %>
|
||||
fullcalendar_locale: <%= ENV["FULLCALENDAR_LOCALE"] %>
|
||||
elasticsearch_language_analyzer: <%= ENV["ELASTICSEARCH_LANGUAGE_ANALYZER"] %>
|
||||
openlab_app_secret: <%= ENV["OPENLAB_APP_SECRET"] %>
|
||||
openlab_app_id: <%= ENV["OPENLAB_APP_ID"] %>
|
||||
openlab_base_uri: <%= ENV["OPENLAB_BASE_URI"] %>
|
||||
|
@ -1,13 +0,0 @@
|
||||
class CreateMachinesPricings < ActiveRecord::Migration
|
||||
def change
|
||||
create_table :machines_pricings do |t|
|
||||
t.belongs_to :machine, index: true
|
||||
t.belongs_to :group, index: true
|
||||
t.integer :not_subscribe_amount
|
||||
t.integer :month_amount
|
||||
t.integer :year_amount
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
@ -1,35 +0,0 @@
|
||||
class MigrateDataFromMachinesPricingsToPrices < ActiveRecord::Migration
|
||||
def up
|
||||
insert <<-SQL
|
||||
DELETE FROM machines_pricings WHERE machine_id NOT IN ( select distinct machines.id from machines )
|
||||
SQL
|
||||
|
||||
machines_pricings = select_all("SELECT * FROM machines_pricings")
|
||||
machines_pricings.each do |m_pricing|
|
||||
time = "'#{Time.now.to_s(:db)}'"
|
||||
|
||||
plan_year = select_one("SELECT id FROM plans where plans.group_id = #{m_pricing['group_id']} and plans.interval = 'year'")
|
||||
insert <<-SQL
|
||||
INSERT INTO prices (group_id, plan_id, priceable_id, priceable_type, amount, created_at, updated_at)
|
||||
VALUES (#{m_pricing['group_id']}, #{plan_year['id']}, #{m_pricing['machine_id']}, 'Machine', #{m_pricing['year_amount']}, #{time}, #{time})
|
||||
SQL
|
||||
|
||||
plan_month = select_one("SELECT id FROM plans where plans.group_id = #{m_pricing['group_id']} and plans.interval = 'month'")
|
||||
insert <<-SQL
|
||||
INSERT INTO prices (group_id, plan_id, priceable_id, priceable_type, amount, created_at, updated_at)
|
||||
VALUES (#{m_pricing['group_id']}, #{plan_month['id']}, #{m_pricing['machine_id']}, 'Machine', #{m_pricing['month_amount']}, #{time}, #{time})
|
||||
SQL
|
||||
|
||||
insert <<-SQL
|
||||
INSERT INTO prices (group_id, plan_id, priceable_id, priceable_type, amount, created_at, updated_at)
|
||||
VALUES (#{m_pricing['group_id']}, NULL, #{m_pricing['machine_id']}, 'Machine', #{m_pricing['not_subscribe_amount']}, #{time}, #{time})
|
||||
SQL
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
insert <<-SQL
|
||||
DELETE FROM prices
|
||||
SQL
|
||||
end
|
||||
end
|
@ -1,5 +0,0 @@
|
||||
class DropMachinesPricings < ActiveRecord::Migration
|
||||
def up
|
||||
drop_table :machines_pricings
|
||||
end
|
||||
end
|
144
db/schema.rb
144
db/schema.rb
@ -30,23 +30,23 @@ ActiveRecord::Schema.define(version: 20160119131623) do
|
||||
add_index "abuses", ["signaled_type", "signaled_id"], name: "index_abuses_on_signaled_type_and_signaled_id", using: :btree
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
@ -63,7 +63,7 @@ ActiveRecord::Schema.define(version: 20160119131623) 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"
|
||||
@ -81,18 +81,18 @@ ActiveRecord::Schema.define(version: 20160119131623) 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"
|
||||
end
|
||||
|
||||
create_table "components", force: :cascade do |t|
|
||||
t.string "name", limit: 255, null: false
|
||||
t.string "name", null: false
|
||||
end
|
||||
|
||||
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"
|
||||
@ -113,7 +113,7 @@ ActiveRecord::Schema.define(version: 20160119131623) do
|
||||
end
|
||||
|
||||
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"
|
||||
@ -139,10 +139,10 @@ ActiveRecord::Schema.define(version: 20160119131623) do
|
||||
add_index "events_categories", ["event_id"], name: "index_events_categories_on_event_id", using: :btree
|
||||
|
||||
create_table "friendly_id_slugs", force: :cascade do |t|
|
||||
t.string "slug", limit: 255, 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
|
||||
|
||||
@ -152,17 +152,17 @@ ActiveRecord::Schema.define(version: 20160119131623) 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"
|
||||
end
|
||||
|
||||
add_index "groups", ["slug"], name: "index_groups_on_slug", unique: true, using: :btree
|
||||
|
||||
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"
|
||||
@ -175,17 +175,17 @@ ActiveRecord::Schema.define(version: 20160119131623) 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"
|
||||
end
|
||||
@ -194,17 +194,17 @@ ActiveRecord::Schema.define(version: 20160119131623) do
|
||||
add_index "invoices", ["user_id"], name: "index_invoices_on_user_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"
|
||||
end
|
||||
|
||||
add_index "machines", ["slug"], name: "index_machines_on_slug", unique: true, using: :btree
|
||||
@ -220,12 +220,12 @@ ActiveRecord::Schema.define(version: 20160119131623) 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.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.string "receiver_type", limit: 255
|
||||
t.string "receiver_type"
|
||||
t.boolean "is_send", default: false
|
||||
t.jsonb "meta_data", default: {}
|
||||
end
|
||||
@ -268,11 +268,11 @@ ActiveRecord::Schema.define(version: 20160119131623) do
|
||||
add_index "offer_days", ["subscription_id"], name: "index_offer_days_on_subscription_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
|
||||
@ -302,11 +302,11 @@ ActiveRecord::Schema.define(version: 20160119131623) 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"
|
||||
@ -320,7 +320,7 @@ ActiveRecord::Schema.define(version: 20160119131623) do
|
||||
t.integer "project_id"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.string "title", limit: 255
|
||||
t.string "title"
|
||||
end
|
||||
|
||||
add_index "project_steps", ["project_id"], name: "index_project_steps_on_project_id", using: :btree
|
||||
@ -331,22 +331,22 @@ ActiveRecord::Schema.define(version: 20160119131623) do
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.boolean "is_valid", default: false
|
||||
t.string "valid_token", limit: 255
|
||||
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
|
||||
|
||||
@ -382,20 +382,20 @@ ActiveRecord::Schema.define(version: 20160119131623) 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"
|
||||
t.integer "nb_reserve_reduced_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
|
||||
@ -420,8 +420,8 @@ ActiveRecord::Schema.define(version: 20160119131623) do
|
||||
t.datetime "updated_at"
|
||||
t.integer "availability_id"
|
||||
t.datetime "ex_start_at"
|
||||
t.datetime "canceled_at"
|
||||
t.datetime "ex_end_at"
|
||||
t.datetime "canceled_at"
|
||||
t.boolean "offered", default: false
|
||||
end
|
||||
|
||||
@ -430,18 +430,18 @@ ActiveRecord::Schema.define(version: 20160119131623) 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"
|
||||
@ -450,8 +450,8 @@ ActiveRecord::Schema.define(version: 20160119131623) 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
|
||||
@ -459,8 +459,8 @@ ActiveRecord::Schema.define(version: 20160119131623) do
|
||||
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
|
||||
@ -477,8 +477,8 @@ ActiveRecord::Schema.define(version: 20160119131623) 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"
|
||||
@ -496,7 +496,7 @@ ActiveRecord::Schema.define(version: 20160119131623) 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 "expired_at"
|
||||
@ -515,15 +515,15 @@ ActiveRecord::Schema.define(version: 20160119131623) 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 "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"
|
||||
end
|
||||
|
||||
@ -579,30 +579,30 @@ ActiveRecord::Schema.define(version: 20160119131623) do
|
||||
add_index "user_trainings", ["user_id"], name: "index_user_trainings_on_user_id", using: :btree
|
||||
|
||||
create_table "users", force: :cascade do |t|
|
||||
t.string "username", limit: 255
|
||||
t.string "email", limit: 255, default: "", null: false
|
||||
t.string "encrypted_password", limit: 255, default: "", null: false
|
||||
t.string "reset_password_token", limit: 255
|
||||
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.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.string "unconfirmed_email"
|
||||
t.integer "failed_attempts", default: 0, null: false
|
||||
t.string "unlock_token", limit: 255
|
||||
t.string "unlock_token"
|
||||
t.datetime "locked_at"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.boolean "is_allow_contact", default: true
|
||||
t.integer "group_id"
|
||||
t.string "stp_customer_id", limit: 255
|
||||
t.string "slug", limit: 255
|
||||
t.string "stp_customer_id"
|
||||
t.string "username"
|
||||
t.string "slug"
|
||||
t.boolean "is_active", default: true
|
||||
t.boolean "invoicing_disabled", default: false
|
||||
t.string "provider"
|
||||
|
@ -1,64 +0,0 @@
|
||||
Group.create!([
|
||||
{name: "standard, association", slug: "standard"},
|
||||
{name: "étudiant, - de 25 ans, enseignant, demandeur d'emploi", slug: "student"},
|
||||
{name: "artisan, commerçant, chercheur, auto-entrepreneur", slug: "merchant"},
|
||||
{name: "PME, PMI, SARL, SA", slug: "business"}
|
||||
])
|
||||
|
||||
if Category.count == 0
|
||||
Category.create!([
|
||||
{name: "Stage"},
|
||||
{name: "Atelier"}
|
||||
])
|
||||
end
|
||||
|
||||
if StatisticIndex.count == 0
|
||||
StatisticIndex.create!([
|
||||
{id:1, es_type_key:'subscription', label:'Abonnements'},
|
||||
{id:2, es_type_key:'machine', label:'Heures machines'},
|
||||
{id:3, es_type_key:'training', label:'Formations'},
|
||||
{id:4, es_type_key:'event', label:'Ateliers/Stages'},
|
||||
{id:5, es_type_key:'account', label:'Inscriptions', ca: false},
|
||||
{id:6, es_type_key:'project', label:'Projets', ca: false},
|
||||
{id:7, es_type_key:'user', label:'Utilisateurs', table: false, ca: false}
|
||||
])
|
||||
connection = ActiveRecord::Base.connection
|
||||
if connection.instance_values["config"][:adapter] == 'postgresql'
|
||||
connection.execute("SELECT setval('statistic_indices_id_seq', 7);")
|
||||
end
|
||||
end
|
||||
|
||||
if StatisticField.count == 0
|
||||
StatisticField.create!([
|
||||
# available data_types : index, number, date, text, list
|
||||
{key:'trainingId', label:'ID Formation', statistic_index_id: 3, data_type: 'index'},
|
||||
{key:'trainingDate', label:'Date Formation', statistic_index_id: 3, data_type: 'date'},
|
||||
{key:'eventId', label:'ID Évènement', statistic_index_id: 4, data_type: 'index'},
|
||||
{key:'eventDate', label:'Date Évènement', statistic_index_id: 4, data_type: 'date'},
|
||||
{key:'themes', label:'Thèmes', statistic_index_id: 6, data_type: 'list'},
|
||||
{key:'components', label:'Composants', statistic_index_id: 6, data_type: 'list'},
|
||||
{key:'machines', label:'Machines', statistic_index_id: 6, data_type: 'list'},
|
||||
{key:'name', label:'Nom Évènement', statistic_index_id: 4, data_type: 'text'},
|
||||
{key:'userId', label:'ID Utilisateur', statistic_index_id: 7, data_type: 'index'}
|
||||
])
|
||||
end
|
||||
|
||||
if StatisticType.count == 0
|
||||
StatisticType.create!([
|
||||
{id:1, statistic_index_id: 1, key: 'month', label:'Abonnements mensuels', graph: true, simple: true},
|
||||
{id:2, statistic_index_id: 1, key: 'year', label:'Abonnements annuels', graph: true, simple: true},
|
||||
{id:3, statistic_index_id: 2, key: 'booking', label:'Réservations', graph: true, simple: true},
|
||||
{id:4, statistic_index_id: 2, key: 'hour', label:"Nombre d'heures", graph: true, simple: false},
|
||||
{id:5, statistic_index_id: 3, key: 'booking', label:'Réservations', graph: false, simple: true},
|
||||
{id:6, statistic_index_id: 3, key: 'hour', label:"Nombre d'heures", graph: false, simple: false},
|
||||
{id:7, statistic_index_id: 4, key: 'booking', label:'Nombre de places', graph: false, simple: false},
|
||||
{id:8, statistic_index_id: 4, key: 'hour', label:"Nombre d'heures", graph: false, simple: false},
|
||||
{id:9, statistic_index_id: 5, key: 'member', label:'Utilisateurs', graph: true, simple: true},
|
||||
{id:10, statistic_index_id: 6, key: 'project', label:'Projets', graph: false, simple: true},
|
||||
{id:11, statistic_index_id: 7, key: 'revenue', label:"Chiffre d'affaires", graph: false, simple: false}
|
||||
])
|
||||
connection = ActiveRecord::Base.connection
|
||||
if connection.instance_values["config"][:adapter] == 'postgresql'
|
||||
connection.execute("SELECT setval('statistic_types_id_seq', 11);")
|
||||
end
|
||||
end
|
@ -44,3 +44,6 @@ TIME_ZONE=Paris
|
||||
WEEK_STARTING_DAY=monday
|
||||
D3_DATE_FORMAT=%d/%m/%y
|
||||
UIB_DATE_FORMAT=dd/MM/yyyy
|
||||
|
||||
OPENLAB_APP_SECRET: 'fSF9jZEWxjHyqjAzzg34jd92'
|
||||
OPENLAB_APP_ID: 'xLn9CmryyURNNHZiDRYVRXbv'
|
||||
|
@ -31,7 +31,7 @@ namespace :fablab do
|
||||
puts "-> Done"
|
||||
end
|
||||
|
||||
desc "Cancel strip subscriptions"
|
||||
desc "Cancel stripe subscriptions"
|
||||
task cancel_subscriptions: :environment do
|
||||
Subscription.where("expired_at >= ?", Time.now.at_beginning_of_day).each do |s|
|
||||
puts "-> Start cancel subscription of #{s.user.email}"
|
||||
@ -76,9 +76,10 @@ namespace :fablab do
|
||||
|
||||
desc "sync all/one project in elastic search index"
|
||||
task :es_build_projects_index, [:id] => :environment do |task, args|
|
||||
unless Project.__elasticsearch__.client.indices.exists? index: 'fablab'
|
||||
Project.__elasticsearch__.client.indices.create index: Project.index_name, body: { settings: Project.settings.to_hash, mappings: Project.mappings.to_hash }
|
||||
if Project.__elasticsearch__.client.indices.exists? index: 'fablab'
|
||||
Project.__elasticsearch__.client.indices.delete index: 'fablab'
|
||||
end
|
||||
Project.__elasticsearch__.client.indices.create index: Project.index_name, body: { settings: Project.settings.to_hash, mappings: Project.mappings.to_hash }
|
||||
if args.id
|
||||
IndexerWorker.perform_async(:index, id)
|
||||
else
|
||||
@ -170,4 +171,24 @@ namespace :fablab do
|
||||
|
||||
puts "\nUsers successfully notified\n\n"
|
||||
end
|
||||
|
||||
desc "generate fixtures from db"
|
||||
task generate_fixtures: :environment do
|
||||
Rails.application.eager_load!
|
||||
ActiveRecord::Base.descendants.reject { |c| c == ActiveRecord::SchemaMigration or c == PartnerPlan }.each do |ar_base|
|
||||
p "========== #{ar_base} =============="
|
||||
ar_base.dump_fixtures
|
||||
end
|
||||
end
|
||||
|
||||
desc 'clean stripe secrets from VCR cassettes'
|
||||
task clean_cassettes_secrets: :environment do
|
||||
Dir['test/vcr_cassettes/*.yml'].each do |cassette_file|
|
||||
cassette = File.read(cassette_file)
|
||||
cassette.gsub!(Rails.application.secrets.stripe_api_key, 'sk_test_testfaketestfaketestfake')
|
||||
cassette.gsub!(Rails.application.secrets.stripe_publishable_key, 'pk_test_faketestfaketestfaketest')
|
||||
puts cassette
|
||||
File.write(cassette_file, cassette)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
13
lib/tasks/fablab/openlab.rake
Normal file
13
lib/tasks/fablab/openlab.rake
Normal file
@ -0,0 +1,13 @@
|
||||
namespace :fablab do
|
||||
namespace :openlab do
|
||||
task bulk_export: :environment do
|
||||
if Rails.application.secrets.openlab_app_secret.present?
|
||||
Project.find_each do |project|
|
||||
project.openlab_create
|
||||
end
|
||||
else
|
||||
warn "Rails.application.secrets.openlab_app_secret not present. Export can't be done."
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,10 +0,0 @@
|
||||
FactoryGirl.define do
|
||||
factory :abus, :class => 'Abuse' do
|
||||
signaled nil
|
||||
first_name "MyString"
|
||||
last_name "MyString"
|
||||
email ""
|
||||
message "MyText"
|
||||
end
|
||||
|
||||
end
|
@ -1,8 +0,0 @@
|
||||
FactoryGirl.define do
|
||||
factory :auth_provider do
|
||||
name "MyString"
|
||||
type ""
|
||||
status "MyString"
|
||||
end
|
||||
|
||||
end
|
@ -1,6 +0,0 @@
|
||||
#FactoryGirl.define do
|
||||
#factory :availability do
|
||||
|
||||
#end
|
||||
|
||||
#end
|
@ -1,7 +0,0 @@
|
||||
FactoryGirl.define do
|
||||
factory :availability_tag do
|
||||
availability nil
|
||||
tag nil
|
||||
end
|
||||
|
||||
end
|
@ -1,6 +0,0 @@
|
||||
#FactoryGirl.define do
|
||||
#factory :avoir do
|
||||
|
||||
#end
|
||||
|
||||
#end
|
@ -1,6 +0,0 @@
|
||||
#FactoryGirl.define do
|
||||
#factory :credit do
|
||||
|
||||
#end
|
||||
|
||||
#end
|
@ -1,6 +0,0 @@
|
||||
FactoryGirl.define do
|
||||
factory :custom_asset do
|
||||
|
||||
end
|
||||
|
||||
end
|
@ -1,6 +0,0 @@
|
||||
FactoryGirl.define do
|
||||
factory :database_provider do
|
||||
|
||||
end
|
||||
|
||||
end
|
@ -1,6 +0,0 @@
|
||||
#FactoryGirl.define do
|
||||
#factory :invoice do
|
||||
|
||||
#end
|
||||
|
||||
#end
|
@ -1,6 +0,0 @@
|
||||
#FactoryGirl.define do
|
||||
#factory :machine do
|
||||
|
||||
#end
|
||||
|
||||
#end
|
@ -1,10 +0,0 @@
|
||||
FactoryGirl.define do
|
||||
factory :o_auth2_mapping do
|
||||
o_auth2_provider nil
|
||||
resource_url "MyString"
|
||||
local_field "MyString"
|
||||
api_field "MyString"
|
||||
data_type "MyString"
|
||||
end
|
||||
|
||||
end
|
@ -1,11 +0,0 @@
|
||||
FactoryGirl.define do
|
||||
factory :o_auth2_provider do
|
||||
base_url "MyString"
|
||||
token_endpoint "MyString"
|
||||
authorization_endpoint "MyString"
|
||||
client_id "MyString"
|
||||
client_secret "MyString"
|
||||
auth_provider nil
|
||||
end
|
||||
|
||||
end
|
@ -1,6 +0,0 @@
|
||||
#FactoryGirl.define do
|
||||
#factory :plan do
|
||||
|
||||
#end
|
||||
|
||||
#end
|
@ -1,9 +0,0 @@
|
||||
FactoryGirl.define do
|
||||
factory :price do
|
||||
group nil
|
||||
plan nil
|
||||
priceable nil
|
||||
amount 1
|
||||
end
|
||||
|
||||
end
|
@ -1,11 +0,0 @@
|
||||
FactoryGirl.define do
|
||||
sequence(:last_name) { |n| n }
|
||||
|
||||
factory :profile do
|
||||
first_name 'member'
|
||||
last_name { FactoryGirl.generate :last_name }
|
||||
gender true
|
||||
birthday { 30.years.ago }
|
||||
phone '0606060606'
|
||||
end
|
||||
end
|
@ -1,8 +0,0 @@
|
||||
FactoryGirl.define do
|
||||
sequence(:name) { |n| "project#{n}" }
|
||||
|
||||
factory :project do
|
||||
name { FactoryGirl.generate :name }
|
||||
description { Faker::Lorem.paragraph }
|
||||
end
|
||||
end
|
@ -1,6 +0,0 @@
|
||||
#FactoryGirl.define do
|
||||
#factory :reservation do
|
||||
|
||||
#end
|
||||
|
||||
#end
|
@ -1,6 +0,0 @@
|
||||
FactoryGirl.define do
|
||||
factory :stylesheet do
|
||||
contents "MyString"
|
||||
end
|
||||
|
||||
end
|
@ -1,6 +0,0 @@
|
||||
#FactoryGirl.define do
|
||||
#factory :subscription do
|
||||
|
||||
#end
|
||||
|
||||
#end
|
@ -1,6 +0,0 @@
|
||||
FactoryGirl.define do
|
||||
factory :tag do
|
||||
name "MyString"
|
||||
end
|
||||
|
||||
end
|
@ -1,6 +0,0 @@
|
||||
#FactoryGirl.define do
|
||||
#factory :training do
|
||||
|
||||
#end
|
||||
|
||||
#end
|
@ -1,7 +0,0 @@
|
||||
FactoryGirl.define do
|
||||
factory :user_tag do
|
||||
user nil
|
||||
tag nil
|
||||
end
|
||||
|
||||
end
|
@ -1,14 +0,0 @@
|
||||
FactoryGirl.define do
|
||||
sequence(:email) { |n| "member#{n}@sleede.com" }
|
||||
sequence(:username) { |n| "member#{n}" }
|
||||
|
||||
factory :user do
|
||||
email { FactoryGirl.generate :email }
|
||||
username { FactoryGirl.generate :username }
|
||||
password 'sleede22'
|
||||
password_confirmation 'sleede22'
|
||||
association :profile, strategy: :build
|
||||
group { Group.first }
|
||||
end
|
||||
|
||||
end
|
@ -1,9 +0,0 @@
|
||||
class DeviseMailerPreview < ActionMailer::Preview
|
||||
def confirmation_instructions
|
||||
Devise::Mailer.confirmation_instructions(User.last, SecureRandom.hex)
|
||||
end
|
||||
|
||||
def reset_password_instructions
|
||||
Devise::Mailer.reset_password_instructions(User.last, SecureRandom.hex)
|
||||
end
|
||||
end
|
@ -1,7 +0,0 @@
|
||||
class NotificationsMailerPreview < ActionMailer::Preview
|
||||
NotificationType::NAMES.each do |name|
|
||||
define_method name do
|
||||
NotificationsMailer.send_mail_by(Notification.where(notification_type_id: NotificationType.find_by_name(name)).last)
|
||||
end
|
||||
end
|
||||
end
|
@ -1,5 +0,0 @@
|
||||
class UsersMailerPreview < ActionMailer::Preview
|
||||
def notify_user_account_created
|
||||
UsersMailer.notify_user_account_created(User.first, 'wfwwefwefsdfsdf')
|
||||
end
|
||||
end
|
@ -1,5 +0,0 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Abuse, type: :model do
|
||||
pending "add some examples to (or delete) #{__FILE__}"
|
||||
end
|
@ -1,5 +0,0 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe AuthProvider, type: :model do
|
||||
pending "add some examples to (or delete) #{__FILE__}"
|
||||
end
|
@ -1,24 +0,0 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Availability, type: :model do
|
||||
|
||||
describe 'create' do
|
||||
it 'is success with start at, end at and type'
|
||||
it 'is invalid without start at'
|
||||
it 'is invalid without end at'
|
||||
it 'is invalid without type'
|
||||
it 'is invalid type isnt in [training, machines, event]'
|
||||
it 'is invalid without training_ids when type training'
|
||||
it 'is invalid without machine_ids when type machines'
|
||||
end
|
||||
|
||||
it 'should can associate one or many reservations'
|
||||
|
||||
it 'should can destroy if dont any reservations'
|
||||
|
||||
it 'should get a title'
|
||||
|
||||
it 'should set a number of places'
|
||||
|
||||
it 'should be completed when number of reservations equal number of places'
|
||||
end
|
@ -1,5 +0,0 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe AvailabilityTag, type: :model do
|
||||
pending "add some examples to (or delete) #{__FILE__}"
|
||||
end
|
@ -1,9 +0,0 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Avoir, type: :model do
|
||||
it 'should generate a reference after create'
|
||||
|
||||
it 'is invoiced if avoir mode isnt in [stripe, cheque, transfer, none, cash]'
|
||||
|
||||
it 'should can expire user subscription if avoir indicate subscription to expire'
|
||||
end
|
@ -1,11 +0,0 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Credit, type: :model do
|
||||
context 'training' do
|
||||
it 'should is saved with plan'
|
||||
end
|
||||
|
||||
context 'machine' do
|
||||
it 'should is saved with plan and hours'
|
||||
end
|
||||
end
|
@ -1,5 +0,0 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe CustomAsset, type: :model do
|
||||
pending "add some examples to (or delete) #{__FILE__}"
|
||||
end
|
@ -1,5 +0,0 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe DatabaseProvider, type: :model do
|
||||
pending "add some examples to (or delete) #{__FILE__}"
|
||||
end
|
@ -1,9 +0,0 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Invoice, type: :model do
|
||||
it 'should generate a reference after create'
|
||||
|
||||
it 'should generate a invoice pdf'
|
||||
|
||||
it 'should can build a avoir'
|
||||
end
|
@ -1,21 +0,0 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Machine, type: :model do
|
||||
|
||||
describe 'create' do
|
||||
it 'is success with name, image, description and spec'
|
||||
it 'is invalid without name'
|
||||
it 'is invalid without image'
|
||||
it 'is invalid without description'
|
||||
it 'is invalid without spec'
|
||||
it 'should auto generate slug by name'
|
||||
end
|
||||
|
||||
it 'can have many machine files'
|
||||
|
||||
it 'can have many projects'
|
||||
|
||||
it 'can have many trainings'
|
||||
|
||||
it 'should return an amount by user group'
|
||||
end
|
@ -1,5 +0,0 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe OAuth2Mapping, type: :model do
|
||||
pending "add some examples to (or delete) #{__FILE__}"
|
||||
end
|
@ -1,5 +0,0 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe OAuth2Provider, type: :model do
|
||||
pending "add some examples to (or delete) #{__FILE__}"
|
||||
end
|
@ -1,60 +0,0 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Plan, type: :model do
|
||||
let(:group){ Group.new(name: 'groupe test', slug: SecureRandom.hex) }
|
||||
|
||||
describe 'validations' do
|
||||
it 'is success with amount and group' do
|
||||
plan = Plan.new(amount: 500, group: group)
|
||||
expect(plan).to be_valid
|
||||
end
|
||||
|
||||
it 'is invalid without amount' do
|
||||
plan = Plan.new(group: group)
|
||||
expect(plan).to be_invalid
|
||||
end
|
||||
|
||||
it 'is invalid without group' do
|
||||
plan = Plan.new(amount: 500)
|
||||
expect(plan).to be_invalid
|
||||
end
|
||||
end
|
||||
|
||||
context "on creation" do
|
||||
before :each do
|
||||
@plan_id = SecureRandom.hex
|
||||
@plan_name = SecureRandom.hex
|
||||
allow(Stripe::Plan).to receive(:create).and_return(double(id: @plan_id, name: @plan_name))
|
||||
end
|
||||
|
||||
it 'calls Stripe::Plan create method' do
|
||||
plan = Plan.create(amount: 500, interval: 'month', group: group)
|
||||
expect(Stripe::Plan).to have_received :create
|
||||
end
|
||||
|
||||
it 'saves stripe_plan.id' do
|
||||
plan = Plan.create(amount: 500, interval: 'month', group: group)
|
||||
expect(plan.stp_plan_id).to eq(@plan_id)
|
||||
end
|
||||
|
||||
it 'saves stripe_plan.name' do
|
||||
plan = Plan.create(amount: 500, interval: 'month', group: group)
|
||||
expect(plan.name).to eq(@plan_name)
|
||||
end
|
||||
end
|
||||
|
||||
context "on update" do
|
||||
before :each do
|
||||
allow(Stripe::Plan).to receive(:create).and_return(double(id: SecureRandom.hex, name: SecureRandom.hex))
|
||||
end
|
||||
|
||||
let(:plan){ Plan.create(amount: 500, interval: 'month', group: group) }
|
||||
|
||||
describe "update_stripe_plan" do
|
||||
it "should return false if plan already has subscriptions" do
|
||||
allow(plan).to receive(:subscriptions).and_return([1,2])
|
||||
expect(plan.send(:update_stripe_plan)).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,5 +0,0 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Price, type: :model do
|
||||
pending "add some examples to (or delete) #{__FILE__}"
|
||||
end
|
@ -1,30 +0,0 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Project, type: :model do
|
||||
|
||||
describe 'create' do
|
||||
it 'is success with author, name and description'
|
||||
it 'is invalid without author'
|
||||
it 'is invalid without name'
|
||||
it 'is invalid without description'
|
||||
it 'should auto generate slug by name'
|
||||
end
|
||||
|
||||
it 'save as draft by default'
|
||||
|
||||
it 'should can published'
|
||||
|
||||
it 'should have a published time after published'
|
||||
|
||||
it 'can only add one project main image'
|
||||
|
||||
it 'can have many project caos'
|
||||
|
||||
it 'can have many machines'
|
||||
|
||||
it 'can have many components'
|
||||
|
||||
it 'can have many project steps'
|
||||
|
||||
it 'can add a licence'
|
||||
end
|
@ -1,28 +0,0 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Reservation, type: :model do
|
||||
describe 'create' do
|
||||
it 'is success with user, slots and reservable'
|
||||
it 'is invalid if reservable isnt in [Training, Machine, Event]'
|
||||
|
||||
it 'is success with a subscription'
|
||||
|
||||
context 'stripe' do
|
||||
it 'should payment success'
|
||||
it 'is invalid if payment info invalid'
|
||||
end
|
||||
|
||||
context 'satori' do
|
||||
it 'should success'
|
||||
it 'is invalid if payment info invalid'
|
||||
end
|
||||
end
|
||||
|
||||
it 'should update user credit after creation'
|
||||
|
||||
it 'should create a invoice'
|
||||
|
||||
it 'should can set a nb reserve places'
|
||||
|
||||
it 'should can set a nb reserve reduced places'
|
||||
end
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user