merge to master

This commit is contained in:
Jordi Pujol i Larena
2015-03-09 22:10:24 +01:00
95 changed files with 1140 additions and 844 deletions

1
.gitignore vendored
View File

@ -39,3 +39,4 @@ sgemset
# Ignore .vagrant folder with images
.vagrant
.metadata

1
.ruby-version Normal file
View File

@ -0,0 +1 @@
2.2.0

View File

@ -1,7 +1,7 @@
language: ruby
rvm:
- 2.1
- 2.2
env: DATABASE_URL=postgres://postgres@localhost/timeoverflow_test
before_script:
- psql -c 'create database timeoverflow_test;' -U postgres
- RAILS_ENV=test bundle exec rake db:migrate
- RAILS_ENV=test bundle exec rake db:migrate

45
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,45 @@
Entorno de desarrollo aconsejado (para mac)
===========================================
iTerm2 - un terminal mucho más completo (<http://iterm2.com>). Especialmente recomendado activar
la opción "Working directory -> Reuse previous session's directory" para el perfil por defecto,
para jugar fácilmente con múltiples pestañas en la misma directory.
SublimeText2 - un editor muy muy avanzado (<http://www.sublimetext.com>). Instalar también
PackageControl para luego instalar extensiones de forma muy fácil (seguir instrucciones en
<https://packagecontrol.io/installation#st2>). Otro editor que promete es Atom (<https://atom.io>)
aunque no es tan maduro, tiene unos grandes desarrolladores detrás. Ambos permiten - y es muy
importante - evitar que se cuelen TABs en el código.
Homebrew - para instalar todo tipo de paquetes adicionales (<http://brew.sh>).
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
YADR - una biblioteca de shortcuts y mejoras para zsh y la línea de comando de ruby
(<https://github.com/skwp/dotfiles>). No hacen falta las opciones para `vim`, sobretodo si uno
no está acostumbrado a vim, la opción "vimification of command line tools" puede ser mortal!
sh -c "`curl -fsSL https://raw.githubusercontent.com/skwp/dotfiles/master/install.sh`" -s ask
Instalar varios paquetes por medio de homebrew
brew install git rbenv ruby-build postgresql
Instalar ruby
rbenv install 2.2.0
rbenv global 2.2.0
Añadir la siguiente línea al fichero `.profile` para tener rbenv activado) y reiniciar
el terminal.
eval "$(rbenv init -)"
Actualizar rubygems e instalar bundler
gem update --system
gem install bundler

View File

@ -1,5 +1,5 @@
timeoverflow:
ruby_version: 2.1.4
ruby_version: 2.2.0
environment: production # RAILS_ENV
monitoring_email: saverio.trioni@gmail.com
domains:
@ -12,7 +12,7 @@ timeoverflow:
# delayed_job: 1
# sidekiq: 1
# clockwork: on
# whenever: on
whenever: on
# elasticsearch: on
databases:
- postgresql

36
Gemfile
View File

@ -1,71 +1,52 @@
source 'https://rubygems.org'
ruby '2.2.0'
gem 'rails', '~> 4.1'
gem 'rails', '~> 4.2'
gem 'rails-i18n'
gem 'rails_12factor'
gem "rdiscount"
gem 'high_voltage', '~> 2.1.0'
gem 'activeadmin', github: 'gregbell/active_admin'
gem 'activeadmin', github: 'activeadmin'
gem 'has_scope'
gem 'inherited_resources'
gem 'responders', '~> 2.0'
gem 'pundit'
gem 'pg', '0.17.1'
gem 'hstore_translate'
gem 'dalli'
gem "sass-rails", "4.0.5"
gem 'coffee-rails'
gem 'ngannotate-rails'
gem 'uglifier', '>= 1.0.3'
gem 'jquery-rails'
gem "devise_browserid_authenticatable"
gem "http_accept_language"
gem 'thin'
gem 'foreman'
gem 'dotenv-rails'
gem 'kaminari'
gem 'textacular', "~> 3.0", require: 'textacular/rails'
gem 'textacular', "3.2.1", require: 'textacular/rails'
# To use debugger
# gem 'debugger'
gem "haml-rails"
gem 'turbolinks'
gem "simple_form", ">= 3.0.0"
gem "awesome_print"
gem "jbuilder"
gem "paranoia"
gem "rest-client"
gem 'memcachier'
gem 'rollbar'
gem "rails-erd", group: :development
gem 'travis-lint'
gem 'faker'
gem 'fabrication'
gem "shelly-dependencies"
gem 'whenever', :require => false
group :development do
gem "binding_of_caller"
gem "better_errors"
gem "rubocop"
gem "haml-lint"
gem 'web-console', '~> 2.0'
end
group :development, :test do
@ -77,4 +58,7 @@ group :test do
# Needed for TravisCI
gem 'rake'
gem "database_cleaner"
gem 'shoulda', ">= 3.5"
gem 'fabrication'
gem 'faker'
end

View File

@ -1,67 +1,74 @@
GIT
remote: git://github.com/gregbell/active_admin.git
revision: b5ce6af4e5538010ff02387be04c2d60b6b62127
remote: git://github.com/activeadmin/activeadmin.git
revision: 60914c887942cb34d737428fb9813f69bb8ae3ee
specs:
activeadmin (1.0.0.pre)
arbre (~> 1.0)
arbre (~> 1.0, >= 1.0.2)
bourbon
coffee-rails
formtastic (~> 2.3.0.rc3)
inherited_resources (~> 1.3)
formtastic (~> 3.1)
formtastic_i18n
inherited_resources (~> 1.6)
jquery-rails
jquery-ui-rails
jquery-ui-rails (~> 5.0)
kaminari (~> 0.15)
rails (>= 3.2, < 4.2)
ransack (~> 1.0)
rails (>= 3.2, < 5.0)
ransack (~> 1.3)
sass-rails
GEM
remote: https://rubygems.org/
specs:
actionmailer (4.1.1)
actionpack (= 4.1.1)
actionview (= 4.1.1)
mail (~> 2.5.4)
actionpack (4.1.1)
actionview (= 4.1.1)
activesupport (= 4.1.1)
rack (~> 1.5.2)
actionmailer (4.2.0)
actionpack (= 4.2.0)
actionview (= 4.2.0)
activejob (= 4.2.0)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 1.0, >= 1.0.5)
actionpack (4.2.0)
actionview (= 4.2.0)
activesupport (= 4.2.0)
rack (~> 1.6.0)
rack-test (~> 0.6.2)
actionview (4.1.1)
activesupport (= 4.1.1)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.1)
actionview (4.2.0)
activesupport (= 4.2.0)
builder (~> 3.1)
erubis (~> 2.7.0)
activemodel (4.1.1)
activesupport (= 4.1.1)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.1)
activejob (4.2.0)
activesupport (= 4.2.0)
globalid (>= 0.3.0)
activemodel (4.2.0)
activesupport (= 4.2.0)
builder (~> 3.1)
activerecord (4.1.1)
activemodel (= 4.1.1)
activesupport (= 4.1.1)
arel (~> 5.0.0)
activerecord-postgres-hstore (0.7.6)
activerecord (>= 3.1)
pg-hstore (>= 1.1.5)
rake
activesupport (4.1.1)
i18n (~> 0.6, >= 0.6.9)
activerecord (4.2.0)
activemodel (= 4.2.0)
activesupport (= 4.2.0)
arel (~> 6.0)
activesupport (4.2.0)
i18n (~> 0.7)
json (~> 1.7, >= 1.7.7)
minitest (~> 5.1)
thread_safe (~> 0.1)
thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1)
arbre (1.0.2)
arbre (1.0.3)
activesupport (>= 3.0.0)
arel (5.0.1.20140414130214)
arel (6.0.0)
ast (2.0.0)
astrolabe (1.3.0)
parser (>= 2.2.0.pre.3, < 3.0)
awesome_print (1.2.0)
bcrypt (3.1.7)
better_errors (1.0.1)
awesome_print (1.6.1)
bcrypt (3.1.9)
better_errors (2.1.1)
coderay (>= 1.0.0)
erubis (>= 2.6.6)
rack (>= 0.9.0)
binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1)
bourbon (3.2.3)
bourbon (3.2.4)
sass (~> 3.2)
thor
builder (3.2.2)
@ -71,218 +78,255 @@ GEM
rack (>= 1.0.0)
rack-test (>= 0.5.4)
xpath (~> 2.0)
choice (0.1.6)
choice (0.1.7)
chronic (0.10.2)
coderay (1.1.0)
coffee-rails (4.0.1)
coffee-rails (4.1.0)
coffee-script (>= 2.2.0)
railties (>= 4.0.0, < 5.0)
coffee-script (2.2.0)
coffee-script (2.3.0)
coffee-script-source
execjs
coffee-script-source (1.7.0)
coffee-script-source (1.8.0)
daemons (1.1.9)
dalli (2.6.4)
database_cleaner (1.2.0)
dalli (2.7.2)
database_cleaner (1.4.0)
debug_inspector (0.0.2)
devise (3.2.4)
devise (3.4.1)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 3.2.6, < 5)
responders
thread_safe (~> 0.1)
warden (~> 1.2.3)
devise_browserid_authenticatable (1.3.2)
devise (>= 2.1)
diff-lcs (1.2.5)
dotenv (0.9.0)
dotenv-rails (0.9.0)
dotenv (= 0.9.0)
dotenv (1.0.2)
dotenv-rails (1.0.2)
dotenv (= 1.0.2)
erubis (2.7.0)
eventmachine (1.0.3)
execjs (2.0.2)
fabrication (2.9.1)
faker (1.2.0)
eventmachine (1.0.7)
execjs (2.2.2)
fabrication (2.11.3)
faker (1.4.3)
i18n (~> 0.5)
foreman (0.63.0)
dotenv (>= 0.7)
thor (>= 0.13.6)
formtastic (2.3.1)
actionpack (>= 3.0)
haml (4.0.5)
foreman (0.77.0)
dotenv (~> 1.0.2)
thor (~> 0.19.1)
formtastic (3.1.3)
actionpack (>= 3.2.13)
formtastic_i18n (0.1.1)
globalid (0.3.0)
activesupport (>= 4.1.0)
haml (4.1.0.beta.1)
tilt
haml-lint (0.8.0)
haml-lint (0.10.0)
haml (~> 4.0)
rubocop (>= 0.25.0)
sysexits (~> 1.1)
haml-rails (0.5.3)
haml-rails (0.6.0)
actionpack (>= 4.0.1)
activesupport (>= 4.0.1)
haml (>= 3.1, < 5.0)
html2haml (>= 1.0.1)
railties (>= 4.0.1)
has_scope (0.6.0.rc)
has_scope (0.6.0)
actionpack (>= 3.2, < 5)
activesupport (>= 3.2, < 5)
hashr (0.0.22)
high_voltage (2.1.0)
hike (1.2.3)
hstore_translate (0.0.12)
hpricot (0.8.6)
hstore_translate (1.0.0)
activerecord (>= 3.1.0)
activerecord-postgres-hstore (~> 0.7.0)
pg
html2haml (1.0.1)
erubis (~> 2.7.0)
haml (>= 4.0.0.rc.1)
hpricot (~> 0.8.6)
ruby_parser (~> 3.1.1)
http_accept_language (2.0.2)
i18n (0.6.11)
inherited_resources (1.4.1)
i18n (0.7.0)
inherited_resources (1.6.0)
actionpack (>= 3.2, < 5)
has_scope (~> 0.6.0.rc)
responders (~> 1.0.0.rc)
jbuilder (1.5.1)
activesupport (>= 3.0.0)
multi_json (>= 1.2.0)
jquery-rails (3.1.0)
railties (>= 3.0, < 5.0)
railties (>= 3.2, < 5)
responders
jbuilder (2.2.6)
activesupport (>= 3.0.0, < 5)
multi_json (~> 1.2)
jquery-rails (4.0.3)
rails-dom-testing (~> 1.0)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
jquery-ui-rails (4.2.1)
jquery-ui-rails (5.0.3)
railties (>= 3.2.16)
json (1.8.1)
kaminari (0.15.1)
json (1.8.2)
kaminari (0.16.1)
actionpack (>= 3.0.0)
activesupport (>= 3.0.0)
mail (2.5.4)
mime-types (~> 1.16)
treetop (~> 1.4.8)
loofah (2.0.1)
nokogiri (>= 1.5.9)
mail (2.6.3)
mime-types (>= 1.16, < 3)
memcachier (0.0.2)
mime-types (1.25.1)
mini_portile (0.6.1)
minitest (5.4.3)
mime-types (2.4.3)
mini_portile (0.6.2)
minitest (5.5.1)
multi_json (1.10.1)
ngannotate-rails (0.9.2)
netrc (0.10.2)
ngannotate-rails (0.14.1)
execjs
rails (>= 3.1)
nokogiri (1.6.4.1)
nokogiri (1.6.6.2)
mini_portile (~> 0.6.0)
orm_adapter (0.5.0)
paranoia (2.0.0)
paranoia (2.0.4)
activerecord (~> 4.0)
parser (2.2.0.pre.8)
parser (2.2.0.2)
ast (>= 1.1, < 3.0)
slop (~> 3.4, >= 3.4.5)
pg (0.17.1)
pg-hstore (1.2.0)
polyamorous (1.0.0)
polyamorous (1.1.0)
activerecord (>= 3.0)
polyglot (0.3.4)
powerpack (0.0.9)
pundit (0.3.0)
activesupport (>= 3.0.0)
rack (1.5.2)
rack-test (0.6.2)
rack (1.6.0)
rack-test (0.6.3)
rack (>= 1.0)
rails (4.1.1)
actionmailer (= 4.1.1)
actionpack (= 4.1.1)
actionview (= 4.1.1)
activemodel (= 4.1.1)
activerecord (= 4.1.1)
activesupport (= 4.1.1)
rails (4.2.0)
actionmailer (= 4.2.0)
actionpack (= 4.2.0)
actionview (= 4.2.0)
activejob (= 4.2.0)
activemodel (= 4.2.0)
activerecord (= 4.2.0)
activesupport (= 4.2.0)
bundler (>= 1.3.0, < 2.0)
railties (= 4.1.1)
sprockets-rails (~> 2.0)
railties (= 4.2.0)
sprockets-rails
rails-deprecated_sanitizer (1.0.3)
activesupport (>= 4.2.0.alpha)
rails-dom-testing (1.0.5)
activesupport (>= 4.2.0.beta, < 5.0)
nokogiri (~> 1.6.0)
rails-deprecated_sanitizer (>= 1.0.1)
rails-erd (1.1.0)
activerecord (>= 3.0)
activesupport (>= 3.0)
choice (~> 0.1.6)
ruby-graphviz (~> 1.0.4)
rails-i18n (4.0.2)
rails-html-sanitizer (1.0.1)
loofah (~> 2.0)
rails-i18n (4.0.3)
i18n (~> 0.6)
rails (>= 4.0)
rails_12factor (0.0.2)
railties (~> 4.0)
rails_12factor (0.0.3)
rails_serve_static_assets
rails_stdout_logging
rails_serve_static_assets (0.0.1)
rails_serve_static_assets (0.0.3)
rails_stdout_logging (0.0.3)
railties (4.1.1)
actionpack (= 4.1.1)
activesupport (= 4.1.1)
railties (4.2.0)
actionpack (= 4.2.0)
activesupport (= 4.2.0)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
rainbow (2.0.0)
rake (10.4.0)
ransack (1.2.3)
rake (10.4.2)
ransack (1.6.3)
actionpack (>= 3.0)
activerecord (>= 3.0)
activesupport (>= 3.0)
i18n
polyamorous (~> 1.0.0)
rdiscount (2.1.7)
responders (1.0.0)
railties (>= 3.2, < 5)
rest-client (1.6.7)
mime-types (>= 1.16)
rollbar (0.12.18)
polyamorous (~> 1.1)
rdiscount (2.1.7.1)
responders (2.0.2)
railties (>= 4.2.0.alpha, < 5)
rest-client (1.7.2)
mime-types (>= 1.16, < 3.0)
netrc (~> 0.7)
rollbar (1.3.2)
multi_json (~> 1.3)
rspec-core (2.14.8)
rspec-expectations (2.14.5)
rspec-collection_matchers (1.1.2)
rspec-expectations (>= 2.99.0.beta1)
rspec-core (2.99.2)
rspec-expectations (2.99.2)
diff-lcs (>= 1.1.3, < 2.0)
rspec-mocks (2.14.6)
rspec-rails (2.14.2)
rspec-mocks (2.99.2)
rspec-rails (2.99.0)
actionpack (>= 3.0)
activemodel (>= 3.0)
activesupport (>= 3.0)
railties (>= 3.0)
rspec-core (~> 2.14.0)
rspec-expectations (~> 2.14.0)
rspec-mocks (~> 2.14.0)
rubocop (0.27.1)
rspec-collection_matchers
rspec-core (~> 2.99.0)
rspec-expectations (~> 2.99.0)
rspec-mocks (~> 2.99.0)
rubocop (0.28.0)
astrolabe (~> 1.3)
parser (>= 2.2.0.pre.7, < 3.0)
powerpack (~> 0.0.6)
rainbow (>= 1.99.1, < 3.0)
ruby-progressbar (~> 1.4)
ruby-graphviz (1.0.9)
ruby-progressbar (1.7.0)
ruby-progressbar (1.7.1)
ruby_parser (3.1.3)
sexp_processor (~> 4.1)
sass (3.2.19)
sass-rails (4.0.5)
railties (>= 4.0.0, < 5.0)
sass (~> 3.2.2)
sprockets (~> 2.8, < 3.0)
sprockets-rails (~> 2.0)
shelly-dependencies (0.2.3)
sexp_processor (4.4.4)
shelly-dependencies (0.2.4)
rake
thin
simple_form (3.0.2)
shoulda (3.5.0)
shoulda-context (~> 1.0, >= 1.0.1)
shoulda-matchers (>= 1.4.1, < 3.0)
shoulda-context (1.2.1)
shoulda-matchers (2.8.0)
activesupport (>= 3.0.0)
simple_form (3.1.0)
actionpack (~> 4.0)
activemodel (~> 4.0)
slop (3.6.0)
sprockets (2.12.3)
hike (~> 1.2)
multi_json (~> 1.0)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
sprockets-rails (2.2.1)
sprockets-rails (2.2.2)
actionpack (>= 3.0)
activesupport (>= 3.0)
sprockets (>= 2.8, < 4.0)
sysexits (1.2.0)
textacular (3.2.0)
activerecord (>= 3.0, < 4.2)
thin (1.6.2)
daemons (>= 1.0.9)
eventmachine (>= 1.0.0)
rack (>= 1.0.0)
textacular (3.2.1)
activerecord (>= 3.0, < 5.0)
thin (1.6.3)
daemons (~> 1.0, >= 1.0.9)
eventmachine (~> 1.0)
rack (~> 1.0)
thor (0.19.1)
thread_safe (0.3.4)
tilt (1.4.1)
travis-lint (1.7.0)
hashr (~> 0.0.22)
treetop (1.4.15)
polyglot
polyglot (>= 0.3.1)
turbolinks (1.3.0)
travis-lint (2.0.0)
json
turbolinks (2.5.3)
coffee-rails
tzinfo (1.2.2)
thread_safe (~> 0.1)
uglifier (2.2.1)
uglifier (2.7.0)
execjs (>= 0.3.0)
multi_json (~> 1.0, >= 1.0.2)
json (>= 1.8.0)
warden (1.2.3)
rack (>= 1.0)
web-console (2.0.0)
activemodel (~> 4.0)
binding_of_caller (>= 0.7.2)
railties (~> 4.0)
sprockets-rails (>= 2.0, < 4.0)
whenever (0.9.4)
chronic (>= 0.6.3)
xpath (2.0.0)
nokogiri (~> 1.3)
@ -309,7 +353,6 @@ DEPENDENCIES
high_voltage (~> 2.1.0)
hstore_translate
http_accept_language
inherited_resources
jbuilder
jquery-rails
kaminari
@ -318,21 +361,25 @@ DEPENDENCIES
paranoia
pg (= 0.17.1)
pundit
rails (~> 4.1)
rails (~> 4.2)
rails-erd
rails-i18n
rails_12factor
rake
rdiscount
responders (~> 2.0)
rest-client
rollbar
rspec-rails (~> 2.14)
rubocop
sass-rails (= 4.0.5)
shelly-dependencies
shoulda (>= 3.5)
simple_form (>= 3.0.0)
textacular (~> 3.0)
textacular (= 3.2.1)
thin
travis-lint
turbolinks
uglifier (>= 1.0.3)
web-console (~> 2.0)
whenever

