diff --git a/app/controllers/invitadxs_controller.rb b/app/controllers/invitadxs_controller.rb deleted file mode 100644 index b6dc81b..0000000 --- a/app/controllers/invitadxs_controller.rb +++ /dev/null @@ -1,65 +0,0 @@ -# frozen_string_literal: true - -# Controlador de Invitadxs -# -# TODO: reemplazar -class InvitadxsController < ApplicationController - include Pundit - - def index - authenticate_usuarie! - - @site = find_site - @invitadxs = @site.invitadxs - end - - def new - @site = Site.find(params[:site_id]) - @has_cover = true - @invitadx = Invitadx.new - end - - def create - @site = Site.find(params[:invitadx][:site]) - @invitadx = Invitadx.new(invitadx_params) - @invitadx.confirmation_token = SecureRandom.hex(16) - @invitadx.sites << @site - - if @invitadx.save - InvitadxMailer.with(site: @site, invitadx: @invitadx) - .confirmation_required.deliver - redirect_to site_invitadx_path(@site, @invitadx) - else - render 'new' - end - end - - def show - @site = Site.find(params[:site_id]) - @has_cover = true - @invitadx = Invitadx.find(params[:id]) - end - - def confirmation - @invitadx = Invitadx.find(params[:invitadx_id]) - @site = @invitadx.sites.find do |site| - site.id == params[:site_id] - end - - if (@invitadx.confirmation_token = params[:confirmation_token]) - @invitadx.update_attribute :confirmed, true - flash[:info] = t('.confirmed') - redirect_to site_invitadxs_login_new_path(@site) - else - redirect_to root_path - end - end - - private - - def invitadx_params - params.require(:invitadx).permit(:email, :password, - :password_confirmation, - :acepta_politicas_de_privacidad) - end -end diff --git a/app/controllers/usuaries_controller.rb b/app/controllers/usuaries_controller.rb index 3c85579..7ecf727 100644 --- a/app/controllers/usuaries_controller.rb +++ b/app/controllers/usuaries_controller.rb @@ -67,10 +67,13 @@ class UsuariesController < ApplicationController authorize SiteUsuarie.new(@site, current_usuarie) # Enviar la invitación si es necesario y agregar al sitio - # - # TODO: Pedir consentimiento para agregar a un sitio! invitaciones.each do |invitacion| - # Si la cuenta no existe, envía una invitación por correo + # Si la cuenta no existe, envía una invitación por correo, sino, + # no se envía nada + # + # TODO: Enviar invitación igual! Podemos no usar el Mailer de + # DeviseInvitations y usar uno propio que contenga texto y se + # envíe de todas formas. usuarie = Usuarie.invite! email: invitacion.address # No invitar al sitio si ya estaba en la lista! @@ -83,6 +86,26 @@ class UsuariesController < ApplicationController redirect_to site_usuaries_path(@site) end + # Aceptar la invitación + def accept_invitation + @site = find_site + authorize SiteUsuarie.new(@site, current_usuarie) + + current_usuarie.rol_for_site(@site).update_attribute :temporal, false + + redirect_to sites_path + end + + # Declinar la invitación + def reject_invitation + @site = find_site + authorize SiteUsuarie.new(@site, current_usuarie) + + current_usuarie.rol_for_site(@site).destroy + + redirect_to sites_path + end + private # Traer todas las invitaciones que al menos tengan usuarie y dominio diff --git a/app/models/rol.rb b/app/models/rol.rb index 0942a22..4e4dd6f 100644 --- a/app/models/rol.rb +++ b/app/models/rol.rb @@ -11,4 +11,12 @@ class Rol < ApplicationRecord belongs_to :site validates_inclusion_of :rol, in: ROLES + + def invitade? + rol == 'invitade' + end + + def usuarie? + rol == 'usuarie' + end end diff --git a/app/models/usuarie.rb b/app/models/usuarie.rb index 827cf5a..4467a4b 100644 --- a/app/models/usuarie.rb +++ b/app/models/usuarie.rb @@ -10,4 +10,8 @@ class Usuarie < ApplicationRecord has_many :roles has_many :sites, through: :roles + + def rol_for_site(site) + site.roles.merge(roles).first + end end diff --git a/app/policies/site_usuarie_policy.rb b/app/policies/site_usuarie_policy.rb index ded4540..d33e4c8 100644 --- a/app/policies/site_usuarie_policy.rb +++ b/app/policies/site_usuarie_policy.rb @@ -35,9 +35,22 @@ class SiteUsuariePolicy usuarie? end + def accept_invitation? + su = site_usuarie + (usuarie? || invitade?) && su.usuarie.rol_for_site(su.site).temporal + end + + def reject_invitation? + accept_invitation? + end + private def usuarie? site_usuarie.site.usuarie? usuarie end + + def invitade? + site_usuarie.site.invitade? usuarie + end end diff --git a/app/views/invitadxs/index.haml b/app/views/invitadxs/index.haml deleted file mode 100644 index 3fc782c..0000000 --- a/app/views/invitadxs/index.haml +++ /dev/null @@ -1,13 +0,0 @@ -.row - .col - = render 'layouts/breadcrumb', crumbs: [ t('sites.index'), t('.title') ] -.row - .col - %h1= t('.title') - - %table.table.table-striped.table-condensed - %tbody - - @invitadxs.each do |invitadx| - %tr - %td= invitadx.email - %td= invitadx.created_at diff --git a/app/views/layouts/_btn_with_tooltip.haml b/app/views/layouts/_btn_with_tooltip.haml index a90a9d3..897480b 100644 --- a/app/views/layouts/_btn_with_tooltip.haml +++ b/app/views/layouts/_btn_with_tooltip.haml @@ -1,2 +1,2 @@ = link_to text, link, class: "btn btn-#{type || secondary}", - data: { toggle: 'tooltip' }, title: tooltip + data: { toggle: 'tooltip' }, 'aria-role': 'button', title: tooltip diff --git a/app/views/sites/index.haml b/app/views/sites/index.haml index fe67360..150591d 100644 --- a/app/views/sites/index.haml +++ b/app/views/sites/index.haml @@ -21,46 +21,60 @@ = t('.invitade') %br .btn-group{role: 'group', 'aria-label': t('sites.actions')} - - if policy(site).show? - = render 'layouts/btn_with_tooltip', - tooltip: t('help.sites.edit_posts'), - type: 'success', - link: site_path(site), - text: t('sites.posts') - - if policy(SiteTranslation.new(site)).edit? - = render 'layouts/btn_with_tooltip', - tooltip: t('help.sites.edit_translations'), - text: t('i18n.edit'), - type: 'info', - link: site_i18n_edit_path(site) - - if policy(SiteUsuarie.new(site, current_usuarie)).index? - = render 'layouts/btn_with_tooltip', - tooltip: t('usuaries.index.help.self'), - text: t('usuaries.index.title'), - type: 'info', - link: site_usuaries_path(site) - - if policy(site).build? - - if site.enqueued? + - if current_usuarie.rol_for_site(site).temporal + = button_to t('sites.invitations.accept'), + site_usuaries_accept_invitation_path(site), + data: { toggle: 'tooltip' }, + title: t('help.sites.invitations.accept'), + method: :patch, + class: 'btn btn-success' + = button_to t('sites.invitations.reject'), + site_usuaries_reject_invitation_path(site), + data: { toggle: 'tooltip' }, + title: t('help.sites.invitations.reject'), + method: :patch, + class: 'btn btn-info' + - else + - if policy(site).show? = render 'layouts/btn_with_tooltip', - tooltip: t('help.sites.enqueued'), - text: t('sites.enqueued'), - type: 'secondary', - link: nil - - else - = form_tag site_enqueue_path(site), method: :post, class: 'form-inline' do - = button_tag type: 'submit', - class: 'btn btn-success', - title: t('help.sites.enqueue'), - data: { toggle: 'tooltip' } do - = fa_icon 'building' - = t('sites.enqueue') + tooltip: t('help.sites.edit_posts'), + type: 'success', + link: site_path(site), + text: t('sites.posts') + - if policy(SiteTranslation.new(site)).edit? + = render 'layouts/btn_with_tooltip', + tooltip: t('help.sites.edit_translations'), + text: t('i18n.edit'), + type: 'info', + link: site_i18n_edit_path(site) + - if policy(SiteUsuarie.new(site, current_usuarie)).index? + = render 'layouts/btn_with_tooltip', + tooltip: t('usuaries.index.help.self'), + text: t('usuaries.index.title'), + type: 'info', + link: site_usuaries_path(site) + - if policy(site).build? + - if site.enqueued? + = render 'layouts/btn_with_tooltip', + tooltip: t('help.sites.enqueued'), + text: t('sites.enqueued'), + type: 'secondary', + link: nil + - else + = form_tag site_enqueue_path(site), method: :post, class: 'form-inline' do + = button_tag type: 'submit', + class: 'btn btn-success', + title: t('help.sites.enqueue'), + data: { toggle: 'tooltip' } do + = fa_icon 'building' + = t('sites.enqueue') - - if policy(site).build_log? - - if site.failed? - %button.btn.btn-danger= t('sites.failed') - - if site.build_log? - = render 'layouts/btn_with_tooltip', - tooltip: t('help.sites.build_log'), - text: t('sites.build_log'), - type: 'warning', - link: site_build_log_path(site) + - if policy(site).build_log? + - if site.failed? + %button.btn.btn-danger= t('sites.failed') + - if site.build_log? + = render 'layouts/btn_with_tooltip', + tooltip: t('help.sites.build_log'), + text: t('sites.build_log'), + type: 'warning', + link: site_build_log_path(site) diff --git a/config/locales/en.yml b/config/locales/en.yml index 3675665..4011ecd 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -90,6 +90,9 @@ en: build_log: "This is the log for what happened during site generation. If there was an issue, you'll see it here." invitade: "Invited users can only add and modify entries but can't publish until reviewed by a user" + invitations: + accept: 'Someone invited you to collaborate on their site. If you accept the invitation, you can access the site.' + reject: 'If you decline, nothing happens.' close: 'Close help' markdown: intro: 'The text is formatted using a syntax called Markdown, a @@ -146,6 +149,9 @@ en: enqueue: 'Build' failed: 'Failed!' build_log: 'Read log' + invitations: + accept: 'Accept invitation' + reject: 'No, thanks' footer: powered_by: 'is developed by' templates: diff --git a/config/locales/es.yml b/config/locales/es.yml index 8900479..ee2a406 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -100,6 +100,9 @@ es: build_log: 'Este es el registro de lo que sucedió mientras se generaba el sitio. Si hubo algún problema, saldrá aquí.' invitade: 'Les invitades a un sitio solo pueden crear y modificar entradas propias y no pueden publicar sin la revisión de une usuarie' + invitations: + accept: 'Alguien te invitó a colaborar en su sitio. Si aceptas la invitación, tendrás acceso a este sitio.' + reject: 'Si rechazas la invitación, no pasa nada.' close: 'Cerrar ayuda' markdown: intro: 'El formato del texto se llama Markdown. Es un formato @@ -155,6 +158,9 @@ es: enqueue: 'Compilar' failed: '¡Falló!' build_log: 'Ver registro' + invitations: + accept: 'Aceptar la invitación' + reject: 'No, gracias' footer: powered_by: 'es desarrollada por' i18n: diff --git a/config/routes.rb b/config/routes.rb index d7d5cd5..664a4bb 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -19,6 +19,8 @@ Rails.application.routes.draw do # Gestionar usuaries get 'usuaries/invite', to: 'usuaries#invite' post 'usuaries/invite', to: 'usuaries#send_invitations' + patch 'usuaries/accept_invitation', to: 'usuaries#accept_invitation' + patch 'usuaries/reject_invitation', to: 'usuaries#reject_invitation' resources :usuaries do patch 'demote', to: 'usuaries#demote' patch 'promote', to: 'usuaries#promote' diff --git a/doc/autenticacion.md b/doc/autenticacion.md index de3d9c0..e7c64ba 100644 --- a/doc/autenticacion.md +++ b/doc/autenticacion.md @@ -12,7 +12,7 @@ artículos y modificar los propios). No nos gusta la idea de implementar todo un sistema de privilegios, primero porque queremos que Sutty sea una plataforma democrática y segundo porque en nuestra experiencia nadie los usa y prefieren usar una -cuenta de administración. +cuenta de administración para todo. La migración a Devise nos va a permitir tener recuperación de contraseñas, registro independiente, correos de bienvenida y varias @@ -27,21 +27,17 @@ integrarla con otras plataformas comunitarias Los Sitios tienen una contraparte física, de archivos, pero también se crean en la base de datos para establecer relaciones con Usuaries. -Une Usuarie puede tener muchos Sitios y viceversa. En Rails esto se -llama "tiene y pertenece a muchos" (HABTM en inglés). +Une Usuarie puede tener muchos Sitios y viceversa. -Sin embargo también queremos saber qué rol tienen, con lo que -necesitamos dos tablas de HABTM, una de Invitades y otra de Usuaries. -De lo contrario necesitamos establecer roles y ya entramos en las -dificultades que decíamos más arriba. - -No podemos saber desde cuándo se creo la relación, a menos que tengamos -una tabla de actividades por separado. +También queremos saber qué rol tienen, con lo que tenemos una tabla de +roles que establece el rol que une usuarie tiene en un sitio. De lo +contrario necesitamos establecer roles y ya entramos en las dificultades +que decíamos más arriba. Podemos saber quién es invitade ingresando a un sitio y fijándonos si está en su lista de invitade. Lo mismo para usuaries. -Les usuaries pueden bloquear invitades y a otres usuaries, y sumar +Les usuaries pueden bloquear invitades y a otres usuaries y sumar usuaries e invitades a su sitio (via correo de invitación). ## Invitaciones a sitios @@ -56,15 +52,19 @@ participación. Si no tienen cuenta, tienen que registrarse completando los datos en el momento, sino se pueden loguear normalmente. -Si ya están logueades, se acepta la invitación inmediatamente. +Para poder hacer una invitación con consentimiento, se guarda el rol +como temporal. Cuando la usuaria acepta la invitación el rol se vuelve +definitivo. -Para poder hacer una invitación con consentimiento, se guarda la -relación en una tabla aparte. Cuando la usuaria acepta la invitación -esa relación se borra y se aplican los cambios. +### Aceptar invitación -En el futuro cambiaríamos el sistema de permisos separados que tenemos -ahora por una tabla de roles (por qué no la hicimos desde el principio!) -con un campo binario que indique si ya se aceptó la invitación o no. +Al ingresar, le usuarie ve su listado de sitios y en la lista ve a +cuales está invitade. Usa el botón de aceptar invitación para poder +acceder al sitio. Si rechaza la invitación, el rol se elimina de la +base de datos. + +Eventualmente queremos pasar a un modelo de estados del rol donde +podamos saber si fue rechazado, aceptado, etc. ### Invitades desde la web