diff --git a/app/controllers/account_controller.rb b/app/controllers/account_controller.rb index a5cf4e07e..091a280e6 100644 --- a/app/controllers/account_controller.rb +++ b/app/controllers/account_controller.rb @@ -11,6 +11,7 @@ class AccountController < ApplicationController if @account.update(account_params) redirect_to account_path, notice: t("flash.actions.save_changes.notice") else + @account.errors.messages.delete(:organization) render :show end end @@ -22,7 +23,11 @@ class AccountController < ApplicationController end def account_params - params.require(:account).permit(:first_name, :last_name, :nickname, :use_nickname, :email_on_debate_comment, :email_on_comment_reply) + if @account.organization? + params.require(:account).permit(:phone_number, :email_on_debate_comment, :email_on_comment_reply, organization_attributes: [:name]) + else + params.require(:account).permit(:first_name, :last_name, :phone_number, :nickname, :use_nickname, :email_on_debate_comment, :email_on_comment_reply) + end end end diff --git a/app/controllers/admin/base_controller.rb b/app/controllers/admin/base_controller.rb index f011be7d9..7a812104b 100644 --- a/app/controllers/admin/base_controller.rb +++ b/app/controllers/admin/base_controller.rb @@ -11,4 +11,4 @@ class Admin::BaseController < ApplicationController raise CanCan::AccessDenied unless current_user.try(:administrator?) end -end \ No newline at end of file +end diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb index 870d7ef55..f7aa5c440 100644 --- a/app/controllers/admin/dashboard_controller.rb +++ b/app/controllers/admin/dashboard_controller.rb @@ -1,5 +1,4 @@ class Admin::DashboardController < Admin::BaseController - layout 'admin' def index end diff --git a/app/controllers/admin/organizations_controller.rb b/app/controllers/admin/organizations_controller.rb new file mode 100644 index 000000000..c7e3c159c --- /dev/null +++ b/app/controllers/admin/organizations_controller.rb @@ -0,0 +1,32 @@ +class Admin::OrganizationsController < Admin::BaseController + before_filter :set_valid_filters + before_filter :parse_filter + + load_and_authorize_resource + + def index + @organizations = @organizations.send(@filter) + @organizations = @organizations.includes(:user).order(:name, 'users.email') + end + + def verify + @organization.verify + redirect_to action: :index, filter: @filter + end + + def reject + @organization.reject + redirect_to action: :index, filter: @filter + end + + private + def set_valid_filters + @valid_filters = %w{all pending verified rejected} + end + + def parse_filter + @filter = params[:filter] + @filter = 'all' unless @valid_filters.include?(@filter) + end + +end diff --git a/app/controllers/moderation/base_controller.rb b/app/controllers/moderation/base_controller.rb index 0ee155e03..f2a794526 100644 --- a/app/controllers/moderation/base_controller.rb +++ b/app/controllers/moderation/base_controller.rb @@ -1,4 +1,6 @@ class Moderation::BaseController < ApplicationController + layout 'admin' + before_action :authenticate_user! skip_authorization_check diff --git a/app/controllers/moderation/dashboard_controller.rb b/app/controllers/moderation/dashboard_controller.rb index 86196af37..ceaddd6f4 100644 --- a/app/controllers/moderation/dashboard_controller.rb +++ b/app/controllers/moderation/dashboard_controller.rb @@ -1,5 +1,4 @@ class Moderation::DashboardController < Moderation::BaseController - layout 'admin' def index end diff --git a/app/controllers/organizations/registrations_controller.rb b/app/controllers/organizations/registrations_controller.rb new file mode 100644 index 000000000..8602ba4ba --- /dev/null +++ b/app/controllers/organizations/registrations_controller.rb @@ -0,0 +1,26 @@ +class Organizations::RegistrationsController < Devise::RegistrationsController + def new + super do |user| + user.build_organization + end + end + + def create + build_resource(sign_up_params) + if resource.valid_with_captcha? + super do |user| + # Removes unuseful "organization is invalid" error message + user.errors.messages.delete(:organization) + end + else + render :new + end + end + + private + + def sign_up_params + params.require(:user).permit(:email, :password, :phone_number, :password_confirmation, :captcha, :captcha_key, organization_attributes: [:name]) + end + +end diff --git a/app/controllers/registrations_controller.rb b/app/controllers/users/registrations_controller.rb similarity index 58% rename from app/controllers/registrations_controller.rb rename to app/controllers/users/registrations_controller.rb index a3449ca8b..65da7e1e0 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/users/registrations_controller.rb @@ -1,4 +1,4 @@ -class RegistrationsController < Devise::RegistrationsController +class Users::RegistrationsController < Devise::RegistrationsController def create build_resource(sign_up_params) @@ -9,11 +9,10 @@ class RegistrationsController < Devise::RegistrationsController end end - private def sign_up_params - params.require(:user).permit(:first_name, :last_name, :email, :password, :password_confirmation, :use_nickname, :nickname, :captcha, :captcha_key) + params.require(:user).permit(:first_name, :last_name, :email, :phone_number, :password, :password_confirmation, :use_nickname, :nickname, :captcha, :captcha_key) end end diff --git a/app/models/ability.rb b/app/models/ability.rb index 62947b52c..9cce129a2 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -8,20 +8,31 @@ class Ability if user # logged-in users can [:read, :update], User, id: user.id - can [:read, :create, :vote], Debate + can :read, Debate can :update, Debate do |debate| debate.editable_by?(user) end - can [:create, :vote], Comment + can :create, Comment + can :create, Debate - if user.moderator? - can [:hide], Comment - can [:hide], Debate + unless user.organization? + can :vote, Debate + can :vote, Comment + end - elsif user.administrator? - can [:restore], Comment - can [:restore], Debate + if user.moderator? || user.administrator? + can :read, Organization + can(:verify, Organization){ |o| !o.verified? } + can(:reject, Organization){ |o| !o.rejected? } + + can :hide, Comment + can :hide, Debate + end + + if user.administrator? + can :restore, Comment + can :restore, Debate end end end diff --git a/app/models/organization.rb b/app/models/organization.rb new file mode 100644 index 000000000..de120de9e --- /dev/null +++ b/app/models/organization.rb @@ -0,0 +1,31 @@ +class Organization < ActiveRecord::Base + + belongs_to :user + + validates :name, presence: true + + delegate :email, :phone_number, to: :user + + scope :pending, -> { where(verified_at: nil, rejected_at: nil) } + scope :verified, -> { where("verified_at is not null and (rejected_at is null or rejected_at < verified_at)") } + scope :rejected, -> { where("rejected_at is not null and (verified_at is null or verified_at < rejected_at)") } + + def verify + update(verified_at: Time.now) + end + + def reject + update(rejected_at: Time.now) + end + + def verified? + verified_at.present? && + (rejected_at.blank? || rejected_at < verified_at) + end + + def rejected? + rejected_at.present? && + (verified_at.blank? || verified_at < rejected_at) + end + +end diff --git a/app/models/user.rb b/app/models/user.rb index 16d1de154..ba4b32fa9 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -5,15 +5,28 @@ class User < ActiveRecord::Base acts_as_voter - validates :first_name, presence: true, unless: :use_nickname? - validates :last_name, presence: true, unless: :use_nickname? - validates :nickname, presence: true, if: :use_nickname? + has_one :administrator + has_one :moderator + has_one :organization + + validates :first_name, presence: true, if: :use_first_name? + validates :last_name, presence: true, if: :use_last_name? + validates :nickname, presence: true, if: :use_nickname? validates :official_level, inclusion: {in: 0..5} - scope :officials, -> { where("official_level > 0") } + validates_associated :organization, message: false + + accepts_nested_attributes_for :organization + + scope :administrators, -> { joins(:administrators) } + scope :moderators, -> { joins(:moderator) } + scope :organizations, -> { joins(:organization) } + scope :officials, -> { where("official_level > 0") } def name - use_nickname? ? nickname : "#{first_name} #{last_name}" + return nickname if use_nickname? + return organization.name if organization? + "#{first_name} #{last_name}" end def debate_votes(debates) @@ -22,11 +35,15 @@ class User < ActiveRecord::Base end def administrator? - @is_administrator ||= Administrator.where(user_id: id).exists? + administrator.present? end def moderator? - @is_moderator ||= Moderator.where(user_id: id).exists? + moderator.present? + end + + def organization? + organization.present? end def official? @@ -45,4 +62,13 @@ class User < ActiveRecord::Base def self.with_email(e) e.present? ? where(email: e) : none end + + private + def use_first_name? + !organization? && !use_nickname? + end + + def use_last_name? + use_first_name? + end end diff --git a/app/views/account/show.html.erb b/app/views/account/show.html.erb index d38f63df7..fb647ef1d 100644 --- a/app/views/account/show.html.erb +++ b/app/views/account/show.html.erb @@ -11,12 +11,24 @@