View File

@ -15,7 +15,7 @@ con tres niveles de privilegios: superadmin, admin (solo gestiona un Banco) y mi
TimeOverflow está dividido en 2 grandes bloques:
* **Sistema de gestión**
* **Red social y banca on-line**
* **Red social y banca on-line para los usuarios**
*NOTA: Un Banco de Tiempo físico puede funcionar solo con el bloque de gestión, podría ocurrir, por ejemplo, que la inmensa*
*mayoria de sus miembros no usen Internet y podrían querer funcionar solo con listados, cheques y una oficina de gestión*
@ -73,17 +73,9 @@ OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
Wireframes
=======
https://moqups.com/sseerrggii/JIu1Z1eD/
Notas para desarrollo
=====================
Únete al equipo de colaboradores!! Gestionamos las tareas en Redbooth.com https://redbooth.com/public/timeoverflow
Únete al equipo de colaboradores!! Gestionamos las tareas en Redbooth.com
Si quieres probar la plataforma en local, necesitas PostgreSQL y ruby en sus últimas versiones.

View File

@ -1,5 +1,4 @@
ActiveAdmin.register Category do
index do
selectable_column
id_column
@ -24,7 +23,7 @@ ActiveAdmin.register Category do
I18n.available_locales.map do |loc|
next unless cat.send("name_#{loc}")
content_tag(:strong, "#{loc}: ") +
content_tag(:span, cat.send("name_#{loc}"))
content_tag(:span, cat.send("name_#{loc}"))
end.compact.sum
end
end
@ -33,5 +32,4 @@ ActiveAdmin.register Category do
end
permit_params :name
end

View File

@ -1,9 +1,7 @@
ActiveAdmin.register_page "Dashboard" do
menu priority: 1, label: proc { I18n.t("active_admin.dashboard") }
menu :priority => 1, :label => proc{ I18n.t("active_admin.dashboard") }
content :title => proc{ I18n.t("active_admin.dashboard") } do
content title: proc { I18n.t("active_admin.dashboard") } do
columns do
column do
panel "Recent Organizations" do
@ -31,6 +29,5 @@ ActiveAdmin.register_page "Dashboard" do
end
end
end
end
end

View File

@ -1,7 +1,7 @@
ActiveAdmin.register Document do
permit_params *Document.attribute_names
index do |t|
index do
selectable_column
id_column
column :documentable

View File

@ -2,7 +2,9 @@ ActiveAdmin.register Organization do
index do
id_column
column :name
column :created_at do |organization| l organization.created_at.to_date, format: :short end
column :created_at do |organization|
l organization.created_at.to_date, format: :short
end
column :city
column :neighborhood
column :email
@ -26,9 +28,8 @@ ActiveAdmin.register Organization do
end
filter :name
filter :city, as: :select, collection: -> { Organization.pluck('city').uniq }
filter :city, as: :select, collection: -> { Organization.pluck(:city).uniq }
filter :neighborhood
permit_params *Organization.attribute_names
end

View File

@ -1,11 +1,12 @@
ActiveAdmin.register Post do
index do
selectable_column
id_column
column :class
column :title
column :created_at do |post| l post.created_at.to_date, format: :short end
column :created_at do |post|
l post.created_at.to_date, format: :short
end
column :user
column :category
column :tag_list
@ -33,5 +34,4 @@ ActiveAdmin.register Post do
filter :organization
filter :category
filter :created_at
end

View File

@ -1,18 +1,19 @@
# coding: utf-8
ActiveAdmin.register User do
action_item :only => :index do
link_to I18n.t("active_admin.users.upload_from_csv"), :action => 'upload_csv'
action_item only: :index do
link_to I18n.t("active_admin.users.upload_from_csv"), action: "upload_csv"
end
collection_action :upload_csv do
render "admin/csv/upload_csv"
end
collection_action :import_csv, :method => :post do
errors = CsvDb.convert_save(params[:dump][:organization_id], params[:dump][:file])
collection_action :import_csv, method: :post do
errors = CsvDb.convert_save(params[:dump][:organization_id],
params[:dump][:file])
flash[:error] = errors.join("<br/>").html_safe if errors.present?
redirect_to :action => :index
redirect_to action: :index
end
index do
@ -37,7 +38,7 @@ ActiveAdmin.register User do
f.inputs "Admin Details" do
f.input :username
f.input :email
f.input :gender, :as => :select, :collection => User::GENDERS
f.input :gender, as: :select, collection: User::GENDERS
f.input :identity_document
end
f.inputs "Members" do
@ -72,5 +73,6 @@ ActiveAdmin.register User do
end
end
permit_params *User.attribute_names, members_attributes: Member.attribute_names
permit_params *User.attribute_names,
members_attributes: Member.attribute_names
end

View File

@ -11,8 +11,6 @@
*= require_self
*/
@import "flags";
.post {
border-top: solid black 1px;
list-style: none;
@ -129,3 +127,12 @@ ul.statistics li{
.ui-datepicker{
z-index: 1000 !important;
}
// fields that contain an error
.field_with_errors{
color: red;
}
label[required]::after{
content: " *";
}

View File

@ -35,7 +35,7 @@ class ApplicationController < ActionController::Base
if user.members.any? &:manager
users_path
else
offers_path
users_path
end
else
page_path("home")

View File

@ -1,9 +1,9 @@
class DocumentsController < InheritedResources::Base
class DocumentsController < ApplicationController
respond_to :html, :js
def show
@document = Document.new(title: 'Missing document', content: 'Available very soon') if Document.find_by_id(params[:id]).blank?
show! do |format|
@document = find_document_or_missing
respond_to do |format|
format.html do
if params[:modal]
render "show+modal", layout: false
@ -11,4 +11,12 @@ class DocumentsController < InheritedResources::Base
end
end
end
private
def find_document_or_missing
Document.find params[:id]
rescue ActiveRecord::NotFound
Document.new(title: "Missing document", content: "Available very soon")
end
end

View File

@ -1,3 +1,5 @@
class InquiriesController < PostsController
def model
Inquiry
end
end

View File

@ -2,14 +2,15 @@ class MembersController < ApplicationController
before_filter :authenticate_user!
def destroy
user_id = Member.find(params[:id]).user_id
current_organization.members.find(params[:id]).destroy
current_organization.posts.where(user_id: user_id).destroy_all
find_member
current_organization.posts.where(user_id: @member.user_id).destroy_all
@member.destroy
redirect_to users_path
end
def toggle_manager
current_organization.members.find(params[:id]).toggle(:manager).save!
find_member
@member.toggle(:manager).save!
respond_to do |format|
format.json { head :ok }
format.html { redirect_to :back }
@ -17,11 +18,17 @@ class MembersController < ApplicationController
end
def toggle_active
current_organization.members.find(params[:id]).toggle(:active).save!
find_member
@member.toggle(:active).save!
respond_to do |format|
format.json { head :ok }
format.html { redirect_to :back }
end
end
private
def find_member
@member ||= current_organization.members.find(params[:id])
end
end

View File

@ -1,6 +1,10 @@
# Managems of offer-type posts
#
class OffersController < PostsController
def model
Offer
end
def dashboard
initial_scope =
if current_organization

View File

@ -1,5 +1,4 @@
class OrganizationsController < ApplicationController
respond_to :json, :html
before_filter :load_resource
@ -12,7 +11,6 @@ class OrganizationsController < ApplicationController
end
end
def new
@organization = Organization.new
end
@ -49,19 +47,33 @@ class OrganizationsController < ApplicationController
def give_time
@destination = @organization.account.id
@source = current_user.members.find_by(organization: @organization).account.id
@offer = current_organization.offers.find(params[:offer]) if params[:offer].present?
@source = find_transfer_source
@offer = find_transfer_offer
@transfer = Transfer.new(source: @source, destination: @destination)
if admin?
@sources = [current_organization.account] + current_organization.member_accounts.where("members.active is true")
end
@sources = find_transfer_sources_for_admin
end
private
def organization_params
params[:organization].permit(*%w"name theme email phone web public_opening_times description address neighborhood city domain")
params[:organization].permit(*%w[name theme email phone web
public_opening_times description address
neighborhood city domain])
end
def find_transfer_offer
current_organization.offers.
find(params[:offer]) if params[:offer].present?
end
def find_transfer_source
current_user.members.
find_by(organization: @organization).account.id
end
def find_transfer_sources_for_admin
return unless admin?
[current_organization.account] +
current_organization.member_accounts.where("members.active is true")
end
end

View File

@ -1,4 +1,4 @@
class PostsController < InheritedResources::Base
class PostsController < ApplicationController
respond_to :html, :js
has_scope :by_category, as: :cat
@ -6,21 +6,63 @@ class PostsController < InheritedResources::Base
has_scope :tagged_with, as: :tag
has_scope :by_organization, as: :org
before_action only: %i[update destroy] do
authorize resource
def index
posts = apply_scopes(model).page(params[:page]).per(25)
instance_variable_set("@#{resources}", posts)
end
protected
def new
post = model.new
post.user = current_user
instance_variable_set("@#{resource}", post)
end
def collection
get_collection_ivar || begin
c = end_of_association_chain
set_collection_ivar(c.page(params[:page]).per(25))
def create
post = model.new(post_params)
post.organization = current_organization
if post.save
redirect_to send("#{resource}_path", post)
else
instance_variable_set("@#{resource}", post)
render action: :new
end
end
def begin_of_association_chain
current_organization
def edit
post = current_organization.posts.find params[:id]
instance_variable_set("@#{resource}", post)
end
def show
post = current_organization.posts.find params[:id]
instance_variable_set("@#{resource}", post)
end
def update
post = current_organization.posts.find params[:id]
authorize post
instance_variable_set("@#{resource}", post)
if post.update_attributes(post_params)
redirect_to post
else
render action: :edit, status: :unprocessable_entity
end
end
def destroy
post = current_organization.posts.find params[:id]
authorize post
redirect_to send("#{resources}_path") if post.destroy
end
private
def resource
controller_name.singularize
end
def resources
controller_name
end
def set_user_id(p)
@ -32,13 +74,12 @@ class PostsController < InheritedResources::Base
end
end
def build_resource_params
def post_params
permitted_fields = %i[description end_on global joinable permanent start_on
title category_id tag_list user_id publisher_id]
[
params.fetch(resource_instance_name, {}).
permit(*permitted_fields).tap { |p| set_user_id(p) }
]
params.fetch(resource, {}).permit(*permitted_fields).tap do |p|
set_user_id(p)
end
end
end

View File

@ -4,12 +4,17 @@ class ReportsController < ApplicationController
layout "report"
def user_list
@members = current_organization.members.active.includes(:user).order("members.member_uid")
@members = current_organization.members.active.
includes(:user).
order("members.member_uid")
end
def post_list
@post_type = (params[:type] || "offer").capitalize.constantize
@posts = current_organization.posts.with_member.where(type: @post_type).group_by(&:category).to_a.sort_by {|c, p| c.try(:name) || ""}
@posts = current_organization.posts.with_member.
where(type: @post_type).
group_by(&:category).
to_a.
sort_by { |category, _| category.try(:name).to_s }
end
end

View File

@ -1,8 +1,19 @@
class StatisticsController < ApplicationController
before_filter :authenticate_user!
AGE_GROUP_LABELS = {
0..17 => " -17",
18..24 => "18-24",
25..34 => "25-34",
35..44 => "35-44",
45..54 => "45-54",
55..64 => "55-64",
65..100 => "65+",
}
def statistics_global_activity
@members = current_organization.members
@active_members = @members.active
@total_hours = num_movements = 0
@members.each do |m|
num_movements += m.account.movements.count
@ -57,46 +68,22 @@ class StatisticsController < ApplicationController
end
def statistics_inactive_users
@members = current_organization.members
@members = current_organization.members.active
end
def statistics_demographics
@members = current_organization.members
@age_groups = @members.group_by do |member|
case (age(member.user.date_of_birth.presence) rescue nil)
when 0..17 then " -17"
when 18..24 then "18-24"
when 25..34 then "25-34"
when 35..44 then "35-44"
when 45..54 then "45-54"
when 55..64 then "55-64"
when 65..100 then "65+"
else t("statistics.statistics_demographics.unknown")
end
end
@age_counts = Hash[@age_groups.map { |name, group| [name, group.size] }]
@gender_groups = @members.group_by do |member|
case member.user_gender
when "male" then t("statistics.statistics_demographics.male")
when "female" then t("statistics.statistics_demographics.female")
else t("statistics.statistics_demographics.unknown")
end
end
@gender_counts = Hash[@gender_groups.map do |name, group|
[name, group.size]
end]
@age_counts = age_counts
@gender_counts = gender_counts
end
def statistics_last_login
@members = current_organization.members.joins(:user).
@members = current_organization.members.active.joins(:user).
order("users.current_sign_in_at ASC NULLS FIRST")
end
def statistics_without_offers
@members = current_organization.members
@members = current_organization.members.active
end
def statistics_type_swaps
@ -107,64 +94,78 @@ class StatisticsController < ApplicationController
count_of_transfers").
where("movements.amount > 0").
group("posts.tags, posts.category_id, posts.updated_at")
total = 0.0
offers_array = offers.map do |offer|
if offer.category_id.blank?
if offer.tags.blank?
total += offer.count_of_transfers
[[t("statistics.statistics_type_swaps.without_category"),
t("statistics.statistics_type_swaps.without_tags"),
offer.sum_of_transfers, offer.count_of_transfers]]
else
offer.tags.map do |tag|
total += offer.count_of_transfers
[t("statistics.statistics_type_swaps.without_category"), tag,
offer.sum_of_transfers, offer.count_of_transfers]
end
end
elsif offer.tags.blank?
total += offer.count_of_transfers
[[offer.category.name,
t("statistics.statistics_type_swaps.without_tags"),
offer.sum_of_transfers, offer.count_of_transfers]]
else
offer.tags.map do |tag|
total += offer.count_of_transfers
[offer.category.name, tag, offer.sum_of_transfers,
offer.count_of_transfers]
end
end
end.flatten(1)
# ["Clases", "clases", 11700, 2], ["Clases", "clases", 1320, 1], ...]
# added %
offers_array = offers_array.map { |a| a.push(a.last / total) }
offers_array = offers_array.group_by { |a, b| [a, b] }
# {["Clases", "clases"]=>[["Clases", "clases", 11700, 2, 0.2],
# ["Clases", "clases", 1320, 1, 0.1]],,...}
@offers = []
offers_array.each do |cat_tag, values|
sum_of_transfers, count_of_transfers, percent = 0, 0, 0
values.each do |value|
sum_of_transfers += value[2]
count_of_transfers += value[3]
percent += value[4]
end
@offers.push([cat_tag, sum_of_transfers,
count_of_transfers, percent].flatten)
end
# [["Clases", "clases", 13020, 3, 0.30],
# ["Domestic", "coche", 2220, 1, 0.1],...]
@offers = @offers.sort_by(&:last).reverse
@offers = count_offers_by_label(offers).to_a.each { |a| a.flatten!(1) }.
sort_by(&:last).reverse
end
protected
def age(date_of_birth)
now = DateTime.now
age = now.year - date_of_birth.year
age -= 1 if (now.month < date_of_birth.month) ||
(now.month == date_of_birth.month &&
now.day < date_of_birth.day)
age
return unless date_of_birth
age_in_days = Date.today - date_of_birth
(age_in_days / 365.26).to_i
end
# returns a hash of
# {
# [ category_label, tag_label ] => [ sum, count, ratio ],
# ...
# }
def count_offers_by_label(offers)
# Cannot use Hash.new([0, 0]) because then
# counters[key][0] += n
# will modify directly the "global default" instead of
# first assigning a new array with the zeroed counters.
counters = Hash.new { |h, k| h[k] = [0, 0] }
offers.each do |offer|
labels_for_offer(offer).each do |labels|
# labels = [ category_label, tag_label ]
counters[labels][0] += offer.sum_of_transfers
counters[labels][1] += offer.count_of_transfers
end
end
add_ratios!(counters)
counters
end
def add_ratios!(counters)
# add the ratio at the end of each value
total_count = counters.values.map { |_, counts| counts }.sum
counters.each do |_, v|
v << v[1].to_f / total_count
end
end
# returns an array of
# [category_name, tag_name]
# one item per each tag. If the category or the tags are missing, they are
# replaced with a fallback "Unknown" label.
def labels_for_offer(offer)
tag_labels = offer.tags.presence ||
[t("statistics.statistics_type_swaps.without_tags")]
category_label = offer.category.try(:name) ||
t("statistics.statistics_type_swaps.without_category")
[category_label].product(tag_labels)
end
def age_counts
@members.each_with_object(Hash.new(0)) do |member, counts|
age = age(member.user_date_of_birth)
age_label = AGE_GROUP_LABELS.detect do |range, _|
range.include? age
end.try(:last) || t("statistics.statistics_demographics.unknown")
counts[age_label] += 1
end
end
def gender_counts
@members.each_with_object(Hash.new(0)) do |member, counts|
gender = member.user_gender || "unknown"
gender_label = t("statistics.statistics_demographics.#{gender}")
counts[gender_label] += 1
end
end
end

View File

@ -10,5 +10,4 @@ class TermsController < ApplicationController
current_user.touch :terms_accepted_at
redirect_to root_path
end
end

View File

@ -1,18 +1,39 @@
class TransfersController < ApplicationController
def create
@source = if admin?
Account.find(transfer_params[:source])
else
current_user.members.find_by(organization: current_organization).account
end
@source = find_source
Transfer.create(transfer_params.merge source: @source)
account = Account.find(transfer_params[:destination])
redirect_to account.accountable_type == "Organization" ? account.accountable : account.accountable.user
@account = Account.find(transfer_params[:destination])
redirect_to redirect_target
end
private
def transfer_params
params.require(:transfer).permit(*[:destination, :amount, :reason, :post_id, (:source if admin?)].compact)
def find_source
if admin?
Account.find(transfer_params[:source])
else
current_user.members.find_by(organization: current_organization).account
end
end
def redirect_target
case accountable = @account.accountable
when Organization
accountable
when Member
accountable.user
else
raise ArgumentError
end
end
def transfer_params
params.
require(:transfer).
permit(:destination,
:amount,
:reason,
:post_id,
*[*(:source if admin?)])
end
end

View File

@ -17,7 +17,7 @@ class UsersController < ApplicationController
def show
@user = current_user if current_user.id == params[:id].to_i
@user ||= scoped_users.find(params[:id])
@user ||= scoped_users.find(params[:id])
end
def new
@ -40,7 +40,18 @@ class UsersController < ApplicationController
if @user.persisted?
@user.tune_after_persisted(current_organization)
redirect_to users_path
id = @user.member(current_organization).member_uid
if params[:more]
redirect_to new_user_path,
notice: I18n.t("users.new.user_created_add",
uid: id,
name: @user.username)
else
redirect_to users_path,
notice: I18n.t("users.index.user_created",
uid: id,
name: @user.username)
end
else
@user.email = "" if empty_email
render action: "new"
@ -61,18 +72,12 @@ class UsersController < ApplicationController
@user = scoped_users.find(params[:id])
@destination = @user.members.
find_by(organization: current_organization).account.id
@source = current_user.members.
find_by(organization: current_organization).account.id
@offer = current_organization.offers.
find(params[:offer]) if params[:offer].present?
@source = find_transfer_source
@offer = find_transfer_offer
@transfer = Transfer.new(source: @source,
destination: @destination,
post: @offer)
if admin?
@sources = [current_organization.account] +
current_organization.
member_accounts.where("members.active is true")
end
@sources = find_transfer_sources_for_admin
end
def toggle_active
@ -100,4 +105,20 @@ class UsersController < ApplicationController
# params[:user].permit(*fields_to_permit).tap &method(:ap)
params.require(:user).permit *fields_to_permit
end
def find_transfer_offer
current_organization.offers.
find(params[:offer]) if params[:offer].present?
end
def find_transfer_source
current_user.members.
find_by(organization: current_organization).account.id
end
def find_transfer_sources_for_admin
return unless admin?
[current_organization.account] +
current_organization.member_accounts.where("members.active is true")
end
end

View File

@ -42,17 +42,12 @@ module ApplicationHelper
end
def languages_list
locales = I18n.available_locales
locales.map do |locale|
content_tag(:li, class: I18n.locale == locale ? :disabled : "") do
locale_key = "locales.#{locale}"
link_to switch_lang_path(locale: locale) do
concat t(locale_key, locale: locale)
concat " (#{t(locale_key)})" unless I18n.locale == locale
end
end
end.join.html_safe
I18n.available_locales.each do |locale|
concat content_tag(:li,
link_to(locale_menu_item(locale),
switch_lang_path(locale: locale)),
class: ("disabled" if I18n.locale == locale))
end
end
def show_error_messages!(resource)
@ -69,4 +64,12 @@ module ApplicationHelper
html.html_safe
end
private
def locale_menu_item(locale)
t("locales.#{locale}", locale: locale).tap do |s|
s << " (#{t("locales.#{locale}")})" unless I18n.locale == locale
end
end
end

View File

@ -1,17 +1,22 @@
module GlyphHelper
GLYPHS = Hash[
GLYPHS = {
"offer" => "hand-up",
"inquiry" => "bell",
"user" => "user",
"tag" => "tag",
"category" => "folder-open",
"organization" => "tower"
]
}
def glyph(kind)
kind = kind.to_s.underscore
content_tag :span, "", class: "glyphicon glyphicon-#{GLYPHS.fetch kind, kind.gsub("_", "-")}"
content_tag :span, "",
class: "glyphicon glyphicon-#{glyph_name(kind)}"
end
private
def glyph_name(kind)
GLYPHS.fetch kind, kind.dasherize
end
end

View File

@ -1,7 +1,7 @@
module UsersHelper
# TODO refactor or eliminate - poosibly the second.
def users_as_json
@users = (admin? || superadmin?)? @users : @users.actives
@users = (admin? || superadmin?) ? @users : @users.actives
@users.map do |user|
membership = @memberships[user.id]
{
@ -16,14 +16,48 @@ module UsersHelper
balance: membership.account_balance.to_i,
url: user_path(user),
edit_link: (superadmin? || admin? || user == current_user) ? edit_user_path(user) : "",
cancel_link: (superadmin? || admin?) ? member_path(membership) : "",
toggle_manager_link: ((superadmin? || admin?) && user != current_user) ? toggle_manager_member_path(membership) : "",
edit_link: edit_user_path(user),
cancel_link: cancel_member_path(membership),
toggle_manager_link: toggle_manager_member_path(membership),
manager: !!membership.manager,
toggle_active_link: (superadmin? || admin?) ? toggle_active_member_path(membership) : "",
toggle_active_link: toggle_active_member_path(membership),
active: membership.active?,
valid_email: user.has_valid_email?
}
end.to_json.html_safe
end
private
def edit_user_path(user)
can_edit_user?(user) ? super : ""
end
def can_edit_user?(user)
superadmin? || admin? || user == current_user
end
def cancel_member_path(member)
can_cancel_member?(member) ? member_path(member) : ""
end
def can_cancel_member?(_member)
superadmin? || admin?
end
def toggle_manager_member_path(member)
can_toggle_manager?(member) ? super : ""
end
def can_toggle_manager?(member)
(superadmin? || admin?) && member.user != current_user
end
def toggle_active_member_path(member)
can_toggle_active?(member) ? super : ""
end
def can_toggle_active?(_member)
superadmin? || admin?
end
end

View File

@ -1,5 +1,5 @@
class CollectionSelect2Input < SimpleForm::Inputs::CollectionSelectInput
def input_html_classes
super.push('select2')
super.push("select2")
end
end

View File

@ -1,6 +1,6 @@
class DatepickerInput < SimpleForm::Inputs::Base
def input
@builder.text_field(attribute_name, input_html_options) + \
@builder.hidden_field(attribute_name, { :class => attribute_name.to_s + "-alt" })
@builder.hidden_field(attribute_name, class: attribute_name.to_s + "-alt")
end
end

View File

@ -0,0 +1,20 @@
class OrganizationNotifier < ActionMailer::Base
default from: "\"TimeOverflow\" <info@timeoverflow.org>"
# Subject can be set in your I18n file at config/locales/en.yml
# with the following lookup:
#
# en.organization_notifier.recent_posts.subject
#
def recent_posts(posts)
# last 10 posts of offers and inquiries
@offers = posts.where(type: "Offer").take(10)
@inquiries = posts.where(type: "Inquiry").take(10)
@organization_name = posts.take.organization.name
# users with email ok
emails = posts.take.organization.users.online_active.actives.pluck(:email)
mail(bcc: emails)
end
end

View File

@ -16,7 +16,7 @@ class Account < ActiveRecord::Base
new_balance = movements.sum(:amount)
self.balance = new_balance
if balance_changed?
self.flagged = !allowed?(self.balance)
self.flagged = !allowed?(balance)
end
save
end
@ -24,7 +24,11 @@ class Account < ActiveRecord::Base
# Return the maximum allowed amount of time that the acccount is able to
# spend without overflowing
def allowance
min_allowed_balance ? [0, balance - min_allowed_balance].min : Float::INFINITY
if min_allowed_balance
[0, balance - min_allowed_balance].min
else
Float::INFINITY
end
end
# Print the account as its accountable reference
@ -35,8 +39,7 @@ class Account < ActiveRecord::Base
private
def allowed?(new_balance)
new_balance < (max_allowed_balance || Float::INFINITY) and
new_balance < (max_allowed_balance || Float::INFINITY) &&
new_balance > (min_allowed_balance || -Float::INFINITY)
end
end

View File

@ -1,6 +1,6 @@
class Category < ActiveRecord::Base
has_many :posts
translates :name
def to_s
name

View File

@ -8,7 +8,7 @@ class CsvDb
gender: User::GENDERS[row[5].to_i - 1])
end
def member_from_row(row, user)
def member_from_row(row, user, organization, errors)
member = organization.members.create(member_uid: row[0],
entry_date: row[1],
user_id: user.id)
@ -17,12 +17,12 @@ class CsvDb
) if member.errors.present?
end
def process_row(row)
def process_row(row, organization, errors)
user = user_from_row(row)
user.skip_confirmation! # auto-confirm, not sending confirmation email
if user.save
member_from_row(row, user)
member_from_row(row, user, organization, errors)
else
errors.push(member_id: row[0], email: row[9],
errors: user.errors.full_messages)
@ -36,7 +36,7 @@ class CsvDb
organization = Organization.find(organization_id)
errors = []
CSV.parse(data, headers: false) do |row|
process_row(row)
process_row(row, organization, errors)
end
organization.ensure_reg_number_seq!
errors

View File

@ -4,5 +4,4 @@ class Document < ActiveRecord::Base
def self.terms_and_conditions
where(label: "t&c", documentable_id: nil).first
end
end

View File

@ -4,10 +4,11 @@ class Member < ActiveRecord::Base
has_one :account, as: :accountable
delegate :balance, to: :account, prefix: true, allow_nil: true
delegate :gender, to: :user, prefix: true, allow_nil: true
delegate :gender, :date_of_birth, to: :user, prefix: true, allow_nil: true
after_create :create_account
before_validation :assign_registration_number, on: :create
after_destroy :remove_orphaned_users
scope :by_month, -> (month) {
where(created_at: month.beginning_of_month..month.end_of_month)
@ -34,4 +35,10 @@ class Member < ActiveRecord::Base
def offers
Post.where(organization: organization, user: user, type: "Offer")
end
private
def remove_orphaned_users
user.destroy if user && user.members.empty?
end
end

View File

@ -13,7 +13,13 @@ class Movement < ActiveRecord::Base
account.update_balance
end
has_one :other_side, (->(self_) { where ["NOT movements.id = #{self_.id}"] }), through: :transfer, source: :movements
has_one :other_side,
(->(self_) { where ["NOT movements.id = #{self_.id}"] }),
through: :transfer,
source: :movements
scope :by_month, ->(month) { where({ created_at: month.beginning_of_month..month.end_of_month})}
scope :by_month,
->(month) {
where(created_at: month.beginning_of_month..month.end_of_month)
}
end

View File

@ -1,3 +1,2 @@
class Offer < Post
end

View File

@ -1,7 +1,7 @@
class Organization < ActiveRecord::Base
before_validation :ensure_url
validates_uniqueness_of :name
has_many :members
has_many :members, dependent: :destroy
has_many :users, -> { order "members.created_at DESC" }, through: :members
has_one :account, as: :accountable
@ -15,7 +15,8 @@ class Organization < ActiveRecord::Base
has_many :documents, as: :documentable
BOOTSWATCH_THEMES = %w[amelia cerulean cosmo cyborg flatly journal readable simplex slate spacelab united]
BOOTSWATCH_THEMES = %w[amelia cerulean cosmo cyborg flatly journal readable
simplex slate spacelab united]
# validates :theme, allow_nil: true, inclusion: {in: BOOTSWATCH_THEMES}
scope :matching, ->(str) {
@ -52,4 +53,5 @@ class Organization < ActiveRecord::Base
errors.add(:web, :url_format_invalid)
end
end
end

View File

@ -7,6 +7,7 @@ class Post < ActiveRecord::Base
attr_reader :member_id
belongs_to :category
delegate :name, to: :category, prefix: true, allow_nil: true
belongs_to :user
belongs_to :organization
belongs_to :publisher, class_name: "User", foreign_key: "publisher_id"
@ -42,7 +43,12 @@ class Post < ActiveRecord::Base
) #{Post.table_name}")
}
scope :from_last_week, -> {
where("created_at >= ?", 1.week.ago.beginning_of_day)
}
validates :user, presence: true
validates :category, presence: true
def to_s
title

View File

@ -20,8 +20,9 @@ class User < ActiveRecord::Base
default_scope -> { order("users.id ASC") }
scope :actives, -> { where(members: { active: true }) }
scope :online_active, -> { where("sign_in_count > 0") }
validates :username, presence: true, uniqueness: true
validates :username, presence: true
validates :email, presence: true, uniqueness: true
# Allows @domain.com for dummy emails but does not allow pure invalid
@ -31,7 +32,7 @@ class User < ActiveRecord::Base
# validates :gender, presence: true, inclusion: {in: GENDERS}
has_many :members
has_many :members, dependent: :destroy
accepts_nested_attributes_for :members
has_many :organizations, through: :members
has_many :accounts, through: :members

View File

@ -1,6 +1,6 @@
class PostPolicy < Struct.new(:user, :post)
def destroy?
user == post.user or user.admins?(post.organization)
user == post.user || user.admins?(post.organization)
end
def create?
@ -8,7 +8,6 @@ class PostPolicy < Struct.new(:user, :post)
end
def update?
user == post.user or user.admins?(post.organization)
user == post.user || user.admins?(post.organization)
end
end

View File

@ -0,0 +1,14 @@
class OrganizationNotifierService
def initialize(organization)
@organization = organization
end
def send_recent_posts_to_online_members
@organization.each do |org|
posts = org.posts.from_last_week
if posts.present?
OrganizationNotifier.recent_posts(posts).deliver
end
end
end
end

View File

@ -136,4 +136,4 @@
%li.disabled
= link_to t("global.locales_header"), "#"
%li.divider
= languages_list
- languages_list

View File

@ -17,7 +17,7 @@
= @category ? @category.name : Category.model_name.human
%span.caret
%ul.dropdown-menu{role: "menu"}
%li= link_to '× #{t("global.all")}', inquiries_path
%li= link_to "× #{t("global.all")}", inquiries_path
- Category.all.sort_by { |a| a.name.downcase }.each do |c|
- next if c == @category
%li= link_to c.name, inquiries_path(cat: c.id)

View File

@ -17,7 +17,7 @@
= @category ? @category.name : Category.model_name.human
%span.caret
%ul.dropdown-menu{role: "menu"}
%li= link_to '× #{t("global.all")}', dashboard_offers_path
%li= link_to "× #{t("global.all")}", dashboard_offers_path
- Category.all.sort_by { |a| a.name.downcase }.each do |c|
- next if c == @category
%li= link_to c.name, offers_path(cat: c.id)

View File

@ -0,0 +1,46 @@
<!DOCTYPE html>
<html>
<head>
<meta content='text/html; charset=UTF-8' http-equiv='Content-Type' />
</head>
<body>
<% if @offers.present? %>
<p>
<%= t("organization_notifier.recent_posts.text1") %>
</p>
<ul>
<% @offers.each do |offer| %>
<li>
<a href="<%= offer_url(offer.id)%>">
<%= "#{offer.title} (#{offer.try(&:category_name)})" %>
</a>
</li>
<% end %>
</ul>
<% end %>
<% if @inquiries.present? %>
<p>
<%= t("organization_notifier.recent_posts.text2") %>
</p>
<ul>
<% @inquiries.each do |inquiry| %>
<li>
<a href="<%= inquiry_url(inquiry.id)%>">
<%= "#{inquiry.title} (#{inquiry.try(&:category_name)})" %>
</a>
</li>
<% end %>
</ul>
<% end %>
--
<p>
<%= t("mailers_globals.footer.text", organization_name: @organization_name) %>
<a href="<%= page_url('home', only_path: false) %>">
TimeOverflow
<a>
</p>
</body>
</html>

View File

@ -26,7 +26,8 @@
};
});
= form_for(post, html: { name: "form" }) do |f|
= show_error_messages!(post)
= form_for(post, html: { name: "form", novalidate: true }) do |f|
.form-group
= f.label :title
= f.text_field :title, class: "form-control"
@ -34,7 +35,7 @@
= f.label :description
= f.text_area :description, rows: "10", class: "form-control"
.form-group
= f.label :category
= f.label :category, required: true
= f.select :category_id, Category.all.sort_by{ |a| a.name.downcase }.map{ |cat| [cat.name, cat.id] }, { include_blank: true }, { class: "form-control" }
.form-group
= f.label :tag_list
@ -47,6 +48,9 @@
= f.select :user_id, options_for_select(current_organization.members.active.order("members.member_uid").map{ |mem| ["#{mem.member_uid} #{mem.user.to_s}", mem.user_id] }, selected: post.user.id || current_user.id), {}, { id: "select2", class: "form-control", required: true }
= f.hidden_field :publisher_id, value: current_user.id
%p.help-block
= t "global.required_field"
= f.button label_button, class: "btn btn-default", style: "margin-bottom: 20px;"
:javascript

View File

@ -6,7 +6,7 @@
%li
= t ".users_reg"
.badge
= @members.count
= @active_members.count
%li
= t ".num_swaps"
.badge

View File

@ -23,8 +23,10 @@
= f.input :date_of_birth, start_year: Date.today.year - 90, end_year: Date.today.year - 12, include_blank: :true
= f.input :description, as: "text"
/ = f.association :categories, label_method: :fqn, input_html: {style: 'width: 100%'}
.form-actions
= f.button :submit
- if @user.new_record?
= f.button :submit, t('users.new.create_more_users_button'), name: 'more'
= link_to t('users.new.cancel'), users_path

View File

@ -15,7 +15,7 @@
- if admin?
.form-group
= f.label :source
= f.select :source, options_for_select(@sources.sort_by{ |source| [source.accountable_type, source.accountable_id]}.map{ |a| ["#{a.accountable_type == "Member" ? a.accountable.member_uid : a.accountable_id} #{a.accountable_type} #{a.accountable.to_s}", a.id]}, selected: current_user.member(current_organization).account.id), {}, { id: "select2-time", class: "form-control" }
= f.select :source, options_for_select(@sources.sort_by{ |source| [source.accountable_type, source.accountable.try(:member_uid)]}.map{ |a| ["#{a.accountable_type == "Member" ? a.accountable.member_uid : ""} #{a.accountable_type} #{a.accountable.to_s}", a.id]}, selected: current_user.member(current_organization).account.id), {}, { id: "select2-time", class: "form-control" }
- if @offer
= f.input :post, readonly: true
= f.input :post_id, as: :hidden

View File

@ -26,11 +26,6 @@
%a{href: edit_user_path(@user)}
%span.glyphicon.glyphicon-pencil
= t "global.edit"
- else
%li
%a
%span.glyphicon.glyphicon-envelope
= t "send message"
- if admin? || @user != current_user
%li
%a{href: give_time_user_path(@user)}

3
bin/bundle Executable file
View File

@ -0,0 +1,3 @@
#!/usr/bin/env ruby
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
load Gem.bin_path('bundler', 'bundle')

4
bin/rails Executable file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env ruby
APP_PATH = File.expand_path('../../config/application', __FILE__)
require_relative '../config/boot'
require 'rails/commands'

4
bin/rake Executable file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env ruby
require_relative '../config/boot'
require 'rake'
Rake.application.run

29
bin/setup Executable file
View File

@ -0,0 +1,29 @@
#!/usr/bin/env ruby
require 'pathname'
# path to your application root.
APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
Dir.chdir APP_ROOT do
# This script is a starting point to setup your application.
# Add necessary setup steps to this file:
puts "== Installing dependencies =="
system "gem install bundler --conservative"
system "bundle check || bundle install"
# puts "\n== Copying sample files =="
# unless File.exist?("config/database.yml")
# system "cp config/database.yml.sample config/database.yml"
# end
puts "\n== Preparing database =="
system "bin/rake db:setup"
puts "\n== Removing old logs and tempfiles =="
system "rm -f log/*"
system "rm -rf tmp/cache"
puts "\n== Restarting application server =="
system "touch tmp/restart.txt"
end

View File

@ -1,16 +1,10 @@
require File.expand_path('../boot', __FILE__)
# Pick the frameworks you want:
require "rails/all"
require 'tilt'
# require "rails/test_unit/railtie"
require 'rails/all'
if defined?(Bundler)
# If you precompile assets before deploying to production, use this line
Bundler.require(*Rails.groups(:assets => %w(development test)))
# If you want your assets lazily compiled in production, use this line
# Bundler.require(:default, :assets, Rails.env)
end
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)
module Timeoverflow
class Application < Rails::Application
@ -18,53 +12,18 @@ module Timeoverflow
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.
# Custom directories with classes and modules you want to be autoloadable.
# config.autoload_paths += %W(#{config.root}/extras)
# Only load the plugins named here, in the order given (default is alphabetical).
# :all can be used as a placeholder for all plugins not explicitly named.
# config.plugins = [ :exception_notification, :ssl_requirement, :all ]
# Activate observers that should always be running.
# config.active_record.observers = :cacher, :garbage_collector, :forum_observer
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
# config.time_zone = 'Central Time (US & Canada)'
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
# config.i18n.default_locale = :de
config.i18n.default_locale = :es
config.i18n.available_locales = [:es, :ca, :en]
config.i18n.fallbacks = true
# Configure the default encoding used in templates for Ruby 1.9.
config.encoding = "utf-8"
# Configure sensitive parameters which will be filtered from the log file.
config.filter_parameters += [:password, :assertion]
# Enable escaping HTML in JSON.
config.active_support.escape_html_entities_in_json = true
# Use SQL instead of Active Record's schema dumper when creating the database.
# This is necessary if your schema can't be completely dumped by the schema dumper,
# like if you have constraints or database-specific column types
# config.active_record.schema_format = :sql
# Enable the asset pipeline
config.assets.enabled = true
# Version of your assets, change this if you want to expire all your assets
config.assets.version = '1.0'
# Add fonts to assets
# config.assets.paths << Rails.root.join("app", "assets", "fonts")
# config.assets.paths << Rails.root.join("app", "assets", "templates")
# config.assets.register_engine '.haml', Tilt::HamlTemplate
# Avoid connecting to the database on precompilation
config.assets.initialize_on_precompile = false
# Do not swallow errors in after_commit/after_rollback callbacks.
config.active_record.raise_in_transactional_callbacks = true
end
end

View File

@ -1,6 +1,3 @@
require 'rubygems'
# Set up gems listed in the Gemfile.
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
require 'bundler/setup' # Set up gems listed in the Gemfile.

View File

@ -1,5 +1,5 @@
# Load the rails application
# Load the Rails application.
require File.expand_path('../application', __FILE__)
# Initialize the rails application
Timeoverflow::Application.initialize!
# Initialize the Rails application.
Rails.application.initialize!

View File

