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 :owner, class_name: "User"
|
||||||
belongs_to :game_system
|
belongs_to :game_system
|
||||||
has_many :players, dependent: :destroy
|
has_many :players, dependent: :destroy
|
||||||
|
has_many :table_invites, dependent: :destroy
|
||||||
has_many :users, through: :players
|
has_many :users, through: :players
|
||||||
|
|
||||||
validates :name, presence: true,
|
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>
|
<h2><%= @table.name %></h2>
|
||||||
|
|
||||||
|
<%= link_to t(".invite_user"), new_table_table_invite_path(@table) %>
|
||||||
<%= link_to t(".edit_table"), edit_table_path(@table) %>
|
<%= link_to t(".edit_table"), edit_table_path(@table) %>
|
||||||
|
|
||||||
<dl>
|
<dl>
|
||||||
|
|
|
@ -60,6 +60,24 @@ en:
|
||||||
destroy:
|
destroy:
|
||||||
log_out: Log out
|
log_out: Log out
|
||||||
success: "You have signed 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:
|
tables:
|
||||||
index:
|
index:
|
||||||
new_table: Create a table
|
new_table: Create a table
|
||||||
|
@ -68,6 +86,7 @@ en:
|
||||||
edit_table: Edit table
|
edit_table: Edit table
|
||||||
game_system: Game system
|
game_system: Game system
|
||||||
owner: Owner
|
owner: Owner
|
||||||
|
invite_user: Invite a new player
|
||||||
new:
|
new:
|
||||||
new_table: New table
|
new_table: New table
|
||||||
create_table: Create table
|
create_table: Create table
|
||||||
|
|
|
@ -12,7 +12,10 @@ Rails.application.routes.draw do
|
||||||
resources :account_verifications, only: [ :show ]
|
resources :account_verifications, only: [ :show ]
|
||||||
resources :sessions, only: [ :new, :create, :destroy ]
|
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 ]
|
resources :admin, only: [ :index ]
|
||||||
namespace :admin do
|
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.
|
# 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
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
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"
|
t.index ["user_id"], name: "index_site_roles_users_on_user_id"
|
||||||
end
|
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|
|
create_table "tables", force: :cascade do |t|
|
||||||
t.string "name", null: false
|
t.string "name", null: false
|
||||||
t.string "slug", 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", "tables"
|
||||||
add_foreign_key "players", "users"
|
add_foreign_key "players", "users"
|
||||||
|
add_foreign_key "table_invites", "tables"
|
||||||
add_foreign_key "tables", "game_systems"
|
add_foreign_key "tables", "game_systems"
|
||||||
add_foreign_key "tables", "users", column: "owner_id"
|
add_foreign_key "tables", "users", column: "owner_id"
|
||||||
end
|
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
|
owner: trevor
|
||||||
uuid: <%= SecureRandom.uuid %>
|
uuid: <%= SecureRandom.uuid %>
|
||||||
slug: $LABEL
|
slug: $LABEL
|
||||||
|
game_system: troika
|
||||||
|
|
||||||
table:
|
table:
|
||||||
<<: *DEFAULTS
|
<<: *DEFAULTS
|
||||||
name: My Table
|
name: My Table
|
||||||
game_system: troika
|
|
||||||
|
|
||||||
my_table_two:
|
my_table_two:
|
||||||
<<: *DEFAULTS
|
<<: *DEFAULTS
|
||||||
name: My Other Table
|
name: My Other Table
|
||||||
game_system: troika
|
|
||||||
|
gimlis_table:
|
||||||
|
<<: *DEFAULTS
|
||||||
|
name: Gimli's Table
|
||||||
|
owner: gimli
|
||||||
|
|
|
@ -15,6 +15,16 @@ gimli:
|
||||||
first_name: Gimli
|
first_name: Gimli
|
||||||
last_name: son of Glóin
|
last_name: son of Glóin
|
||||||
|
|
||||||
|
frodo:
|
||||||
|
<<: *DEFAULTS
|
||||||
|
first_name: Frodo
|
||||||
|
last_name: Baggins
|
||||||
|
|
||||||
|
uninvited_user:
|
||||||
|
<<: *DEFAULTS
|
||||||
|
first_name: Fitzchivalry
|
||||||
|
last_name: Farseer
|
||||||
|
|
||||||
unverified:
|
unverified:
|
||||||
<<: *DEFAULTS
|
<<: *DEFAULTS
|
||||||
first_name: Unverified
|
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