<%= t("account.show.personal")%>

- <%= f.text_field :first_name, placeholder: t("account.show.first_name_label") %> - <%= f.text_field :last_name, placeholder: t("account.show.last_name_label") %> - <%= f.text_field :nickname, placeholder: t("account.show.nickname_label") %> + <% if @account.organization? %> - <%= f.check_box :use_nickname, label: false %> - <%= t("account.show.use_nickname_label") %> + <%= f.fields_for :organization do |fo| %> + <%= fo.text_field :name, autofocus: true, placeholder: t("account.show.organization_name_label") %> + <% end %> + + <% else %> + + <%= f.text_field :first_name, placeholder: t("account.show.first_name_label") %> + <%= f.text_field :last_name, placeholder: t("account.show.last_name_label") %> + <%= f.text_field :nickname, placeholder: t("account.show.nickname_label") %> + + <%= f.check_box :use_nickname, label: false %> + <%= t("account.show.use_nickname_label") %> + + <% end %> + + <%= f.text_field :phone_number, placeholder: t("account.show.phone_number_label") %>
@@ -41,5 +53,6 @@
<% end %> + - \ No newline at end of file + diff --git a/app/views/admin/_menu.html.erb b/app/views/admin/_menu.html.erb index 1193d6638..585ca41a0 100644 --- a/app/views/admin/_menu.html.erb +++ b/app/views/admin/_menu.html.erb @@ -25,6 +25,13 @@ <% end %> +
  • > + <%= link_to admin_organizations_path do %> + + <%= t('admin.menu.organizations') %> + <% end %> +
  • +
  • > <%= link_to admin_officials_path do %> diff --git a/app/views/admin/organizations/index.html.erb b/app/views/admin/organizations/index.html.erb new file mode 100644 index 000000000..b22dfdbc9 --- /dev/null +++ b/app/views/admin/organizations/index.html.erb @@ -0,0 +1,48 @@ +
    + +

    <%= t('admin.organizations.index.title') %>

    + +

    + <%= t('admin.organizations.index.filter') %>: + + <% @valid_filters.each do |filter| %> + <% if @filter == filter %> + <%= t("admin.organizations.index.filters.#{filter}") %> + <% else %> + <%= link_to t("admin.organizations.index.filters.#{filter}"), + admin_organizations_path(filter: filter) %> + <% end %> + <% end %> +

    + + + <% @organizations.each do |organization| %> + + + + + <% if organization.verified? %> + + <% end %> + <% if can? :verify, organization %> + + <% end %> + <% if organization.rejected? %> + + <% end %> + <% if can? :reject, organization %> + + <% end %> + + <% end %> +
    <%= organization.name %><%= organization.email %><%= organization.phone_number %><%= t('admin.organizations.index.verified') %><%= link_to t('admin.organizations.index.verify'), + verify_admin_organization_path(organization, filter: @filter), + method: :put + %> + <%= t('admin.organizations.index.rejected') %><%= link_to t('admin.organizations.index.reject'), + reject_admin_organization_path(organization, filter: @filter), + method: :put + %> +
    + +
    diff --git a/app/views/devise/registrations/edit.html.erb b/app/views/devise/registrations/edit.html.erb deleted file mode 100644 index 980df3333..000000000 --- a/app/views/devise/registrations/edit.html.erb +++ /dev/null @@ -1,49 +0,0 @@ -
    -
    -
    -  <%= link_to t("devise_views.registrations.edit.back_link"), :back, class: "left back" %> - -

    <%= t("devise_views.registrations.edit.edit") %> <%= resource_name.to_s.humanize %>

    - - <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> - <%= render 'shared/errors', resource: resource %> - -
    -
    - <%= f.email_field :email, autofocus: true, placeholder: t("devise_views.registrations.edit.email_label") %> -
    - -
    - <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %> -
    <%= t("devise_views.registrations.edit.waiting_for") %> <%= resource.unconfirmed_email %>
    - <% end %> -
    - -
    - <%= f.label :password, t("devise_views.registrations.edit.password_label") %> - <%= t("devise_views.registrations.edit.leave_blank") %> - <%= f.password_field :password, autocomplete: "off", - label: false, - placeholder: t("devise_views.registrations.edit.password_label") %> -
    - -
    - <%= f.password_field :password_confirmation, autocomplete: "off", placeholder: t("devise_views.registrations.edit.password_confirmation_label") %> -
    - -
    - <%= f.label :current_password, t("devise_views.registrations.edit.current_password_label") %> - <%= t("devise_views.registrations.edit.need_current") %> - <%= f.password_field :current_password, autocomplete: "off", - label: false, - placeholder: t("devise_views.registrations.edit.current_password_label") %> -
    - -
    - <%= f.submit t("devise_views.registrations.edit.update_submit"), class: "button radius" %> -
    -
    - <% end %> -
    -
    -
    \ No newline at end of file diff --git a/app/views/devise/shared/_links.html.erb b/app/views/devise/shared/_links.html.erb index d7a77614e..0fd80f429 100644 --- a/app/views/devise/shared/_links.html.erb +++ b/app/views/devise/shared/_links.html.erb @@ -3,10 +3,16 @@ <%= link_to t("devise_views.shared.links.login"), new_session_path(resource_name) %>
    <% end -%> - <%- if devise_mapping.registerable? && controller_name != 'registrations' %> - <%= link_to t("devise_views.shared.links.signup"), new_registration_path(resource_name) %>
    + <%- if devise_mapping.registerable? && + controller_name != 'registrations' || + controller_path != 'users/registrations' %> + <%= link_to t("devise_views.shared.links.signup"), new_user_registration_path %>
    <% end -%> + <%- if devise_mapping.registerable? && controller_name == 'registrations' && controller_path != 'organizations/registrations' %> + <%= link_to t("devise_views.shared.links.organization_signup"), new_organization_registration_path %>
    + <% end -%> + <%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %> <%= link_to t("devise_views.shared.links.new_password"), new_password_path(resource_name) %>
    <% end -%> @@ -24,4 +30,5 @@ <%= link_to t("devise_views.shared.links.signin_with_provider", provider: provider.to_s.titleize), omniauth_authorize_path(resource_name, provider) %>
    <% end -%> <% end -%> - \ No newline at end of file + + diff --git a/app/views/organizations/registrations/new.html.erb b/app/views/organizations/registrations/new.html.erb new file mode 100644 index 000000000..4f8b05862 --- /dev/null +++ b/app/views/organizations/registrations/new.html.erb @@ -0,0 +1,36 @@ +
    +
    +
    +

    <%= t("devise_views.organizations.registrations.new.title") %>

    + + <%= form_for(resource, as: :user, url: organization_registration_path) do |f| %> + <%= render 'shared/errors', resource: resource %> +
    +
    + + <%= f.fields_for :organization do |fo| %> + <%= fo.text_field :name, autofocus: true, placeholder: t("devise_views.organizations.registrations.new.organization_name_label") %> + <% end %> + + <%= f.email_field :email, placeholder: t("devise_views.organizations.registrations.new.email_label") %> + + <%= f.text_field :phone_number, placeholder: t("devise_views.organizations.registrations.new.phone_number_label") %> + + <%= f.password_field :password, autocomplete: "off", + placeholder: t("devise_views.organizations.registrations.new.password_label") %> + + <%= f.password_field :password_confirmation, autocomplete: "off", + label: t("devise_views.organizations.registrations.new.password_confirmation_label"), + placeholder: t("devise_views.organizations.registrations.new.password_confirmation_label") %> + + <%= f.simple_captcha input_html: {required: false} %> + + <%= f.submit t("devise_views.organizations.registrations.new.submit"), class: "button radius expand" %> +
    +
    + <% end %> + + <%= render "devise/shared/links" %> +
    +
    +
    diff --git a/app/views/users/registrations/edit.html.erb b/app/views/users/registrations/edit.html.erb new file mode 100644 index 000000000..b17b8f2e1 --- /dev/null +++ b/app/views/users/registrations/edit.html.erb @@ -0,0 +1,45 @@ +
    + +
    diff --git a/app/views/devise/registrations/new.html.erb b/app/views/users/registrations/new.html.erb similarity index 50% rename from app/views/devise/registrations/new.html.erb rename to app/views/users/registrations/new.html.erb index 2912b82dc..c39c5f5f1 100644 --- a/app/views/devise/registrations/new.html.erb +++ b/app/views/users/registrations/new.html.erb @@ -1,31 +1,31 @@
    -

    <%= t("devise_views.registrations.new.title") %>

    - +

    <%= t("devise_views.users.registrations.new.title") %>

    <%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %> <%= render 'shared/errors', resource: resource %> -
    <%= f.text_field :first_name, autofocus: true, - placeholder: t("devise_views.registrations.new.first_name_label") %> - <%= f.text_field :last_name, placeholder: t("devise_views.registrations.new.last_name_label") %> - <%= f.email_field :email, placeholder: t("devise_views.registrations.new.email_label") %> - <%= f.text_field :nickname, placeholder: t("devise_views.registrations.new.nickname_label") %> + placeholder: t("devise_views.users.registrations.new.first_name_label") %> + <%= f.text_field :last_name, placeholder: t("devise_views.users.registrations.new.last_name_label") %> + <%= f.email_field :email, placeholder: t("devise_views.users.registrations.new.email_label") %> + <%= f.text_field :nickname, placeholder: t("devise_views.users.registrations.new.nickname_label") %> - <%= f.check_box :use_nickname, label: t("devise_views.registrations.new.use_nickname_label") %> + <%= f.check_box :use_nickname, label: t("devise_views.users.registrations.new.use_nickname_label") %> + + <%= f.text_field :phone_number, placeholder: t("devise_views.users.registrations.new.phone_number_label") %> <%= f.password_field :password, autocomplete: "off", - placeholder: t("devise_views.registrations.new.password_label") %> + placeholder: t("devise_views.users.registrations.new.password_label") %> <%= f.password_field :password_confirmation, autocomplete: "off", - label: t("devise_views.registrations.new.password_confirmation_label"), - placeholder: t("devise_views.registrations.new.password_confirmation_label") %> + label: t("devise_views.users.registrations.new.password_confirmation_label"), + placeholder: t("devise_views.users.registrations.new.password_confirmation_label") %> <%= f.simple_captcha input_html: {required: false} %> - <%= f.submit t("devise_views.registrations.new.submit"), class: "button radius expand" %> + <%= f.submit t("devise_views.users.registrations.new.submit"), class: "button radius expand" %>
    <% end %> @@ -33,4 +33,4 @@ <%= render "devise/shared/links" %>
    -
    \ No newline at end of file + diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml index d2eef0c1d..14d8b82a7 100644 --- a/config/i18n-tasks.yml +++ b/config/i18n-tasks.yml @@ -93,6 +93,8 @@ ignore_missing: ## Consider these keys used: ignore_unused: + - 'activerecord.*' + - 'admin.organizations.index.filter.*' - 'unauthorized.*' - 'simple_captcha.*' - 'admin.officials.level_*' diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index f761b972f..f7cdfb292 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -211,7 +211,7 @@ Devise.setup do |config| # Turn scoped views on. Before rendering "sessions/new", it will first check for # "users/sessions/new". It's turned off by default because it's slower if you # are using only default views. - # config.scoped_views = false + config.scoped_views = true # Configure the default scope given to Warden. By default it's the first # devise role declared in your routes (usually :user). diff --git a/config/initializers/simple_captcha.rb b/config/initializers/simple_captcha.rb index 8b55c0724..2ac5826c3 100644 --- a/config/initializers/simple_captcha.rb +++ b/config/initializers/simple_captcha.rb @@ -34,4 +34,4 @@ SimpleCaptcha.setup do |sc| # "-fill '#86818B'", # "-border 1", # "-bordercolor '#E0E2E3'"]) -end \ No newline at end of file +end diff --git a/config/locales/activerecord.en.yml b/config/locales/activerecord.en.yml index dcf924632..8a4063d9a 100644 --- a/config/locales/activerecord.en.yml +++ b/config/locales/activerecord.en.yml @@ -6,6 +6,7 @@ en: tag: Topic user: User vote: Vote + organization: Organization attributes: comment: body: Comment @@ -21,5 +22,8 @@ en: last_name: "Last name" nickname: Nickname password: Password + phone_number: Phone number official_position: Official position official_level: Official level + organization: + name: Organization name diff --git a/config/locales/activerecord.es.yml b/config/locales/activerecord.es.yml index dccc9cee2..b5fa8050e 100644 --- a/config/locales/activerecord.es.yml +++ b/config/locales/activerecord.es.yml @@ -6,6 +6,7 @@ es: tag: Tema user: Usuario vote: Voto + organization: Organización attributes: comment: body: Comentario @@ -21,5 +22,8 @@ es: last_name: Apellidos nickname: Pseudónimo password: Contraseña + phone_number: Teléfono official_position: Cargo público - official_level: Nivel del cargo \ No newline at end of file + official_level: Nivel del cargo + organization: + name: Nombre de organización diff --git a/config/locales/admin.en.yml b/config/locales/admin.en.yml index 154cde012..e0ed572b5 100644 --- a/config/locales/admin.en.yml +++ b/config/locales/admin.en.yml @@ -13,8 +13,22 @@ en: debate_topics: Debate topics hidden_debates: Hidden debates hidden_comments: Hidden comments + organizations: Organizations officials: Officials stats: Statistics + organizations: + index: + title: Organizations + verify: Verify + reject: Reject + verified: Verified + rejected: Rejected + filter: Filter + filters: + all: All + pending: Pending + verified: Verified + rejected: Rejected actions: hide: Hide restore: Restore diff --git a/config/locales/admin.es.yml b/config/locales/admin.es.yml index 419300cf9..108c6f7bb 100644 --- a/config/locales/admin.es.yml +++ b/config/locales/admin.es.yml @@ -13,8 +13,22 @@ es: debate_topics: Temas de debate hidden_debates: Debates ocultos hidden_comments: Comentarios ocultos + organizations: Organizaciones officials: Cargos públicos stats: Estadísticas + organizations: + index: + title: Organizaciones + verify: Verificar + reject: Rechazar + verified: Verificada + rejected: Rechazada + filter: Filtro + filters: + all: Todas + pending: Pendientes + verified: Verificadas + rejected: Rechazadas actions: hide: Ocultar restore: Permitir diff --git a/config/locales/devise_views.en.yml b/config/locales/devise_views.en.yml index 5eb4eccce..e5a1ec48d 100644 --- a/config/locales/devise_views.en.yml +++ b/config/locales/devise_views.en.yml @@ -35,28 +35,40 @@ en: title: "Forgot your password?" email_label: "Email" send_submit: "Send me reset password instructions" - registrations: - edit: - edit: "Edit" - email_label: "Email" - waiting_for: "Currently waiting confirmation for:" - leave_blank: "Leave blank if you don't want to change it" - password_label: "New password" - password_confirmation_label: "Confirm new password" - current_password_label: "Current password" - need_current: "We need your current password to confirm your changes" - update_submit: "Update" - back_link: "Back" - new: - title: "Sign up" - first_name_label: "First name" - last_name_label: "Last name" - nickname_label: "Nickname" - use_nickname_label: "Use nickname" - email_label: "Email" - password_label: "Password" - password_confirmation_label: "Confirm password" - submit: "Sign up" + users: + registrations: + edit: + edit: "Edit" + email_label: "Email" + waiting_for: "Currently waiting confirmation for:" + leave_blank: "Leave blank if you don't want to change it" + password_label: "New password" + password_confirmation_label: "Confirm new password" + current_password_label: "Current password" + need_current: "We need your current password to confirm your changes" + update_submit: "Update" + back_link: "Back" + new: + title: "Sign up" + first_name_label: "First name" + last_name_label: "Last name" + nickname_label: "Nickname" + use_nickname_label: "Use nickname" + email_label: "Email" + phone_number_label: "Phone number" + password_label: "Password" + password_confirmation_label: "Confirm password" + submit: "Sign up" + organizations: + registrations: + new: + title: "Sign up as organization" + organization_name_label: "Organization name" + email_label: "Email" + password_label: "Password" + phone_number_label: "Phone number" + password_confirmation_label: "Confirm password" + submit: "Sign up" sessions: new: title: "Log in" @@ -74,6 +86,7 @@ en: login: "Log in" signup: "Sign up" signin_with_provider: "Sign in with %{provider}" + organization_signup: "Sign up as an organization" new_password: "Forgot your password?" new_confirmation: "Didn't receive confirmation instructions?" new_unlock: "Didn't receive unlock instructions?" diff --git a/config/locales/devise_views.es.yml b/config/locales/devise_views.es.yml index 6c1997745..c44ddfb31 100644 --- a/config/locales/devise_views.es.yml +++ b/config/locales/devise_views.es.yml @@ -8,7 +8,7 @@ es: confirmation_instructions: welcome: "Bienvenido" text: "Puedes confirmar tu cuenta de correo electrónico en el siguiente enlace:" - confirm_link: "Confirmar my cuenta" + confirm_link: "Confirmar mi cuenta" reset_password_instructions: hello: "Hola" text: "Se ha solicitado un enlace para cambiar tu contraseña, puedes hacerlo en el siguiente enlace:" @@ -35,28 +35,40 @@ es: title: "¿Has olvidado tu contraseña?" email_label: "Email" send_submit: "Recibir instrucciones para recuperar mi contraseña" - registrations: - edit: - edit: "Editar" - email_label: "Email" - waiting_for: "Esperando confirmación de:" - leave_blank: "Dejar en blanco si no deseas cambiarla" - password_label: "Contraseña nueva" - password_confirmation_label: "Confirmar contraseña nueva" - current_password_label: "Contraseña actual" - need_current: "Necesitamos tu contraseña actual para confirmar los cambios" - update_submit: "Actualizar" - back_link: "Atrás" - new: - title: "Registrarse" - first_name_label: "Nombre" - last_name_label: "Apellidos" - nickname_label: "Pseudónimo" - use_nickname_label: "Usar pseudónimo" - email_label: "Email" - password_label: "Contraseña" - password_confirmation_label: "Confirmar contraseña" - submit: "Registrarse" + users: + registrations: + edit: + edit: "Editar" + email_label: "Email" + waiting_for: "Esperando confirmación de:" + leave_blank: "Dejar en blanco si no deseas cambiarla" + password_label: "Contraseña nueva" + password_confirmation_label: "Confirmar contraseña nueva" + current_password_label: "Contraseña actual" + need_current: "Necesitamos tu contraseña actual para confirmar los cambios" + update_submit: "Actualizar" + back_link: "Atrás" + new: + title: "Registrarse" + first_name_label: "Nombre" + last_name_label: "Apellidos" + nickname_label: "Pseudónimo" + use_nickname_label: "Usar pseudónimo" + email_label: "Email" + phone_number_label: "Teléfono" + password_label: "Contraseña" + password_confirmation_label: "Confirmar contraseña" + submit: "Registrarse" + organizations: + registrations: + new: + title: "Registrarse como organización" + organization_name_label: "Nombre de la organización" + email_label: "Email" + password_label: "Contraseña" + phone_number_label: "Teléfono" + password_confirmation_label: "Confirmar contraseña" + submit: "Registrarse" sessions: new: title: "Entrar" @@ -73,6 +85,7 @@ es: links: login: "Entrar" signup: "Registrarse" + organization_signup: "Registro para organizaciones" signin_with_provider: "Entrar con %{provider}" new_password: "¿Olvidaste tu contraseña?" new_confirmation: "¿No has recibido instrucciones para confirmar tu cuenta?" diff --git a/config/locales/en.yml b/config/locales/en.yml index 9b60136d6..4ab4d754c 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -115,6 +115,8 @@ en: personal: "Personal data" first_name_label: "First Name" last_name_label: "Last Name" + phone_number_label: "Phone number" + organization_name_label: "Organization name" use_nickname_label: "Use nickname" nickname_label: "Nickname" notifications: Notifications diff --git a/config/locales/es.yml b/config/locales/es.yml index 41dc92104..e39ced946 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -115,6 +115,8 @@ es: personal: "Datos personales" first_name_label: "Nombre" last_name_label: "Apellidos" + phone_number_label: "Teléfono" + organization_name_label: "Nombre de la organización" use_nickname_label: "Usar pseudónimo" nickname_label: "Pseudónimo" notifications: Notificaciones diff --git a/config/locales/moderation.en.yml b/config/locales/moderation.en.yml index f6cf170e6..cf53051ed 100644 --- a/config/locales/moderation.en.yml +++ b/config/locales/moderation.en.yml @@ -2,4 +2,5 @@ en: moderation: dashboard: index: - title: Moderation \ No newline at end of file + title: Moderation + diff --git a/config/locales/moderation.es.yml b/config/locales/moderation.es.yml index d54710977..a921f0827 100644 --- a/config/locales/moderation.es.yml +++ b/config/locales/moderation.es.yml @@ -2,4 +2,6 @@ es: moderation: dashboard: index: - title: Moderación \ No newline at end of file + title: Moderación + + diff --git a/config/routes.rb b/config/routes.rb index a11896b08..66b74546b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,5 +1,10 @@ Rails.application.routes.draw do - devise_for :users, controllers: { registrations: 'registrations' } + devise_for :users, controllers: { registrations: 'users/registrations' } + devise_for :organizations, class_name: 'User', + controllers: { + registrations: 'organizations/registrations', + sessions: 'devise/sessions' + } # The priority is based upon order of creation: first created -> highest priority. # See how all your routes lay out with "rake routes". @@ -24,6 +29,12 @@ Rails.application.routes.draw do namespace :admin do root to: "dashboard#index" + resources :organizations, only: :index do + member do + put :verify + put :reject + end + end resources :debates, only: [:index, :show] do member { put :restore } diff --git a/db/migrate/20150813142213_create_organizations.rb b/db/migrate/20150813142213_create_organizations.rb new file mode 100644 index 000000000..a0974df7f --- /dev/null +++ b/db/migrate/20150813142213_create_organizations.rb @@ -0,0 +1,10 @@ +class CreateOrganizations < ActiveRecord::Migration + def change + create_table :organizations do |t| + t.belongs_to :user, index: true, foreign_key: true + t.string :name, limit: 80 + t.datetime :verified_at + t.datetime :rejected_at + end + end +end diff --git a/db/migrate/20150813161654_add_phone_number_to_users.rb b/db/migrate/20150813161654_add_phone_number_to_users.rb new file mode 100644 index 000000000..070b99586 --- /dev/null +++ b/db/migrate/20150813161654_add_phone_number_to_users.rb @@ -0,0 +1,5 @@ +class AddPhoneNumberToUsers < ActiveRecord::Migration + def change + add_column :users, :phone_number, :string, limit: 30 + end +end diff --git a/db/schema.rb b/db/schema.rb index 9790fc75a..f2bbe60fc 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -73,6 +73,15 @@ ActiveRecord::Schema.define(version: 20150817150457) do add_index "moderators", ["user_id"], name: "index_moderators_on_user_id", using: :btree + create_table "organizations", force: :cascade do |t| + t.integer "user_id" + t.string "name", limit: 80 + t.datetime "verified_at" + t.datetime "rejected_at" + end + + add_index "organizations", ["user_id"], name: "index_organizations_on_user_id", using: :btree + create_table "settings", force: :cascade do |t| t.string "key" t.string "value" @@ -109,18 +118,18 @@ ActiveRecord::Schema.define(version: 20150817150457) do add_index "tags", ["name"], name: "index_tags_on_name", unique: true, using: :btree create_table "users", force: :cascade do |t| - t.string "email", default: "", null: false - t.string "encrypted_password", default: "", null: false + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" - t.integer "sign_in_count", default: 0, null: false + t.integer "sign_in_count", default: 0, null: false t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" t.string "current_sign_in_ip" t.string "last_sign_in_ip" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.string "first_name" t.string "last_name" t.string "confirmation_token" @@ -128,6 +137,7 @@ ActiveRecord::Schema.define(version: 20150817150457) do t.datetime "confirmation_sent_at" t.string "unconfirmed_email" t.string "nickname" + t.string "phone_number", limit: 30 t.boolean "use_nickname", default: false, null: false t.boolean "email_on_debate_comment", default: false t.boolean "email_on_comment_reply", default: false @@ -186,4 +196,5 @@ ActiveRecord::Schema.define(version: 20150817150457) do add_foreign_key "administrators", "users" add_foreign_key "moderators", "users" + add_foreign_key "organizations", "users" end diff --git a/spec/factories.rb b/spec/factories.rb index cc593ae15..a6090efbb 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -43,6 +43,19 @@ FactoryGirl.define do user end + factory :organization do + user + sequence(:name) { |n| "org#{n}" } + end + + factory :verified_organization, parent: :organization do + verified_at { Time.now} + end + + factory :rejected_organization, parent: :organization do + rejected_at { Time.now} + end + factory :tag, class: 'ActsAsTaggableOn::Tag' do sequence(:name) { |n| "Tag #{n} name" } diff --git a/spec/features/account_spec.rb b/spec/features/account_spec.rb index 1294d59d2..28785ed08 100644 --- a/spec/features/account_spec.rb +++ b/spec/features/account_spec.rb @@ -19,6 +19,18 @@ feature 'Account' do expect(page).to have_selector(avatar('Manuela Colau'), count: 1) end + scenario 'Show organization' do + create(:organization, user: @user, name: "Manuela Corp") + + visit account_path + + expect(page).to have_selector("input[value='Manuela Corp']") + expect(page).to_not have_selector("input[value='Manuela']") + expect(page).to_not have_selector("input[value='Colau']") + + expect(page).to have_selector(avatar('Manuela Corp'), count: 1) + end + scenario 'Edit' do visit account_path @@ -38,6 +50,24 @@ feature 'Account' do expect(page).to have_selector("input[id='account_email_on_comment_reply'][value='1']") end + scenario 'Edit Organization' do + create(:organization, user: @user, name: "Manuela Corp") + visit account_path + + fill_in 'account_organization_attributes_name', with: 'Google' + check 'account_email_on_debate_comment' + check 'account_email_on_comment_reply' + click_button 'Save changes' + + expect(page).to have_content "Saved" + + visit account_path + + expect(page).to have_selector("input[value='Google']") + expect(page).to have_selector("input[id='account_email_on_debate_comment'][value='1']") + expect(page).to have_selector("input[id='account_email_on_comment_reply'][value='1']") + end + scenario "Errors on edit" do visit account_path diff --git a/spec/features/admin/organizations_spec.rb b/spec/features/admin/organizations_spec.rb new file mode 100644 index 000000000..c3ac46d3d --- /dev/null +++ b/spec/features/admin/organizations_spec.rb @@ -0,0 +1,114 @@ +require 'rails_helper' + +feature 'Moderations::Organizations' do + + + background do + administrator = create(:user) + create(:administrator, user: administrator) + + login_as(administrator) + end + + scenario "pending organizations have links to verify and reject" do + organization = create(:organization) + + visit admin_organizations_path + expect(page).to have_link('Verify') + expect(page).to have_link('Reject') + + click_on 'Verify' + expect(current_path).to eq(admin_organizations_path) + expect(page).to have_content ('Verified') + + expect(organization.reload.verified?).to eq(true) + end + + scenario "verified organizations have link to reject" do + organization = create(:verified_organization) + + visit admin_organizations_path + expect(page).to have_content ('Verified') + expect(page).to_not have_link('Verify') + expect(page).to have_link('Reject') + + click_on 'Reject' + expect(current_path).to eq(admin_organizations_path) + expect(page).to have_content ('Rejected') + + expect(organization.reload.rejected?).to eq(true) + end + + scenario "rejected organizations have link to verify" do + organization = create(:rejected_organization) + + visit admin_organizations_path + expect(page).to have_link('Verify') + expect(page).to_not have_link('Reject', exact: true) + + click_on 'Verify' + expect(current_path).to eq(admin_organizations_path) + expect(page).to have_content ('Verified') + + expect(organization.reload.verified?).to eq(true) + end + + scenario "Current filter is properly highlighted" do + visit admin_organizations_path + expect(page).to_not have_link('All') + expect(page).to have_link('Pending') + expect(page).to have_link('Verified') + expect(page).to have_link('Rejected') + + visit admin_organizations_path(filter: 'all') + expect(page).to_not have_link('All') + expect(page).to have_link('Pending') + expect(page).to have_link('Verified') + expect(page).to have_link('Rejected') + + visit admin_organizations_path(filter: 'pending') + expect(page).to have_link('All') + expect(page).to_not have_link('Pending') + expect(page).to have_link('Verified') + expect(page).to have_link('Rejected') + + visit admin_organizations_path(filter: 'verified') + expect(page).to have_link('All') + expect(page).to have_link('Pending') + expect(page).to_not have_link('Verified') + expect(page).to have_link('Rejected') + + visit admin_organizations_path(filter: 'rejected') + expect(page).to have_link('All') + expect(page).to have_link('Pending') + expect(page).to have_link('Verified') + expect(page).to_not have_link('Rejected') + end + + scenario "Filtering organizations" do + create(:organization, name: "Pending Organization") + create(:rejected_organization, name: "Rejected Organization") + create(:verified_organization, name: "Verified Organization") + + visit admin_organizations_path(filter: 'all') + expect(page).to have_content('Pending Organization') + expect(page).to have_content('Rejected Organization') + expect(page).to have_content('Verified Organization') + + visit admin_organizations_path(filter: 'pending') + expect(page).to have_content('Pending Organization') + expect(page).to_not have_content('Rejected Organization') + expect(page).to_not have_content('Verified Organization') + + visit admin_organizations_path(filter: 'verified') + expect(page).to_not have_content('Pending Organization') + expect(page).to_not have_content('Rejected Organization') + expect(page).to have_content('Verified Organization') + + visit admin_organizations_path(filter: 'rejected') + expect(page).to_not have_content('Pending Organization') + expect(page).to have_content('Rejected Organization') + expect(page).to_not have_content('Verified Organization') + end + +end diff --git a/spec/features/organizations_spec.rb b/spec/features/organizations_spec.rb new file mode 100644 index 000000000..dcbaaab51 --- /dev/null +++ b/spec/features/organizations_spec.rb @@ -0,0 +1,45 @@ +require 'rails_helper' + +feature 'Organizations' do + + scenario 'Organizations can be created' do + user = User.organizations.where(email: 'green@peace.com').first + expect(user).to_not be + + visit new_organization_registration_path + + fill_in 'user_organization_attributes_name', with: 'Greenpeace' + fill_in 'user_email', with: 'green@peace.com' + fill_in 'user_password', with: 'greenpeace' + fill_in 'user_password_confirmation', with: 'greenpeace' + fill_in 'user_captcha', with: correct_captcha_text + + click_button 'Sign up' + + user = User.organizations.where(email: 'green@peace.com').first + expect(user).to be + expect(user).to be_organization + expect(user.organization).to_not be_verified + end + + scenario 'Errors on create' do + visit new_organization_registration_path + + click_button 'Sign up' + + expect(page).to have_content error_message + end + + scenario 'Shared links' do + visit new_user_registration_path + expect(page).to have_link "Sign up as an organization" + + visit new_organization_registration_path + expect(page).to have_link "Sign up" + + visit new_user_session_path + + expect(page).to have_link "Sign up" + expect(page).to_not have_link "Sign up as an organization" + end +end diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index 5486b286e..20a3456d4 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -46,6 +46,21 @@ describe Ability do end end + describe "Organization" do + let(:user) { create(:user) } + before(:each) { create(:organization, user: user) } + + it { should be_able_to(:show, user) } + it { should be_able_to(:edit, user) } + + it { should be_able_to(:index, Debate) } + it { should be_able_to(:show, debate) } + it { should_not be_able_to(:vote, debate) } + + it { should be_able_to(:create, Comment) } + it { should_not be_able_to(:vote, Comment) } + end + describe "Moderator" do let(:user) { create(:user) } before { create(:moderator, user: user) } @@ -54,6 +69,23 @@ describe Ability do it { should be_able_to(:show, debate) } it { should be_able_to(:vote, debate) } + it { should be_able_to(:read, Organization) } + + describe "organizations" do + let(:pending_organization) { create(:organization) } + let(:rejected_organization) { create(:rejected_organization) } + let(:verified_organization) { create(:verified_organization) } + + it { should be_able_to( :verify, pending_organization) } + it { should be_able_to( :reject, pending_organization) } + + it { should_not be_able_to(:verify, verified_organization) } + it { should be_able_to( :reject, verified_organization) } + + it { should_not be_able_to(:reject, rejected_organization) } + it { should be_able_to( :verify, rejected_organization) } + end + it { should be_able_to(:hide, comment) } it { should be_able_to(:hide, debate) } diff --git a/spec/models/organization_spec.rb b/spec/models/organization_spec.rb new file mode 100644 index 000000000..6fad93d5b --- /dev/null +++ b/spec/models/organization_spec.rb @@ -0,0 +1,46 @@ +require 'rails_helper' + +describe Organization do + + subject { create(:organization) } + + describe "verified?" do + it "is false when verified_at? is blank" do + expect(subject.verified?).to be false + end + it "is true when verified_at? exists" do + subject.verified_at = Time.now + expect(subject.verified?).to be true + end + it "is false when the organization was verified and then rejected" do + subject.verified_at = Time.now + subject.rejected_at = Time.now + 1 + expect(subject.verified?).to be false + end + it "is true when the organization was rejected and then verified" do + subject.rejected_at = Time.now + subject.verified_at = Time.now + 1 + expect(subject.verified?).to be true + end + end + + describe "rejected?" do + it "is false when rejected_at? is blank" do + expect(subject.rejected?).to be false + end + it "is true when rejected_at? exists" do + subject.rejected_at = Time.now + expect(subject.rejected?).to be true + end + it "is true when the organization was verified and then rejected" do + subject.verified_at = Time.now + subject.rejected_at = Time.now + 1 + expect(subject.rejected?).to be true + end + it "is false when the organization was rejected and then verified" do + subject.rejected_at = Time.now + subject.verified_at = Time.now + 1 + expect(subject.rejected?).to be false + end + end +end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 2d8d83562..4752e6f28 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -107,6 +107,42 @@ describe User do end end + describe "organization?" do + it "is false when the user is not an organization" do + expect(subject.organization?).to be false + end + + describe 'when it is an organization' do + before(:each) { create(:organization, user: subject) } + + it "is true when the user is an organization" do + expect(subject.organization?).to be true + end + + it "calculates the name using the organization name" do + expect(subject.name).to eq(subject.organization.name) + end + end + end + + describe "organization_attributes" do + before(:each) { subject.organization_attributes = {name: 'org'} } + + it "triggers the creation of an associated organization" do + expect(subject.organization).to be + expect(subject.organization.name).to eq('org') + end + + it "deactivates the validation of first_name and last_name, and activates the validation of organization" do + subject.first_name = nil + subject.last_name = nil + expect(subject).to be_valid + + subject.organization.name= nil + expect(subject).to_not be_valid + end + end + describe "official?" do it "is false when the user is not an official" do expect(subject.official_level).to eq(0) @@ -172,7 +208,7 @@ describe User do describe "self.with_email" do it "find users by email" do user1 = create(:user, email: "larry@madrid.es") - user2 = create(:user, email: "bird@madrid.es") + create(:user, email: "bird@madrid.es") search = User.with_email("larry@madrid.es") expect(search.size).to eq(1) expect(search.first).to eq(user1)