Add table invites
This commit is contained in:
parent
e1c74ac2f5
commit
18c264b64b
|
@ -0,0 +1,55 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class TableInvitesController < ApplicationController
|
||||
before_action :set_table, only: [ :new, :create ]
|
||||
before_action :set_table_invite, only: [ :edit, :update ]
|
||||
|
||||
def new
|
||||
@table_invite = @table.table_invites.new
|
||||
end
|
||||
|
||||
def create
|
||||
@user = User.find_by(email: table_invite_params[:email])
|
||||
if @user.blank?
|
||||
# TODO: Allow inviting non-users, we can send an email invite
|
||||
flash[:alert] = t(".user_not_found")
|
||||
render :new, status: :unprocessable_entity and return
|
||||
end
|
||||
|
||||
@table_invite = @table.table_invites.new(table_invite_params)
|
||||
if @table_invite.save
|
||||
redirect_to @table, notice: t(".success", email: @table_invite.email)
|
||||
else
|
||||
render :new, status: :unprocessable_entity, alert: t(".error")
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
redirect_to @table_invite.table if @table_invite.responded?
|
||||
end
|
||||
|
||||
def update
|
||||
if params[:accept] == "true"
|
||||
@table_invite.accept!
|
||||
redirect_to @table_invite.table, notice: t(".accept_success")
|
||||
elsif params[:decline] == "true"
|
||||
@table_invite.decline!
|
||||
redirect_to :root, notice: t(".decline_success")
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_table
|
||||
@table = Current.user.owned_tables.find(params[:table_id])
|
||||
end
|
||||
|
||||
def set_table_invite
|
||||
@table_invite = TableInvite.where(email: Current.user.email)
|
||||
.find(params[:id])
|
||||
end
|
||||
|
||||
def table_invite_params
|
||||
params.require(:table_invite).permit(:email)
|
||||
end
|
||||
end
|
|
@ -4,6 +4,7 @@ class Table < ApplicationRecord
|
|||
belongs_to :owner, class_name: "User"
|
||||
belongs_to :game_system
|
||||
has_many :players, dependent: :destroy
|
||||
has_many :table_invites, dependent: :destroy
|
||||
has_many :users, through: :players
|
||||
|
||||
validates :name, presence: true,
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class TableInvite < ApplicationRecord
|
||||
belongs_to :table
|
||||
|
||||
validates :email, format: { with: URI::MailTo::EMAIL_REGEXP },
|
||||
presence: true,
|
||||
length: { maximum: 100 }
|
||||
|
||||
def accepted?
|
||||
accepted_at.present?
|
||||
end
|
||||
|
||||
def declined?
|
||||
declined_at.present?
|
||||
end
|
||||
|
||||
def responded?
|
||||
accepted? || declined?
|
||||
end
|
||||
|
||||
def accept!
|
||||
raise "Already declined" if declined?
|
||||
|
||||
user = User.find_by(email:)
|
||||
user.tables << table
|
||||
user.save
|
||||
update(accepted_at: Time.zone.now)
|
||||
end
|
||||
|
||||
def decline!
|
||||
raise "Already accepted" if accepted?
|
||||
|
||||
update(declined_at: Time.zone.now)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,10 @@
|
|||
<% content_for :title, t(".respond_to_invite") %>
|
||||
|
||||
<h2><%= t(".respond_to_invite") %></h2>
|
||||
|
||||
<p><%= t(".accept_invite_description", table_name: @table_invite.table.name) %></p>
|
||||
|
||||
<%= link_to t(".accept_invite"), table_invite_path(@table_invite, accept: true), data: {
|
||||
turbo_method: :patch } %>
|
||||
<%= link_to t(".decline_invite"), table_invite_path(@table_invite, decline: true), data: {
|
||||
turbo_method: :patch } %>
|
|
@ -0,0 +1,11 @@
|
|||
<% content_for :title, t(".new_table_invite", name: @table.name) %>
|
||||
|
||||
<h2><%= t(".new_table_invite", name: @table.name) %></h2>
|
||||
|
||||
<p><%= t(".new_invite_description") %></p>
|
||||
<%= form_with url: table_table_invites_path do |f| %>
|
||||
<%= f.label :email %>
|
||||
<%= f.email_field :email %>
|
||||
|
||||
<%= f.submit t(".button_text") %>
|
||||
<% end %>
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
<h2><%= @table.name %></h2>
|
||||
|
||||
<%= link_to t(".invite_user"), new_table_table_invite_path(@table) %>
|
||||
<%= link_to t(".edit_table"), edit_table_path(@table) %>
|
||||
|
||||
<dl>
|
||||
|
|
|
@ -60,6 +60,24 @@ en:
|
|||
destroy:
|
||||
log_out: Log out
|
||||
success: "You have signed out."
|
||||
table_invites:
|
||||
new:
|
||||
new_table_invite: Invite a player to %{name}
|
||||
new_invite_description: Enter an email address to invite a player to your table.
|
||||
create_table_invite: Send invite
|
||||
button_text: Send invite
|
||||
create:
|
||||
user_not_found: There is no registered user with that email address. Ask them to sign up!
|
||||
success: Send invite to %{email}
|
||||
error: Failed to send invite
|
||||
edit:
|
||||
respond_to_invite: Respond to your invitation
|
||||
accept_invite_description: You have been invited to join the table %{table_name}. To start playing, hit accept!
|
||||
accept_invite: Accept
|
||||
decline_invite: Decline
|
||||
update:
|
||||
accept_success: You accepted your invite
|
||||
decline_success: You declined your invite
|
||||
tables:
|
||||
index:
|
||||
new_table: Create a table
|
||||
|
@ -68,6 +86,7 @@ en:
|
|||
edit_table: Edit table
|
||||
game_system: Game system
|
||||
owner: Owner
|
||||
invite_user: Invite a new player
|
||||
new:
|
||||
new_table: New table
|
||||
create_table: Create table
|
||||
|
|
|
@ -12,7 +12,10 @@ Rails.application.routes.draw do
|
|||
resources :account_verifications, only: [ :show ]
|
||||
resources :sessions, only: [ :new, :create, :destroy ]
|
||||
|
||||
resources :tables
|
||||
resources :table_invites, only: [ :edit, :update ]
|
||||
resources :tables do
|
||||
resources :table_invites, only: [ :new, :create ]
|
||||
end
|
||||
|
||||
resources :admin, only: [ :index ]
|
||||
namespace :admin do
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateTableInvites < ActiveRecord::Migration[7.1]
|
||||
def change
|
||||
create_table :table_invites do |t|
|
||||
t.belongs_to :table, null: false, foreign_key: true
|
||||
t.string :email, null: false
|
||||
t.datetime :accepted_at
|
||||
t.datetime :declined_at
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
add_check_constraint :table_invites, "length(email) >= 5", name: "chk_email_min_length"
|
||||
add_check_constraint :table_invites, "length(email) <= 100", name: "chk_email_max_length"
|
||||
end
|
||||
end
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.1].define(version: 2024_05_29_074949) do
|
||||
ActiveRecord::Schema[7.1].define(version: 2024_05_29_122012) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
||||
|
@ -44,6 +44,18 @@ ActiveRecord::Schema[7.1].define(version: 2024_05_29_074949) do
|
|||
t.index ["user_id"], name: "index_site_roles_users_on_user_id"
|
||||
end
|
||||
|
||||
create_table "table_invites", force: :cascade do |t|
|
||||
t.bigint "table_id", null: false
|
||||
t.string "email", null: false
|
||||
t.datetime "accepted_at"
|
||||
t.datetime "declined_at"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["table_id"], name: "index_table_invites_on_table_id"
|
||||
t.check_constraint "length(email::text) <= 100", name: "chk_email_max_length"
|
||||
t.check_constraint "length(email::text) >= 5", name: "chk_email_min_length"
|
||||
end
|
||||
|
||||
create_table "tables", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.string "slug", null: false
|
||||
|
@ -78,6 +90,7 @@ ActiveRecord::Schema[7.1].define(version: 2024_05_29_074949) do
|
|||
|
||||
add_foreign_key "players", "tables"
|
||||
add_foreign_key "players", "users"
|
||||
add_foreign_key "table_invites", "tables"
|
||||
add_foreign_key "tables", "game_systems"
|
||||
add_foreign_key "tables", "users", column: "owner_id"
|
||||
end
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "test_helper"
|
||||
|
||||
class TableInviteInvitesControllerTest < ActionDispatch::IntegrationTest
|
||||
test "only a table owner can invite players" do
|
||||
sign_in users(:trevor)
|
||||
get new_table_table_invite_url(tables(:gimlis_table))
|
||||
assert_response :not_found
|
||||
end
|
||||
|
||||
test "should get new" do
|
||||
sign_in users(:trevor)
|
||||
get new_table_table_invite_url(tables(:table))
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
test "should get edit" do
|
||||
sign_in users(:frodo)
|
||||
get edit_table_invite_url(table_invites(:unaccepted))
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
test "should create table_invite" do
|
||||
sign_in users(:trevor)
|
||||
assert_changes("TableInvite.count", +1) do
|
||||
post(table_table_invites_url(tables(:table)), params: { table_invite: { email: "uninvited_user@example.com" } })
|
||||
end
|
||||
assert_redirected_to table_path(tables(:table))
|
||||
end
|
||||
|
||||
test "should accept table_invite" do
|
||||
sign_in users(:frodo)
|
||||
invite = table_invites(:unaccepted)
|
||||
assert_nil invite.accepted_at
|
||||
patch table_invite_url(invite), params: { accept: true }
|
||||
assert_redirected_to table_path(invite.table)
|
||||
invite.reload
|
||||
assert_not_nil invite.accepted_at
|
||||
end
|
||||
|
||||
test "should decline table_invite" do
|
||||
sign_in users(:frodo)
|
||||
invite = table_invites(:unaccepted)
|
||||
assert_nil invite.accepted_at
|
||||
patch table_invite_url(invite), params: { decline: true }
|
||||
assert_redirected_to root_path
|
||||
invite.reload
|
||||
assert_not_nil invite.declined_at
|
||||
end
|
||||
end
|
|
@ -0,0 +1,20 @@
|
|||
one:
|
||||
table: table
|
||||
email: trevor@example.com
|
||||
accepted_at: 2024-05-29 13:20:12
|
||||
|
||||
two:
|
||||
table: table
|
||||
email: gimli@example.com
|
||||
accepted_at: 2024-05-29 13:20:12
|
||||
|
||||
unaccepted:
|
||||
table: table
|
||||
email: frodo@example.com
|
||||
accepted_at: nil
|
||||
|
||||
declined:
|
||||
table: table
|
||||
email: admin@example.com
|
||||
accepted_at: nil
|
||||
declined_at: 2024-05-29 13:20:12
|
|
@ -2,13 +2,17 @@ DEFAULTS: &DEFAULTS
|
|||
owner: trevor
|
||||
uuid: <%= SecureRandom.uuid %>
|
||||
slug: $LABEL
|
||||
game_system: troika
|
||||
|
||||
table:
|
||||
<<: *DEFAULTS
|
||||
name: My Table
|
||||
game_system: troika
|
||||
|
||||
my_table_two:
|
||||
<<: *DEFAULTS
|
||||
name: My Other Table
|
||||
game_system: troika
|
||||
|
||||
gimlis_table:
|
||||
<<: *DEFAULTS
|
||||
name: Gimli's Table
|
||||
owner: gimli
|
||||
|
|
|
@ -15,6 +15,16 @@ gimli:
|
|||
first_name: Gimli
|
||||
last_name: son of Glóin
|
||||
|
||||
frodo:
|
||||
<<: *DEFAULTS
|
||||
first_name: Frodo
|
||||
last_name: Baggins
|
||||
|
||||
uninvited_user:
|
||||
<<: *DEFAULTS
|
||||
first_name: Fitzchivalry
|
||||
last_name: Farseer
|
||||
|
||||
unverified:
|
||||
<<: *DEFAULTS
|
||||
first_name: Unverified
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "test_helper"
|
||||
|
||||
class TableInviteTest < ActiveSupport::TestCase
|
||||
test "accept! accepts the invite" do
|
||||
invite = table_invites(:unaccepted)
|
||||
user = User.find_by(email: invite.email)
|
||||
assert_not_includes user.tables, invite.table
|
||||
assert_nil invite.accepted_at
|
||||
invite.accept!
|
||||
assert_not_nil invite.accepted_at
|
||||
assert_includes user.tables, invite.table
|
||||
end
|
||||
|
||||
test "decline! declines the invite" do
|
||||
invite = table_invites(:unaccepted)
|
||||
assert_nil invite.declined_at
|
||||
invite.decline!
|
||||
assert_not_nil invite.declined_at
|
||||
end
|
||||
|
||||
test "cannot accept a declined invitation" do
|
||||
assert_raises do
|
||||
table_invites(:declined).accept!
|
||||
end
|
||||
end
|
||||
|
||||
test "cannot decline an accepted invitation" do
|
||||
assert_raises do
|
||||
table_invites(:one).decline!
|
||||
end
|
||||
end
|
||||
|
||||
test "responded? is true for both accepted and declined invites" do
|
||||
assert table_invites(:one).responded?
|
||||
assert table_invites(:declined).responded?
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue