1
0
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:
cyril 2016-05-02 17:15:50 +02:00
commit 8864863343
191 changed files with 12988 additions and 1429 deletions

10
CHANGELOG.md Normal file
View 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

15
Gemfile
View File

@ -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
@ -137,4 +136,6 @@ gem 'chroma'
gem 'protected_attributes'
gem 'message_format'
gem 'message_format'
gem 'openlab_ruby'

View File

@ -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

View File

@ -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

View File

@ -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 ###

View File

@ -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
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
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.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) ->
$state.go('app.public.projects_show', {id: project.slug})
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()

View File

@ -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" %>'

View 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
]

View 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 }
])

View File

@ -10,5 +10,5 @@ Application.Services.factory 'Project', ["$resource", ($resource)->
search:
method: 'GET'
url: '/api/projects/search'
isArray: true
isArray: false
]

View File

@ -65,49 +65,73 @@
height: 100%;
}
.box-thumb {
opacity: 0.9;
&:hover { opacity: 1;}
&:hover .box-footer { opacity: 1; }
// component card
.card {
position: relative;
background-size: cover;
background-repeat: no-repeat;
border-radius: 8px;
margin-bottom: 30px;
cursor: pointer;
// todo
display: block;
margin-bottom: .75rem;
background-color: #fff;
border: 1px solid #e5e5e5;
border-radius: .25rem;
height: 325px;
overflow: hidden;
height: 280px;
img {
opacity: 0.9;
margin-bottom: 3rem;
cursor: pointer;
}
.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 {
.card-overlay {
margin: 1.25rem;
position: absolute;
top: 45px;
cursor: pointer;
z-index: 2;
top: 0;
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);
text-align: center;
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;
}
}
.box-footer {
opacity: 0;
position: absolute;
bottom: 10px;
left: 0;
right: 0;
text-align: center;
&:hover {
.card-overlay {
opacity: 1;
}
}
.card-header {
width: 100%;
display: block;
padding: 1.25rem;
.card-header-bg {
background-size: cover;
background-repeat: no-repeat;
background-position: center;
height: 180px;
}
}
.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
}
}
}
@ -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;
}
}

View File

@ -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; }
}

View File

@ -10,19 +10,35 @@
.font-felt { font-family: $font-felt; }
p.font-felt {
}
p, .widget p {
&.font-felt, .font-felt { font-size: rem-calc(18) !important; }
&.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;}

View File

@ -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>

View File

@ -19,87 +19,133 @@
</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">
<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>
</div>
</section>
<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" 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>
<div class="col-md-8" ng-hide="user.merged_at">
<ng-include src="'<%= asset_path 'profile/_token.html' %>'"></ng-include>
<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"
action="{{ actionUrl }}"
ng-upload="submited(content)"
upload-options-enable-rails-csrf="true"
novalidate>
<form role="form"
name="userForm"
class="form-horizontal col-md-8 m-t"
action="{{ actionUrl }}"
ng-upload="submited(content)"
upload-options-enable-rails-csrf="true"
ng-if="!user.auth_token"
novalidate>
<section>
<section class="panel panel-default bg-light m-lg m-t-xs">
<div class="panel-body m-r">
<!-- common fields -->
<ng-include src="'<%= asset_path 'shared/_member_form.html' %>'"></ng-include>
<div class="panel-body m-r">
<!-- common fields -->
<ng-include src="'<%= asset_path 'shared/_member_form.html' %>'"></ng-include>
<div class="row">
<div class="col-sm-3 col-sm-offset-1"></div>
<div class="col-sm-offset-1 col-sm-6">
<!-- group -->
<div class="form-group" ng-class="{'has-error': userForm['user[group_id]'].$dirty && userForm['user[group_id]'].$invalid}">
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-users"></i></span>
<select ng-model="user.group_id" class="form-control" name="user[group_id]" required>
<option value=null translate>{{ 'your_user_s_profile' }}</option>
<option ng-repeat="group in groups" value="{{group.id}}" ng-selected="group.id == user.group_id">{{group.name}}</option>
</select>
</div>
<span class="help-block" ng-show="userForm['user[group_id]'].$dirty && userForm['user[group_id]'].$error.required" translate>{{ 'user_s_profile_is_required' }}</span>
</div>
<div class="row">
<div class="col-sm-3 col-sm-offset-1"></div>
<div class="col-sm-offset-1 col-sm-6">
<!-- group -->
<div class="form-group" ng-class="{'has-error': userForm['user[group_id]'].$dirty && userForm['user[group_id]'].$invalid}">
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-users"></i></span>
<select ng-model="user.group_id" class="form-control" name="user[group_id]" required>
<option value=null translate>{{ 'your_user_s_profile' }}</option>
<option ng-repeat="group in groups" value="{{group.id}}" ng-selected="group.id == user.group_id">{{group.name}}</option>
</select>
<!-- allow contact-->
<div class="form-group" ng-class="{'has-error': userForm['user[is_allow_contact]'].$dirty && userForm['user[is_allow_contact]'].$invalid}">
<input type="checkbox"
name="user[is_allow_contact]"
ng-model="user.is_allow_contact"
value="true"/> {{ 'i_authorize_Fablab_users_registered_on_the_site_to_contact_me' | translate }}
</div>
<!-- accept cgu -->
<div class="form-group" ng-class="{'has-error': userForm.cgu.$dirty && userForm.cgu.$invalid}" ng-show="cgu">
<input type="checkbox"
name="cgu"
ng-model="user.cgu"
value="true"
ng-required="cgu != null"/> {{ 'i_ve_read_and_i_accept_' | translate }} <a href="{{cgu.custom_asset_file_attributes.attachment_url}}" target="_blank" translate>{{ '_the_fablab_policy' }}</a>
</div>
</div>
</div>
</div> <!-- ./panel-body -->
<div class="panel-footer no-padder">
<input type="submit"
value="{{ 'confirm_changes' | translate }}"
class="r-b btn-valid btn btn-warning btn-block p-lg btn-lg text-u-c"
ng-disabled="userForm.$invalid"/>
</div>
<span class="help-block" ng-show="userForm['user[group_id]'].$dirty && userForm['user[group_id]'].$error.required" translate>{{ 'user_s_profile_is_required' }}</span>
</div>
<!-- allow contact-->
<div class="form-group" ng-class="{'has-error': userForm['user[is_allow_contact]'].$dirty && userForm['user[is_allow_contact]'].$invalid}">
<input type="checkbox"
name="user[is_allow_contact]"
ng-model="user.is_allow_contact"
value="true"/> {{ 'i_authorize_Fablab_users_registered_on_the_site_to_contact_me' | translate }}
</div>
<!-- accept cgu -->
<div class="form-group" ng-class="{'has-error': userForm.cgu.$dirty && userForm.cgu.$invalid}" ng-show="cgu">
<input type="checkbox"
name="cgu"
ng-model="user.cgu"
value="true"
ng-required="cgu != null"/> {{ 'i_ve_read_and_i_accept_' | translate }} <a href="{{cgu.custom_asset_file_attributes.attachment_url}}" target="_blank" translate>{{ '_the_fablab_policy' }}</a>
</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>
</div> <!-- ./panel-body -->
<div class="panel-footer no-padder">
<input type="submit"
value="{{ 'confirm_changes' | translate }}"
class="r-b btn-valid btn btn-warning btn-block p-lg btn-lg text-u-c"
ng-disabled="userForm.$invalid"/>
</div>
</section>
</form>
</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>

View File

@ -22,77 +22,100 @@
<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>
<div class="col-md-3 m-b" ng-show="isAuthenticated()">
<select ng-model="search.from" ng-change="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>
</select>
</div>
<span ng-if="!openlab.searchOverWholeNetwork">
<div class="col-md-3 m-b" ng-show="isAuthenticated()">
<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>
</select>
</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">
<option value="" translate>{{ 'all_machines' }}</option>
</select>
</div>
<div class="col-md-3 m-b">
<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">
<option value="" translate>{{ 'all_themes' }}</option>
</select>
</div>
<div class="col-md-3 m-b">
<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">
<option value="" translate>{{ 'all_materials' }}</option>
</select>
</div>
<div class="col-md-3 m-b">
<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)">
<img src="data:image/png;base64," data-src="holder.js/100%x100%/text:&#xf03e;/font:FontAwesome/icon" bs-holder ng-if="!project.project_image">
<div class="card card-project">
<div class="box-content project-caption">
<h1>{{project.name}}</h1>
<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:&#xf03e;/font:FontAwesome/icon" bs-holder ng-if="!project.project_image">
</div>
</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>

View 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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
@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
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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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] = {

View 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

View File

@ -45,88 +45,40 @@ 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
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 = 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
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
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 = 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
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)
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,
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
# === 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

View File

@ -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

View File

@ -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|

View 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

View 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

View 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

View 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

View File

@ -1,3 +1,3 @@
json.admin do
json.reporting do
json.extract! @abuse, :id, :signaled_id, :signaled_type
end

View File

@ -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

View File

@ -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>
<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') %>">
<% 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 -->

View 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

View File

@ -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'

View File

@ -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

View 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

View 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

View File

@ -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?"

View File

@ -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 ?"

View File

@ -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
@ -212,4 +215,4 @@ en:
you_booked_(DATE): "You booked ({{DATE}}):" # angular interpolation
book: "Book"
change_the_reservation: "Change the reservation"
you_can_shift_this_reservation_on_the_following_slots: "You can shift this reservation on the following slots:"
you_can_shift_this_reservation_on_the_following_slots: "You can shift this reservation on the following slots:"

View File

@ -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
@ -212,4 +215,4 @@ fr:
you_booked_(DATE): "Vous avez réservé ({{DATE}}) :" # angular interpolation
book: "Réserver"
change_the_reservation: "Modifier la réservation"
you_can_shift_this_reservation_on_the_following_slots: "Vous pouvez déplacer cette réservation sur les créneaux suivants :"
you_can_shift_this_reservation_on_the_following_slots: "Vous pouvez déplacer cette réservation sur les créneaux suivants :"

View File

@ -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"

View File

@ -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"

View File

@ -27,6 +27,7 @@ Rails.application.routes.draw do
get :search
end
end
resources :openlab_projects, only: :index
resources :machines
resources :components
resources :themes

View File

@ -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"] %>

View File

@ -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

View File

@ -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

View File

@ -1,5 +0,0 @@
class DropMachinesPricings < ActiveRecord::Migration
def up
drop_table :machines_pricings
end
end

View File

@ -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,11 +63,11 @@ 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"
t.boolean "destroying", default: false
t.boolean "destroying", default: false
end
create_table "availability_tags", force: :cascade do |t|
@ -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.integer "sluggable_id", null: false
t.string "slug", null: false
t.integer "sluggable_id", null: false
t.string "sluggable_type", limit: 50
t.string "scope", limit: 255
t.string "scope"
t.datetime "created_at"
end
@ -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,14 +220,14 @@ 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.boolean "is_read", default: false
t.datetime "created_at"
t.datetime "updated_at"
t.string "receiver_type", limit: 255
t.boolean "is_send", default: false
t.jsonb "meta_data", default: {}
t.string "receiver_type"
t.boolean "is_send", default: false
t.jsonb "meta_data", default: {}
end
add_index "notifications", ["notification_type_id"], name: "index_notifications_on_notification_type_id", using: :btree
@ -268,20 +268,20 @@ 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
t.boolean "is_rolling", default: true
t.integer "training_credit_nb", default: 0
t.boolean "is_rolling", default: true
t.text "description"
t.string "type"
t.string "base_name"
t.integer "ui_weight", default: 0
t.integer "interval_count", default: 1
t.integer "ui_weight", default: 0
t.integer "interval_count", default: 1
end
add_index "plans", ["group_id"], name: "index_plans_on_group_id", using: :btree
@ -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
@ -330,23 +330,23 @@ ActiveRecord::Schema.define(version: 20160119131623) do
t.integer "user_id"
t.datetime "created_at"
t.datetime "updated_at"
t.boolean "is_valid", default: false
t.string "valid_token", limit: 255
t.boolean "is_valid", default: false
t.string "valid_token"
end
add_index "project_users", ["project_id"], name: "index_project_users_on_project_id", using: :btree
add_index "project_users", ["user_id"], name: "index_project_users_on_user_id", using: :btree
create_table "projects", force: :cascade do |t|
t.string "name", limit: 255
t.string "name"
t.text "description"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "author_id"
t.text "tags"
t.integer "licence_id"
t.string "state", limit: 255
t.string "slug", limit: 255
t.string "state"
t.string "slug"
t.datetime "published_at"
end
@ -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,17 +450,17 @@ 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
t.boolean "ca", default: true
t.boolean "table", default: true
t.boolean "ca", default: true
end
create_table "statistic_sub_types", force: :cascade do |t|
t.string "key", limit: 255
t.string "label", limit: 255
t.string "key"
t.string "label"
t.datetime "created_at"
t.datetime "updated_at"
end
@ -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,32 +579,32 @@ 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.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip", limit: 255
t.string "last_sign_in_ip", limit: 255
t.string "confirmation_token", limit: 255
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.string "confirmation_token"
t.datetime "confirmed_at"
t.datetime "confirmation_sent_at"
t.string "unconfirmed_email", limit: 255
t.integer "failed_attempts", default: 0, null: false
t.string "unlock_token", limit: 255
t.string "unconfirmed_email"
t.integer "failed_attempts", default: 0, null: false
t.string "unlock_token"
t.datetime "locked_at"
t.datetime "created_at"
t.datetime "updated_at"
t.boolean "is_allow_contact", default: true
t.boolean "is_allow_contact", default: true
t.integer "group_id"
t.string "stp_customer_id", limit: 255
t.string "slug", limit: 255
t.boolean "is_active", default: true
t.boolean "invoicing_disabled", default: false
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"
t.string "uid"
t.string "auth_token"

