forked from mastodon/mastodon
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add consumable invites (mastodon#5814)
* Add consumable invites * Add UI for generating invite codes * Add tests * Display max uses and expiration in invites table, delete invite * Remove unused column and redundant validator - Default follows not used, probably bad idea - InviteCodeValidator is redundant because RegistrationsController checks invite code validity * Add admin setting to disable invites * Add admin UI for invites, configurable role for invite creation - Admin UI that lists everyone's invites, always available - Admin setting min_invite_role to control who can invite people - Non-admin invite UI only visible if users are allowed to * Do not remove invites from database, expire them instantly
- Loading branch information
Showing
28 changed files
with
439 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
# frozen_string_literal: true | ||
|
||
module Admin | ||
class InvitesController < BaseController | ||
def index | ||
authorize :invite, :index? | ||
|
||
@invites = Invite.includes(user: :account).page(params[:page]) | ||
@invite = Invite.new | ||
end | ||
|
||
def create | ||
authorize :invite, :create? | ||
|
||
@invite = Invite.new(resource_params) | ||
@invite.user = current_user | ||
|
||
if @invite.save | ||
redirect_to admin_invites_path | ||
else | ||
@invites = Invite.page(params[:page]) | ||
render :index | ||
end | ||
end | ||
|
||
def destroy | ||
@invite = Invite.find(params[:id]) | ||
authorize @invite, :destroy? | ||
@invite.expire! | ||
redirect_to admin_invites_path | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
# frozen_string_literal: true | ||
|
||
class InvitesController < ApplicationController | ||
include Authorization | ||
|
||
layout 'admin' | ||
|
||
before_action :authenticate_user! | ||
|
||
def index | ||
authorize :invite, :create? | ||
|
||
@invites = Invite.where(user: current_user) | ||
@invite = Invite.new(expires_in: 1.day.to_i) | ||
end | ||
|
||
def create | ||
authorize :invite, :create? | ||
|
||
@invite = Invite.new(resource_params) | ||
@invite.user = current_user | ||
|
||
if @invite.save | ||
redirect_to invites_path | ||
else | ||
@invites = Invite.where(user: current_user) | ||
render :index | ||
end | ||
end | ||
|
||
def destroy | ||
@invite = Invite.where(user: current_user).find(params[:id]) | ||
authorize @invite, :destroy? | ||
@invite.expire! | ||
redirect_to invites_path | ||
end | ||
|
||
private | ||
|
||
def resource_params | ||
params.require(:invite).permit(:max_uses, :expires_in) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
# frozen_string_literal: true | ||
# == Schema Information | ||
# | ||
# Table name: invites | ||
# | ||
# id :integer not null, primary key | ||
# user_id :integer | ||
# code :string default(""), not null | ||
# expires_at :datetime | ||
# max_uses :integer | ||
# uses :integer default(0), not null | ||
# created_at :datetime not null | ||
# updated_at :datetime not null | ||
# | ||
|
||
class Invite < ApplicationRecord | ||
belongs_to :user, required: true | ||
has_many :users, inverse_of: :invite | ||
|
||
before_validation :set_code | ||
|
||
attr_reader :expires_in | ||
|
||
def expires_in=(interval) | ||
self.expires_at = interval.to_i.seconds.from_now unless interval.blank? | ||
@expires_in = interval | ||
end | ||
|
||
def valid_for_use? | ||
(max_uses.nil? || uses < max_uses) && (expires_at.nil? || expires_at >= Time.now.utc) | ||
end | ||
|
||
def expire! | ||
touch(:expires_at) | ||
end | ||
|
||
private | ||
|
||
def set_code | ||
loop do | ||
self.code = ([*('a'..'z'), *('A'..'Z'), *('0'..'9')] - %w(0 1 I l O)).sample(8).join | ||
break if Invite.find_by(code: code).nil? | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# frozen_string_literal: true | ||
|
||
class InvitePolicy < ApplicationPolicy | ||
def index? | ||
staff? | ||
end | ||
|
||
def create? | ||
min_required_role? | ||
end | ||
|
||
def destroy? | ||
owner? || staff? | ||
end | ||
|
||
private | ||
|
||
def owner? | ||
record.user_id == current_user&.id | ||
end | ||
|
||
def min_required_role? | ||
current_user&.role?(Setting.min_invite_role) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
%tr | ||
%td | ||
.name-tag | ||
= image_tag invite.user.account.avatar.url(:original), alt: '', width: 16, height: 16, class: 'avatar' | ||
%span.username= invite.user.account.username | ||
%td | ||
= invite.uses | ||
= " / #{invite.max_uses}" unless invite.max_uses.nil? | ||
%td | ||
- if invite.expires_at.nil? | ||
∞ | ||
- else | ||
= l invite.expires_at | ||
%td= table_link_to 'link', public_invite_url(invite_code: invite.code), public_invite_url(invite_code: invite.code) | ||
%td= table_link_to 'times', t('invites.delete'), invite_path(invite), method: :delete if policy(invite).destroy? |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
- content_for :page_title do | ||
= t('admin.invites.title') | ||
|
||
- if policy(:invite).create? | ||
%p= t('invites.prompt') | ||
|
||
= render 'invites/form' | ||
|
||
%hr/ | ||
|
||
%table.table | ||
%thead | ||
%tr | ||
%th | ||
%th= t('invites.table.uses') | ||
%th= t('invites.table.expires_at') | ||
%th | ||
%th | ||
%tbody | ||
= render @invites | ||
|
||
= paginate @invites |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
= simple_form_for(@invite) do |f| | ||
= render 'shared/error_messages', object: @invite | ||
|
||
.fields-group | ||
= f.input :max_uses, wrapper: :with_label, collection: [1, 5, 10, 25, 50, 100], label_method: lambda { |num| I18n.t('invites.max_uses', count: num) }, prompt: I18n.t('invites.max_uses_prompt') | ||
= f.input :expires_in, wrapper: :with_label, collection: [30.minutes, 1.hour, 6.hours, 12.hours, 1.day].map(&:to_i), label_method: lambda { |i| I18n.t("invites.expires_in.#{i}") }, prompt: I18n.t('invites.expires_in_prompt') | ||
|
||
.actions | ||
= f.button :button, t('invites.generate'), type: :submit |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
%tr | ||
%td | ||
= invite.uses | ||
= " / #{invite.max_uses}" unless invite.max_uses.nil? | ||
%td | ||
- if invite.expires_at.nil? | ||
∞ | ||
- else | ||
= l invite.expires_at | ||
%td= table_link_to 'link', public_invite_url(invite_code: invite.code), public_invite_url(invite_code: invite.code) | ||
%td= table_link_to 'times', t('invites.delete'), invite_path(invite), method: :delete if policy(invite).destroy? |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
- content_for :page_title do | ||
= t('invites.title') | ||
|
||
- if policy(:invite).create? | ||
%p= t('invites.prompt') | ||
|
||
= render 'form' | ||
|
||
%hr/ | ||
|
||
%table.table | ||
%thead | ||
%tr | ||
%th= t('invites.table.uses') | ||
%th= t('invites.table.expires_at') | ||
%th | ||
%th | ||
%tbody | ||
= render @invites |
Oops, something went wrong.