@ -1,32 +1,44 @@
Timeoverflow::Application.configure do
# Settings specified here will take precedence over those in config/application.rb
Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
# In the development environment your application's code is reloaded on
# every request. This slows down response time but is perfect for development
# since you don't have to restart the web server when you make code changes.
config.cache_classes = false
# Do not eager load code on boot.
config.eager_load = false
# Show full error reports and disable caching
# Show full error reports and disable caching.
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
# Action Mailer
# Don't care if the mailer can't send.
config.action_mailer.raise_delivery_errors = true
config.action_mailer.perform_deliveries = false
config.action_mailer.default_url_options = {
:host => (ENV["MAIL_LINK_HOST"] || 'localhost:3000')
host: (ENV["MAIL_LINK_HOST"] || "localhost:3000")
}
# Print deprecation notices to the Rails logger
# Print deprecation notices to the Rails logger.
config.active_support.deprecation = :log
# Only use best-standards-support built into browsers
config.action_dispatch.best_standards_support = :builtin
# Raise an error on page load if there are pending migrations.
config.active_record.migration_error = :page_load
# Do not compress assets
config.assets.compress = false
# Expands the lines which load the assets
# Debug mode disables concatenation and preprocessing of assets.
# This option may cause significant delays in view rendering with a large
# number of complex assets.
config.assets.debug = true
# Asset digests allow you to set far-future HTTP expiration dates on all assets,
# yet still be able to expire them through the digest params.
config.assets.digest = true
# Adds additional error checking when serving assets at runtime.
# Checks for improperly declared sprockets dependencies.
# Raises helpful error messages.
config.assets.raise_runtime_errors = true
# Raises error for missing translations
# config.action_view.raise_on_missing_translations = true
end

View File

@ -1,56 +1,68 @@
Timeoverflow::Application.configure do
# Settings specified here will take precedence over those in config/application.rb
Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
# Code is not reloaded between requests
# Code is not reloaded between requests.
config.cache_classes = true
# Eager load code on boot. This eager loads most of Rails and
# your application in memory, allowing both threaded web servers
# and those relying on copy on write to perform better.
# Rake tasks automatically ignore this option for performance.
config.eager_load = true
# Full error reports are disabled and caching is turned on
# Full error reports are disabled and caching is turned on.
config.consider_all_requests_local = false
config.action_controller.perform_caching = true
# Disable Rails's static asset server (Apache or nginx will already do this)
config.serve_static_assets = false
# Enable Rack::Cache to put a simple HTTP cache in front of your application
# Add `rack-cache` to your Gemfile before enabling this.
# For large-scale production use, consider using a caching reverse proxy like
# NGINX, varnish or squid.
# config.action_dispatch.rack_cache = true
# Compress JavaScripts and CSS
config.assets.compress = true
# Disable serving static files from the `/public` folder by default since
# Apache or NGINX already handles this.
config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present?
# Don't fallback to assets pipeline if a precompiled asset is missed
# Compress JavaScripts and CSS.
config.assets.js_compressor = :uglifier
# config.assets.css_compressor = :sass
# Do not fallback to assets pipeline if a precompiled asset is missed.
config.assets.compile = true # false
# Generate digests for assets URLs
# Asset digests allow you to set far-future HTTP expiration dates on all assets,
# yet still be able to expire them through the digest params.
config.assets.digest = true
# Defaults to nil and saved in location specified by config.assets.prefix
# config.assets.manifest = YOUR_PATH
# `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
# Specifies the header that your server uses for sending files
# config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
# Specifies the header that your server uses for sending files.
# config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
# config.force_ssl = true
# See everything in the log (default is :info)
# config.log_level = :debug
# Use the lowest log level to ensure availability of diagnostic information
# when problems arise.
config.log_level = :debug
# Prepend all log lines with the following tags
# Prepend all log lines with the following tags.
# config.log_tags = [ :subdomain, :uuid ]
# Use a different logger for distributed setups
# Use a different logger for distributed setups.
# config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
# Use a different cache store in production
# Use a different cache store in production.
# config.cache_store = :mem_cache_store
config.cache_store = :dalli_store
# Enable serving of images, stylesheets, and JavaScripts from an asset server
# config.action_controller.asset_host = "http://assets.example.com"
# Enable serving of images, stylesheets, and JavaScripts from an asset server.
# config.action_controller.asset_host = 'http://assets.example.com'
# Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
# config.assets.precompile += %w( search.js )
# Action Mailer
# Ignore bad email addresses and do not raise email delivery errors.
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
config.action_mailer.raise_delivery_errors = true
config.action_mailer.delivery_method = :smtp
@ -69,13 +81,16 @@ Timeoverflow::Application.configure do
config.action_mailer.smtp_settings = smtp_env
end
# Enable threaded mode
# config.threadsafe!
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
# the I18n.default_locale when a translation can not be found)
# the I18n.default_locale when a translation cannot be found).
config.i18n.fallbacks = true
# Send deprecation notices to registered listeners
# Send deprecation notices to registered listeners.
config.active_support.deprecation = :notify
# Use default logging formatter so that PID and timestamp are not suppressed.
config.log_formatter = ::Logger::Formatter.new
# Do not dump schema after migrations.
config.active_record.dump_schema_after_migration = false
end

View File

@ -1,26 +1,30 @@
Timeoverflow::Application.configure do
# Settings specified here will take precedence over those in config/application.rb
Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
# The test environment is used exclusively to run your application's
# test suite. You never need to work with it otherwise. Remember that
# your test database is "scratch space" for the test suite and is wiped
# and recreated between test runs. Don't rely on the data there!
config.cache_classes = true
# Do not eager load code on boot. This avoids loading your whole application
# just for the purpose of running a single test. If you are using a tool that
# preloads Rails for running tests, you may have to set it to true.
config.eager_load = false
# Configure static asset server for tests with Cache-Control for performance
config.serve_static_assets = true
config.static_cache_control = "public, max-age=3600"
# Configure static file server for tests with Cache-Control for performance.
config.serve_static_files = true
config.static_cache_control = 'public, max-age=3600'
# Show full error reports and disable caching
# Show full error reports and disable caching.
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
# Raise exceptions instead of rendering exception templates
# Raise exceptions instead of rendering exception templates.
config.action_dispatch.show_exceptions = false
# Disable request forgery protection in test environment
config.action_controller.allow_forgery_protection = false
# Disable request forgery protection in test environment.
config.action_controller.allow_forgery_protection = false
config.action_mailer.default_url_options = { host: "localhost:3000" }
@ -29,8 +33,12 @@ Timeoverflow::Application.configure do
# ActionMailer::Base.deliveries array.
config.action_mailer.delivery_method = :test
# Print deprecation notices to the stderr
# Randomize the order test cases are executed.
config.active_support.test_order = :random
# Print deprecation notices to the stderr.
config.active_support.deprecation = :stderr
config.log_level = :error
# Raises error for missing translations
# config.action_view.raise_on_missing_translations = true
end

View File

@ -0,0 +1,13 @@
# Be sure to restart your server when you modify this file.
# Version of your assets, change this if you want to expire all your assets.
Rails.application.config.assets.version = '1.0'
# Add additional assets to the asset load path
# Rails.application.config.assets.paths << Emoji.images_path
# Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
# Rails.application.config.assets.precompile += %w( search.js )
Rails.application.config.assets.initialize_on_precompile = false

View File

@ -0,0 +1,3 @@
# Be sure to restart your server when you modify this file.
Rails.application.config.action_dispatch.cookies_serializer = :marshal

View File

@ -0,0 +1,4 @@
# Be sure to restart your server when you modify this file.
# Configure sensitive parameters which will be filtered from the log file.
Rails.application.config.filter_parameters += [:password, :assertion]

View File

@ -1,15 +1,16 @@
# Be sure to restart your server when you modify this file.
# Add new inflection rules using the following format
# (all these examples are active by default):
# ActiveSupport::Inflector.inflections do |inflect|
# Add new inflection rules using the following format. Inflections
# are locale specific, and you may define rules for as many different
# locales as you wish. All of these examples are active by default:
# ActiveSupport::Inflector.inflections(:en) do |inflect|
# inflect.plural /^(ox)$/i, '\1en'
# inflect.singular /^(ox)en/i, '\1'
# inflect.irregular 'person', 'people'
# inflect.uncountable %w( fish sheep )
# end
#
# These inflection rules are supported but not enabled by default:
# ActiveSupport::Inflector.inflections do |inflect|
# ActiveSupport::Inflector.inflections(:en) do |inflect|
# inflect.acronym 'RESTful'
# end

View File

@ -1,7 +0,0 @@
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
# Rails.application.config.time_zone = 'Central Time (US & Canada)'
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
# Rails.application.config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
# Rails.application.config.i18n.default_locale = :de

View File

@ -2,4 +2,3 @@
# Add new mime types for use in respond_to blocks:
# Mime::Type.register "text/richtext", :rtf
# Mime::Type.register_alias "text/html", :iphone

View File

@ -1,8 +1,3 @@
# Be sure to restart your server when you modify this file.
Timeoverflow::Application.config.session_store :cookie_store, key: '_timeoverflow_session'
# Use the database for sessions instead of the cookie-based default,
# which shouldn't be used to store highly confidential information
# (create the session table with "rails generate session_migration")
# Timeoverflow::Application.config.session_store :active_record_store
Rails.application.config.session_store :cookie_store, key: '_timeoverflow_session'

View File

@ -1,14 +1,14 @@
# Be sure to restart your server when you modify this file.
#
# This file contains settings for ActionController::ParamsWrapper which
# is enabled by default.
# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
ActiveSupport.on_load(:action_controller) do
wrap_parameters format: [:json]
wrap_parameters format: [:json] if respond_to?(:wrap_parameters)
end
# Disable root element in JSON by default.
# To enable root element in JSON for ActiveRecord objects.
ActiveSupport.on_load(:active_record) do
self.include_root_in_json = false
end

View File

@ -150,8 +150,12 @@ ca:
cancel_warning_angular: "Vas a eliminar del banc a l'usuari {{username}}"
active_warning_angular: "Vas a canviar l'estat del compte de l'usuari {{username}}"
actions: Accions
user_created: "Usuari %{uid} %{name} guardat"
new:
new_user: Nou usuari
create_more_users_button: Crear i introduir un nou usuari
user_created_add: "Usuari %{uid} %{name} guardat, crea ara el següent"
cancel: Cancel·lar
edit:
edit_user: Canviar usuari
show:
@ -306,6 +310,7 @@ ca:
promote: Convertir en administrador
statistics: Estadístiques
locales_header: canviar llengua
required_field: "* Camp obligatori"
locales:
en: Anglès

View File

@ -0,0 +1,10 @@
ca:
mailers_globals:
footer:
text: "%{organization_name} en"
organization_notifier:
recent_posts:
subject: Boletín semanal
text1: "Últimas ofertas publicadas:"
text2: "Últimas demandas publicadas:"

View File

@ -0,0 +1,10 @@
en:
mailers_globals:
footer:
text: "%{organization_name} from"
organization_notifier:
recent_posts:
subject: Newsletter
text1: "Latest offers published:"
text2: "Lastest inquiries published:"

View File

@ -0,0 +1,10 @@
es:
mailers_globals:
footer:
text: "%{organization_name} en"
organization_notifier:
recent_posts:
subject: Boletín semanal
text1: "Últimas ofertas publicadas:"
text2: "Últimas demandas publicadas:"

View File

@ -150,8 +150,12 @@ en:
cancel_warning_angular: "You are going to delete account from the Time Bank for user {{username}}"
active_warning_angular: "You are going to change user account status for {{username}}"
actions: Actions
user_created: "User %{uid} %{name} saved"
new:
new_user: New user
create_more_users_button: Create and add another user
user_created_add: "User %{uid} %{name} saved, now create next one"
cancel: Cancel
edit:
edit_user: Update user
show:
@ -326,6 +330,7 @@ en:
promote: Promote to administrator
statistics: Statistics
locales_header: change language
required_field: "* Required field"
locales:
en: English

View File

@ -150,8 +150,12 @@ es:
cancel_warning_angular: "Va a eliminar del banco al usuario {{username}}"
active_warning_angular: "Va a cambiar el estado de la cuenta del usuario {{username}}"
actions: Acciones
user_created: "Usuario %{uid} %{name} guardado"
new:
new_user: Nuevo usuario
create_more_users_button: Crear e introducir otro usuario
cancel: Cancelar
user_created_add: "Usuario %{uid} %{name} guardado. Introduce ahora el siguiente"
edit:
edit_user: Cambiar usuario
show:
@ -310,6 +314,7 @@ es:
promote: Convertir en administrador
statistics: Estadísticas
locales_header: cambiar idioma
required_field: "* Campo obligatorio"
locales:
en: Inglés

View File

@ -1,4 +1,4 @@
Timeoverflow::Application.routes.draw do
Rails.application.routes.draw do
get "global/switch_lang", as: :switch_lang
get 'tags/index'