View File

@ -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

View File

@ -43,4 +43,7 @@ ELASTICSEARCH_LANGUAGE_ANALYZER=french
TIME_ZONE=Paris
WEEK_STARTING_DAY=monday
D3_DATE_FORMAT=%d/%m/%y
UIB_DATE_FORMAT=dd/MM/yyyy
UIB_DATE_FORMAT=dd/MM/yyyy
OPENLAB_APP_SECRET: 'fSF9jZEWxjHyqjAzzg34jd92'
OPENLAB_APP_ID: 'xLn9CmryyURNNHZiDRYVRXbv'

View File

@ -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

View 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

View File

@ -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

View File

@ -1,8 +0,0 @@
FactoryGirl.define do
factory :auth_provider do
name "MyString"
type ""
status "MyString"
end
end

View File

@ -1,6 +0,0 @@
#FactoryGirl.define do
#factory :availability do
#end
#end

View File

@ -1,7 +0,0 @@
FactoryGirl.define do
factory :availability_tag do
availability nil
tag nil
end
end

View File

@ -1,6 +0,0 @@
#FactoryGirl.define do
#factory :avoir do
#end
#end

View File

@ -1,6 +0,0 @@
#FactoryGirl.define do
#factory :credit do
#end
#end

View File

@ -1,6 +0,0 @@
FactoryGirl.define do
factory :custom_asset do
end
end

View File

@ -1,6 +0,0 @@
FactoryGirl.define do
factory :database_provider do
end
end

View File

@ -1,6 +0,0 @@
#FactoryGirl.define do
#factory :invoice do
#end
#end

View File

@ -1,6 +0,0 @@
#FactoryGirl.define do
#factory :machine do
#end
#end

View File

@ -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

View File

@ -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

View File

@ -1,6 +0,0 @@
#FactoryGirl.define do
#factory :plan do
#end
#end

View File

@ -1,9 +0,0 @@
FactoryGirl.define do
factory :price do
group nil
plan nil
priceable nil
amount 1
end
end

View File

@ -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

View File

@ -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

View File

@ -1,6 +0,0 @@
#FactoryGirl.define do
#factory :reservation do
#end
#end

View File

@ -1,6 +0,0 @@
FactoryGirl.define do
factory :stylesheet do
contents "MyString"
end
end

View File

@ -1,6 +0,0 @@
#FactoryGirl.define do
#factory :subscription do
#end
#end

View File

@ -1,6 +0,0 @@
FactoryGirl.define do
factory :tag do
name "MyString"
end
end

View File

@ -1,6 +0,0 @@
#FactoryGirl.define do
#factory :training do
#end
#end

View File

@ -1,7 +0,0 @@
FactoryGirl.define do
factory :user_tag do
user nil
tag nil
end
end

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,5 +0,0 @@
class UsersMailerPreview < ActionMailer::Preview
def notify_user_account_created
UsersMailer.notify_user_account_created(User.first, 'wfwwefwefsdfsdf')
end
end

View File

@ -1,5 +0,0 @@
require 'rails_helper'
RSpec.describe Abuse, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end

View File

@ -1,5 +0,0 @@
require 'rails_helper'
RSpec.describe AuthProvider, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end

View File

@ -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

View File

@ -1,5 +0,0 @@
require 'rails_helper'
RSpec.describe AvailabilityTag, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end

View File

@ -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

View File

@ -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

View File

@ -1,5 +0,0 @@
require 'rails_helper'
RSpec.describe CustomAsset, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end

View File

@ -1,5 +0,0 @@
require 'rails_helper'
RSpec.describe DatabaseProvider, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end

View File

@ -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

View File

@ -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

View File

@ -1,5 +0,0 @@
require 'rails_helper'
RSpec.describe OAuth2Mapping, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end

View File

@ -1,5 +0,0 @@
require 'rails_helper'
RSpec.describe OAuth2Provider, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end

View File

@ -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

View File

@ -1,5 +0,0 @@
require 'rails_helper'
RSpec.describe Price, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end

View File

@ -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

View File

@ -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