34
config/schedule.rb Normal file
View File

@ -0,0 +1,34 @@
set :environment, "production"
set :output, "log/cron_log.log"
every :monday, at: "9am" do
runner "OrganizationNotifierService.new(Organization.all).
send_recent_posts_to_online_members"
end
# Cada vez que se modifique este archivo, crontab debe actualizarse:
# whenever --update-crontab
# Use this file to easily define all of your cron jobs.
#
# It's helpful, but not entirely necessary to understand cron before proceeding.
# http://en.wikipedia.org/wiki/Cron
# Example:
#
# set :output, "/path/to/my/cron_log.log"
#
# every 2.hours do
# command "/usr/bin/some_great_command"
# runner "MyModel.some_method"
# rake "some:great:rake:task"
# end
#
# every 4.days do
# runner "AnotherModel.prune_old_records"
# end
# Learn more: http://github.com/javan/whenever
# rake "pruebas:hello" #task en lib/tasks para comprobar el funcionamiento de
# whenever

View File

@ -1,2 +0,0 @@
Use this README file to introduce your application and point to useful places in the API for learning more.
Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries.

View File

@ -1,2 +0,0 @@
LOG: could not open configuration file "/usr/local/pgsql/data/postgresql.conf": Permiso denegado
FATAL: configuration file "/usr/local/pgsql/data/postgresql.conf" contains errors

View File

@ -1,6 +0,0 @@
#!/usr/bin/env ruby
# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
APP_PATH = File.expand_path('../../config/application', __FILE__)
require File.expand_path('../../config/boot', __FILE__)
require 'rails/commands'

View File

@ -1,32 +1,37 @@
require 'spec_helper'
require "spec_helper"
describe InquiriesController do
let (:test_organization) { Fabricate(:organization)}
let (:member) { Fabricate(:member, organization: test_organization)}
let (:another_member) { Fabricate(:member, organization: test_organization)}
let! (:inquiry) { Fabricate(:inquiry, user: member.user, organization: test_organization)}
let (:test_organization) { Fabricate(:organization) }
let (:member) { Fabricate(:member, organization: test_organization) }
let (:another_member) { Fabricate(:member, organization: test_organization) }
let (:test_category) { Fabricate(:category) }
let! (:inquiry) do
Fabricate(:inquiry,
user: member.user,
organization: test_organization,
category: test_category)
end
include_context "stub browser locale"
before { set_browser_locale('ca') }
before { set_browser_locale("ca") }
describe "GET #index" do
context "with a logged user" do
it "populates and array of inquiries" do
login(another_member.user)
get 'index'
get "index"
expect(assigns(:inquiries)).to eq([inquiry])
end
end
end
describe "GET #show" do
context "with valid params" do
context "with a logged user" do
it "assigns the requested inquiry to @inquiry" do
login(another_member.user)
get 'show', id: inquiry.id
get "show", id: inquiry.id
expect(assigns(:inquiry)).to eq(inquiry)
end
end
@ -39,9 +44,10 @@ describe InquiriesController do
it "creates a new inquiry" do
login(another_member.user)
expect {
post 'create', inquiry: Fabricate.to_params(:inquiry)
}.to change(Inquiry,:count).by(1)
expect do
post "create", inquiry: { user: another_member.user,
category_id: test_category.id }
end.to change(Inquiry, :count).by(1)
end
end
end
@ -53,14 +59,19 @@ describe InquiriesController do
it "located the requested @inquiry" do
login(member.user)
put 'update', id: inquiry.id, inquiry: Fabricate.to_params(:inquiry)
put "update", id: inquiry.id, inquiry: Fabricate.to_params(:inquiry)
expect(assigns(:inquiry)).to eq(inquiry)
end
it "changes @inquiry's attributes" do
login(member.user)
put 'update', id: inquiry.id, inquiry: Fabricate.to_params(:inquiry, user: member, title: "New title", description: "New description")
put "update",
id: inquiry.id,
inquiry: Fabricate.to_params(:inquiry,
user: member,
title: "New title",
description: "New description")
inquiry.reload
expect(inquiry.title).to eq("New title")
@ -74,7 +85,12 @@ describe InquiriesController do
it "does not change @inquiry's attributes" do
login(member.user)
put :update, id: inquiry.id, inquiry: Fabricate.to_params(:inquiry, user: nil, title: "New title", description: "New description")
put :update,
id: inquiry.id,
inquiry: Fabricate.to_params(:inquiry,
user: nil,
title: "New title",
description: "New description")
expect(inquiry.title).not_to eq("New title")
expect(inquiry.description).not_to eq("New description")

View File

@ -1,32 +1,37 @@
require 'spec_helper'
require "spec_helper"
describe OffersController do
let (:test_organization) { Fabricate(:organization)}
let (:member) { Fabricate(:member, organization: test_organization)}
let (:another_member) { Fabricate(:member, organization: test_organization)}
let! (:offer) { Fabricate(:offer, user: member.user, organization: test_organization)}
let (:test_organization) { Fabricate(:organization) }
let (:member) { Fabricate(:member, organization: test_organization) }
let (:another_member) { Fabricate(:member, organization: test_organization) }
let (:test_category) { Fabricate(:category) }
let! (:offer) do
Fabricate(:offer,
user: member.user,
organization: test_organization,
category: test_category)
end
include_context "stub browser locale"
before { set_browser_locale('ca') }
before { set_browser_locale("ca") }
describe "GET #index" do
context "with a logged user" do
it "populates and array of offers" do
login(another_member.user)
get 'index'
get "index"
expect(assigns(:offers)).to eq([offer])
end
end
end
describe "GET #show" do
context "with valid params" do
context "with a logged user" do
it "assigns the requested offer to @offer" do
login(another_member.user)
get 'show', id: offer.id
get "show", id: offer.id
expect(assigns(:offer)).to eq(offer)
end
end
@ -39,9 +44,10 @@ describe OffersController do
it "creates a new offer" do
login(another_member.user)
expect {
post 'create', offer: Fabricate.to_params(:offer)
}.to change(Offer,:count).by(1)
expect do
post "create", offer: { user: another_member.user,
category_id: test_category }
end.to change(Offer, :count).by(1)
end
end
end
@ -53,14 +59,19 @@ describe OffersController do
it "located the requested @offer" do
login(member.user)
put 'update', id: offer.id, offer: Fabricate.to_params(:offer)
put "update", id: offer.id, offer: Fabricate.to_params(:offer)
expect(assigns(:offer)).to eq(offer)
end
it "changes @offer's attributes" do
login(member.user)
put 'update', id: offer.id, offer: Fabricate.to_params(:offer, user: member, title: "New title", description: "New description")
put "update",
id: offer.id,
offer: Fabricate.to_params(:offer,
user: member,
title: "New title",
description: "New description")
offer.reload
expect(offer.title).to eq("New title")
@ -74,7 +85,12 @@ describe OffersController do
it "does not change @offer's attributes" do
login(member.user)
put :update, id: offer.id, offer: Fabricate.to_params(:offer, user: nil, title: "New title", description: "New description")
put :update,
id: offer.id,
offer: Fabricate.to_params(:offer,
user: nil,
title: "New title",
description: "New description")
expect(offer.title).not_to eq("New title")
expect(offer.description).not_to eq("New description")

View File

@ -7,7 +7,7 @@ describe TransfersController do
let (:member_taker) { Fabricate(:member, organization: test_organization) }
include_context "stub browser locale"
before { set_browser_locale('ca') }
describe "POST #create" do
before { login(user) }
@ -73,7 +73,7 @@ describe TransfersController do
end
it "redirects to destination" do
expect(subject).to redirect_to(member_taker)
expect(subject).to redirect_to(member_taker.user)
end
end
end

View File

@ -0,0 +1,5 @@
Fabricator(:category) do
name_translations do
{ "es" => "Idiomas", "en" => "Languages", "ca" => "Idiomes" }
end
end

View File

@ -4,4 +4,4 @@ Fabricator(:member) do
organization { Fabricate(:organization) }
manager false
end
end

View File

@ -3,6 +3,7 @@ Fabricator(:post) do
title { Faker::Lorem.sentence }
user { Fabricate(:user) }
description { Faker::Lorem.paragraph }
category { Fabricate(:category) }
permanent { false }
joinable { false }
global { false }
@ -16,6 +17,7 @@ Fabricator(:inquiry) do
title { Faker::Lorem.sentence }
user { Fabricate(:user) }
description { Faker::Lorem.paragraph }
category { Fabricate(:category) }
permanent { false }
joinable { false }
global { false }
@ -29,8 +31,9 @@ Fabricator(:offer) do
title { Faker::Lorem.sentence }
user { Fabricate(:user) }
description { Faker::Lorem.paragraph }
category { Fabricate(:category) }
permanent { false }
joinable { false }
global { false }
end
end

View File

@ -13,4 +13,4 @@ Fabricator(:user) do
terms_accepted_at DateTime.now.utc
confirmed_at DateTime.now.utc
end
end

View File

@ -1,5 +1,61 @@
require 'spec_helper'
describe Member do
pending "add some examples to (or delete) #{__FILE__}"
subject { Fabricate(:member) }
it { should belong_to(:user) }
it { should belong_to(:organization) }
it { should have_one(:account) }
it { should delegate_method(:balance).to(:account).with_prefix }
it { should delegate_method(:gender).to(:user).with_prefix }
it { should delegate_method(:date_of_birth).to(:user).with_prefix }
it { should validate_presence_of(:organization_id) }
it { should validate_presence_of(:member_uid) }
describe "#offers" do
let(:member) { Fabricate(:member) }
let(:member_offer) { Fabricate(
:offer,
user: member.user,
organization: member.organization)
}
it "should be a list of Offers matching user and organization" do
another_member_offer = Fabricate(:offer)
another_organization_offer = Fabricate(
:offer,
user: member.user,
organization: Fabricate(:organization)
)
expect(member.offers).to include(member_offer)
expect(member.offers).to_not include(another_member_offer)
expect(member.offers).to_not include(another_organization_offer)
end
end
context "callbacks" do
context "#after_create" do
let(:member) { Fabricate(:member) }
it "should have an account" do
expect(member.account).to_not be_nil
end
end
context "#after_destroy" do
let!(:member) { Fabricate(:member) }
it "destroy user if it was last membership" do
expect { member.destroy }.to change(User, :count).by(-1)
end
it "do not destroy user if it has more memberships" do
second_member = Fabricate(:member, user: member.user)
expect { second_member.destroy }.not_to change(User, :count)
end
end
end
end

View File

@ -9,6 +9,14 @@ require 'capybara/rails'
# in spec/support/ and its subdirectories.
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
# Make sure schem persists when running tests.
# We ran into an error that forced us to run rake db:migrate RAILS_ENV=test
# before running tests. This kind of fixes it, although we should have a closer
# look at this and find a better solution
# Ref:
# https://www.relishapp.com/rspec/rspec-rails/docs/upgrade#pending-migration-checks
ActiveRecord::Migration.maintain_test_schema!
# Checks for pending migrations before tests are run.
# If you are not using ActiveRecord, you can remove this line.
ActiveRecord::Migration.check_pending! if defined?(ActiveRecord::Migration)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

View File

@ -1,257 +0,0 @@
.flag {
width: 16px;
height: 11px;
background:url('/assets/flags.png') no-repeat
}
.flag.flag-ad {background-position: -16px 0}
.flag.flag-ae {background-position: -32px 0}
.flag.flag-af {background-position: -48px 0}
.flag.flag-ag {background-position: -64px 0}
.flag.flag-ai {background-position: -80px 0}
.flag.flag-al {background-position: -96px 0}
.flag.flag-am {background-position: -112px 0}
.flag.flag-an {background-position: -128px 0}
.flag.flag-ao {background-position: -144px 0}
.flag.flag-ar {background-position: -160px 0}
.flag.flag-as {background-position: -176px 0}
.flag.flag-at {background-position: -192px 0}
.flag.flag-au {background-position: -208px 0}
.flag.flag-aw {background-position: -224px 0}
.flag.flag-az {background-position: -240px 0}
.flag.flag-ba {background-position: 0 -11px}
.flag.flag-bb {background-position: -16px -11px}
.flag.flag-bd {background-position: -32px -11px}
.flag.flag-be {background-position: -48px -11px}
.flag.flag-bf {background-position: -64px -11px}
.flag.flag-bg {background-position: -80px -11px}
.flag.flag-bh {background-position: -96px -11px}
.flag.flag-bi {background-position: -112px -11px}
.flag.flag-bj {background-position: -128px -11px}
.flag.flag-bm {background-position: -144px -11px}
.flag.flag-bn {background-position: -160px -11px}
.flag.flag-bo {background-position: -176px -11px}
.flag.flag-br {background-position: -192px -11px}
.flag.flag-bs {background-position: -208px -11px}
.flag.flag-bt {background-position: -224px -11px}
.flag.flag-bv {background-position: -240px -11px}
.flag.flag-bw {background-position: 0 -22px}
.flag.flag-by {background-position: -16px -22px}
.flag.flag-bz {background-position: -32px -22px}
.flag.flag-ca {background-position: -48px -22px}
.flag.flag-catalonia {background-position: -64px -22px}
.flag.flag-cd {background-position: -80px -22px}
.flag.flag-cf {background-position: -96px -22px}
.flag.flag-cg {background-position: -112px -22px}
.flag.flag-ch {background-position: -128px -22px}
.flag.flag-ci {background-position: -144px -22px}
.flag.flag-ck {background-position: -160px -22px}
.flag.flag-cl {background-position: -176px -22px}
.flag.flag-cm {background-position: -192px -22px}
.flag.flag-cn {background-position: -208px -22px}
.flag.flag-co {background-position: -224px -22px}
.flag.flag-cr {background-position: -240px -22px}
.flag.flag-cu {background-position: 0 -33px}
.flag.flag-cv {background-position: -16px -33px}
.flag.flag-cw {background-position: -32px -33px}
.flag.flag-cy {background-position: -48px -33px}
.flag.flag-cz {background-position: -64px -33px}
.flag.flag-de {background-position: -80px -33px}
.flag.flag-dj {background-position: -96px -33px}
.flag.flag-dk {background-position: -112px -33px}
.flag.flag-dm {background-position: -128px -33px}
.flag.flag-do {background-position: -144px -33px}
.flag.flag-dz {background-position: -160px -33px}
.flag.flag-ec {background-position: -176px -33px}
.flag.flag-ee {background-position: -192px -33px}
.flag.flag-eg {background-position: -208px -33px}
.flag.flag-eh {background-position: -224px -33px}
.flag.flag-england {background-position: -240px -33px}
.flag.flag-er {background-position: 0 -44px}
.flag.flag-es {background-position: -16px -44px}
.flag.flag-et {background-position: -32px -44px}
.flag.flag-eu {background-position: -48px -44px}
.flag.flag-fi {background-position: -64px -44px}
.flag.flag-fj {background-position: -80px -44px}
.flag.flag-fk {background-position: -96px -44px}
.flag.flag-fm {background-position: -112px -44px}
.flag.flag-fo {background-position: -128px -44px}
.flag.flag-fr {background-position: -144px -44px}
.flag.flag-ga {background-position: -160px -44px}
.flag.flag-gb {background-position: -176px -44px}
.flag.flag-gd {background-position: -192px -44px}
.flag.flag-ge {background-position: -208px -44px}
.flag.flag-gf {background-position: -224px -44px}
.flag.flag-gg {background-position: -240px -44px}
.flag.flag-gh {background-position: 0 -55px}
.flag.flag-gi {background-position: -16px -55px}
.flag.flag-gl {background-position: -32px -55px}
.flag.flag-gm {background-position: -48px -55px}
.flag.flag-gn {background-position: -64px -55px}
.flag.flag-gp {background-position: -80px -55px}
.flag.flag-gq {background-position: -96px -55px}
.flag.flag-gr {background-position: -112px -55px}
.flag.flag-gs {background-position: -128px -55px}
.flag.flag-gt {background-position: -144px -55px}
.flag.flag-gu {background-position: -160px -55px}
.flag.flag-gw {background-position: -176px -55px}
.flag.flag-gy {background-position: -192px -55px}
.flag.flag-hk {background-position: -208px -55px}
.flag.flag-hm {background-position: -224px -55px}
.flag.flag-hn {background-position: -240px -55px}
.flag.flag-hr {background-position: 0 -66px}
.flag.flag-ht {background-position: -16px -66px}
.flag.flag-hu {background-position: -32px -66px}
.flag.flag-ic {background-position: -48px -66px}
.flag.flag-id {background-position: -64px -66px}
.flag.flag-ie {background-position: -80px -66px}
.flag.flag-il {background-position: -96px -66px}
.flag.flag-im {background-position: -112px -66px}
.flag.flag-in {background-position: -128px -66px}
.flag.flag-io {background-position: -144px -66px}
.flag.flag-iq {background-position: -160px -66px}
.flag.flag-ir {background-position: -176px -66px}
.flag.flag-is {background-position: -192px -66px}
.flag.flag-it {background-position: -208px -66px}
.flag.flag-je {background-position: -224px -66px}
.flag.flag-jm {background-position: -240px -66px}
.flag.flag-jo {background-position: 0 -77px}
.flag.flag-jp {background-position: -16px -77px}
.flag.flag-ke {background-position: -32px -77px}
.flag.flag-kg {background-position: -48px -77px}
.flag.flag-kh {background-position: -64px -77px}
.flag.flag-ki {background-position: -80px -77px}
.flag.flag-km {background-position: -96px -77px}
.flag.flag-kn {background-position: -112px -77px}
.flag.flag-kp {background-position: -128px -77px}
.flag.flag-kr {background-position: -144px -77px}
.flag.flag-kurdistan {background-position: -160px -77px}
.flag.flag-kw {background-position: -176px -77px}
.flag.flag-ky {background-position: -192px -77px}
.flag.flag-kz {background-position: -208px -77px}
.flag.flag-la {background-position: -224px -77px}
.flag.flag-lb {background-position: -240px -77px}
.flag.flag-lc {background-position: 0 -88px}
.flag.flag-li {background-position: -16px -88px}
.flag.flag-lk {background-position: -32px -88px}
.flag.flag-lr {background-position: -48px -88px}
.flag.flag-ls {background-position: -64px -88px}
.flag.flag-lt {background-position: -80px -88px}
.flag.flag-lu {background-position: -96px -88px}
.flag.flag-lv {background-position: -112px -88px}
.flag.flag-ly {background-position: -128px -88px}
.flag.flag-ma {background-position: -144px -88px}
.flag.flag-mc {background-position: -160px -88px}
.flag.flag-md {background-position: -176px -88px}
.flag.flag-me {background-position: -192px -88px}
.flag.flag-mg {background-position: -208px -88px}
.flag.flag-mh {background-position: -224px -88px}
.flag.flag-mk {background-position: -240px -88px}
.flag.flag-ml {background-position: 0 -99px}
.flag.flag-mm {background-position: -16px -99px}
.flag.flag-mn {background-position: -32px -99px}
.flag.flag-mo {background-position: -48px -99px}
.flag.flag-mp {background-position: -64px -99px}
.flag.flag-mq {background-position: -80px -99px}
.flag.flag-mr {background-position: -96px -99px}
.flag.flag-ms {background-position: -112px -99px}
.flag.flag-mt {background-position: -128px -99px}
.flag.flag-mu {background-position: -144px -99px}
.flag.flag-mv {background-position: -160px -99px}
.flag.flag-mw {background-position: -176px -99px}
.flag.flag-mx {background-position: -192px -99px}
.flag.flag-my {background-position: -208px -99px}
.flag.flag-mz {background-position: -224px -99px}
.flag.flag-na {background-position: -240px -99px}
.flag.flag-nc {background-position: 0 -110px}
.flag.flag-ne {background-position: -16px -110px}
.flag.flag-nf {background-position: -32px -110px}
.flag.flag-ng {background-position: -48px -110px}
.flag.flag-ni {background-position: -64px -110px}
.flag.flag-nl {background-position: -80px -110px}
.flag.flag-no {background-position: -96px -110px}
.flag.flag-np {background-position: -112px -110px}
.flag.flag-nr {background-position: -128px -110px}
.flag.flag-nu {background-position: -144px -110px}
.flag.flag-nz {background-position: -160px -110px}
.flag.flag-om {background-position: -176px -110px}
.flag.flag-pa {background-position: -192px -110px}
.flag.flag-pe {background-position: -208px -110px}
.flag.flag-pf {background-position: -224px -110px}
.flag.flag-pg {background-position: -240px -110px}
.flag.flag-ph {background-position: 0 -121px}
.flag.flag-pk {background-position: -16px -121px}
.flag.flag-pl {background-position: -32px -121px}
.flag.flag-pm {background-position: -48px -121px}
.flag.flag-pn {background-position: -64px -121px}
.flag.flag-pr {background-position: -80px -121px}
.flag.flag-ps {background-position: -96px -121px}
.flag.flag-pt {background-position: -112px -121px}
.flag.flag-pw {background-position: -128px -121px}
.flag.flag-py {background-position: -144px -121px}
.flag.flag-qa {background-position: -160px -121px}
.flag.flag-re {background-position: -176px -121px}
.flag.flag-ro {background-position: -192px -121px}
.flag.flag-rs {background-position: -208px -121px}
.flag.flag-ru {background-position: -224px -121px}
.flag.flag-rw {background-position: -240px -121px}
.flag.flag-sa {background-position: 0 -132px}
.flag.flag-sb {background-position: -16px -132px}
.flag.flag-sc {background-position: -32px -132px}
.flag.flag-scotland {background-position: -48px -132px}
.flag.flag-sd {background-position: -64px -132px}
.flag.flag-se {background-position: -80px -132px}
.flag.flag-sg {background-position: -96px -132px}
.flag.flag-sh {background-position: -112px -132px}
.flag.flag-si {background-position: -128px -132px}
.flag.flag-sk {background-position: -144px -132px}
.flag.flag-sl {background-position: -160px -132px}
.flag.flag-sm {background-position: -176px -132px}
.flag.flag-sn {background-position: -192px -132px}
.flag.flag-so {background-position: -208px -132px}
.flag.flag-somaliland {background-position: -224px -132px}
.flag.flag-sr {background-position: -240px -132px}
.flag.flag-ss {background-position: 0 -143px}
.flag.flag-st {background-position: -16px -143px}
.flag.flag-sv {background-position: -32px -143px}
.flag.flag-sx {background-position: -48px -143px}
.flag.flag-sy {background-position: -64px -143px}
.flag.flag-sz {background-position: -80px -143px}
.flag.flag-tc {background-position: -96px -143px}
.flag.flag-td {background-position: -112px -143px}
.flag.flag-tf {background-position: -128px -143px}
.flag.flag-tg {background-position: -144px -143px}
.flag.flag-th {background-position: -160px -143px}
.flag.flag-tj {background-position: -176px -143px}
.flag.flag-tk {background-position: -192px -143px}
.flag.flag-tl {background-position: -208px -143px}
.flag.flag-tm {background-position: -224px -143px}
.flag.flag-tn {background-position: -240px -143px}
.flag.flag-to {background-position: 0 -154px}
.flag.flag-tr {background-position: -16px -154px}
.flag.flag-tt {background-position: -32px -154px}
.flag.flag-tv {background-position: -48px -154px}
.flag.flag-tw {background-position: -64px -154px}
.flag.flag-tz {background-position: -80px -154px}
.flag.flag-ua {background-position: -96px -154px}
.flag.flag-ug {background-position: -112px -154px}
.flag.flag-um {background-position: -128px -154px}
.flag.flag-us {background-position: -144px -154px}
.flag.flag-uy {background-position: -160px -154px}
.flag.flag-uz {background-position: -176px -154px}
.flag.flag-va {background-position: -192px -154px}
.flag.flag-vc {background-position: -208px -154px}
.flag.flag-ve {background-position: -224px -154px}
.flag.flag-vg {background-position: -240px -154px}
.flag.flag-vi {background-position: 0 -165px}
.flag.flag-vn {background-position: -16px -165px}
.flag.flag-vu {background-position: -32px -165px}
.flag.flag-wales {background-position: -48px -165px}
.flag.flag-wf {background-position: -64px -165px}
.flag.flag-ws {background-position: -80px -165px}
.flag.flag-ye {background-position: -96px -165px}
.flag.flag-yt {background-position: -112px -165px}
.flag.flag-za {background-position: -128px -165px}
.flag.flag-zanzibar {background-position: -144px -165px}
.flag.flag-zm {background-position: -160px -165px}
.flag.flag-zw {background-position: -176px -165px}