Compare commits

...

10 Commits

Author SHA1 Message Date
Trevor Vallender 93359a5b59 Add CRUD for game systems 2024-05-26 11:42:48 +01:00
Trevor Vallender 716176a1b8 Add admin controller 2024-05-26 10:34:09 +01:00
Trevor Vallender 72bee55d7e Add model for admins 2024-05-26 10:06:04 +01:00
Trevor Vallender ed01262d88 Add GameSystem model 2024-05-26 09:51:31 +01:00
Trevor Vallender f70a676456 Styling improvements 2024-05-26 09:51:17 +01:00
Trevor Vallender ce241b1674 Sprockets to Propshaft migration 2024-05-26 09:38:17 +01:00
Trevor Vallender 5a930074f6 Rename todos to tables 2024-05-26 09:36:07 +01:00
Trevor Vallender c22a5ffac2 Add git hooks 2024-05-26 09:11:31 +01:00
Trevor Vallender e3f53839d5 Use existing codebase for Tabletop Companion 2024-05-26 09:11:14 +01:00
Trevor Vallender df777e9927 Gem updates 2024-05-26 09:03:33 +01:00
45 changed files with 575 additions and 152 deletions

4
.git-hooks/install.bash Executable file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env bash
ln .git-hooks/pre-commit .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit

24
.git-hooks/pre-commit Executable file
View File

@ -0,0 +1,24 @@
#!/usr/bin/env bash
GREEN='\033[0;32m'
AMBER='\033[0;33m'
RED='\033[0;31m'
CLEAR_COLOR='\033[0m'
function run_command {
local COMMAND=$1
local COMMAND_NAME=$2
echo -e "${AMBER}Running $COMMAND_NAME…${AMBER}"
$COMMAND &>/dev/null
if [[ $? -ne 0 ]] ; then
echo -e "${RED}❌ $COMMAND_NAME failed${CLEAR_COLOR}"
exit 1
else
echo -e "${GREEN}✓ $COMMAND_NAME passed${CLEAR_COLOR}"
fi
}
run_command "brakeman --format html -o ../tmp/brakeman.html" "Brakeman"
run_command "bundle exec rubocop" "Rubocop"
run_command "bundle exec rails test" "test suite"

View File

@ -3,7 +3,7 @@ source "https://rubygems.org"
ruby file: "./.ruby-version" ruby file: "./.ruby-version"
gem "rails", "~> 7.1.3", ">= 7.1.3.2" gem "rails", "~> 7.1.3", ">= 7.1.3.2"
gem "sprockets-rails" gem "propshaft"
gem "pg" gem "pg"
gem "puma", ">= 5.0" gem "puma", ">= 5.0"
gem "importmap-rails" gem "importmap-rails"

View File

@ -1,35 +1,35 @@
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
actioncable (7.1.3.2) actioncable (7.1.3.3)
actionpack (= 7.1.3.2) actionpack (= 7.1.3.3)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.3)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6) zeitwerk (~> 2.6)
actionmailbox (7.1.3.2) actionmailbox (7.1.3.3)
actionpack (= 7.1.3.2) actionpack (= 7.1.3.3)
activejob (= 7.1.3.2) activejob (= 7.1.3.3)
activerecord (= 7.1.3.2) activerecord (= 7.1.3.3)
activestorage (= 7.1.3.2) activestorage (= 7.1.3.3)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.3)
mail (>= 2.7.1) mail (>= 2.7.1)
net-imap net-imap
net-pop net-pop
net-smtp net-smtp
actionmailer (7.1.3.2) actionmailer (7.1.3.3)
actionpack (= 7.1.3.2) actionpack (= 7.1.3.3)
actionview (= 7.1.3.2) actionview (= 7.1.3.3)
activejob (= 7.1.3.2) activejob (= 7.1.3.3)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.3)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
net-imap net-imap
net-pop net-pop
net-smtp net-smtp
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
actionpack (7.1.3.2) actionpack (7.1.3.3)
actionview (= 7.1.3.2) actionview (= 7.1.3.3)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.3)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
racc racc
rack (>= 2.2.4) rack (>= 2.2.4)
@ -37,35 +37,35 @@ GEM
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6) rails-html-sanitizer (~> 1.6)
actiontext (7.1.3.2) actiontext (7.1.3.3)
actionpack (= 7.1.3.2) actionpack (= 7.1.3.3)
activerecord (= 7.1.3.2) activerecord (= 7.1.3.3)
activestorage (= 7.1.3.2) activestorage (= 7.1.3.3)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.3)
globalid (>= 0.6.0) globalid (>= 0.6.0)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (7.1.3.2) actionview (7.1.3.3)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.3)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.11) erubi (~> 1.11)
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6) rails-html-sanitizer (~> 1.6)
activejob (7.1.3.2) activejob (7.1.3.3)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.3)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (7.1.3.2) activemodel (7.1.3.3)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.3)
activerecord (7.1.3.2) activerecord (7.1.3.3)
activemodel (= 7.1.3.2) activemodel (= 7.1.3.3)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.3)
timeout (>= 0.4.0) timeout (>= 0.4.0)
activestorage (7.1.3.2) activestorage (7.1.3.3)
actionpack (= 7.1.3.2) actionpack (= 7.1.3.3)
activejob (= 7.1.3.2) activejob (= 7.1.3.3)
activerecord (= 7.1.3.2) activerecord (= 7.1.3.3)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.3)
marcel (~> 1.0) marcel (~> 1.0)
activesupport (7.1.3.2) activesupport (7.1.3.3)
base64 base64
bigdecimal bigdecimal
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
@ -80,7 +80,7 @@ GEM
ast (2.4.2) ast (2.4.2)
base64 (0.2.0) base64 (0.2.0)
bcrypt (3.1.20) bcrypt (3.1.20)
bigdecimal (3.1.7) bigdecimal (3.1.8)
bindex (0.8.1) bindex (0.8.1)
bootsnap (1.18.3) bootsnap (1.18.3)
msgpack (~> 1.2) msgpack (~> 1.2)
@ -105,17 +105,17 @@ GEM
erubi (1.12.0) erubi (1.12.0)
globalid (1.2.1) globalid (1.2.1)
activesupport (>= 6.1) activesupport (>= 6.1)
i18n (1.14.4) i18n (1.14.5)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
importmap-rails (2.0.1) importmap-rails (2.0.1)
actionpack (>= 6.0.0) actionpack (>= 6.0.0)
activesupport (>= 6.0.0) activesupport (>= 6.0.0)
railties (>= 6.0.0) railties (>= 6.0.0)
io-console (0.7.2) io-console (0.7.2)
irb (1.12.0) irb (1.13.1)
rdoc rdoc (>= 4.0.0)
reline (>= 0.4.2) reline (>= 0.4.2)
jbuilder (2.11.5) jbuilder (2.12.0)
actionview (>= 5.0.0) actionview (>= 5.0.0)
activesupport (>= 5.0.0) activesupport (>= 5.0.0)
json (2.7.2) json (2.7.2)
@ -131,10 +131,10 @@ GEM
marcel (1.0.4) marcel (1.0.4)
matrix (0.4.2) matrix (0.4.2)
mini_mime (1.1.5) mini_mime (1.1.5)
minitest (5.22.3) minitest (5.23.1)
msgpack (1.7.2) msgpack (1.7.2)
mutex_m (0.2.0) mutex_m (0.2.0)
net-imap (0.4.10) net-imap (0.4.11)
date date
net-protocol net-protocol
net-pop (0.1.2) net-pop (0.1.2)
@ -143,31 +143,36 @@ GEM
timeout timeout
net-smtp (0.5.0) net-smtp (0.5.0)
net-protocol net-protocol
nio4r (2.7.1) nio4r (2.7.3)
nokogiri (1.16.4-aarch64-linux) nokogiri (1.16.5-aarch64-linux)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.16.4-arm-linux) nokogiri (1.16.5-arm-linux)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.16.4-arm64-darwin) nokogiri (1.16.5-arm64-darwin)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.16.4-x86-linux) nokogiri (1.16.5-x86-linux)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.16.4-x86_64-darwin) nokogiri (1.16.5-x86_64-darwin)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.16.4-x86_64-linux) nokogiri (1.16.5-x86_64-linux)
racc (~> 1.4) racc (~> 1.4)
parallel (1.24.0) parallel (1.24.0)
parser (3.3.0.5) parser (3.3.1.0)
ast (~> 2.4.1) ast (~> 2.4.1)
racc racc
pg (1.5.6) pg (1.5.6)
propshaft (0.9.0)
actionpack (>= 7.0.0)
activesupport (>= 7.0.0)
rack
railties (>= 7.0.0)
psych (5.1.2) psych (5.1.2)
stringio stringio
public_suffix (5.0.5) public_suffix (5.0.5)
puma (6.4.2) puma (6.4.2)
nio4r (~> 2.0) nio4r (~> 2.0)
racc (1.7.3) racc (1.8.0)
rack (3.0.10) rack (3.0.11)
rack-session (2.0.0) rack-session (2.0.0)
rack (>= 3.0.0) rack (>= 3.0.0)
rack-test (2.1.0) rack-test (2.1.0)
@ -175,20 +180,20 @@ GEM
rackup (2.1.0) rackup (2.1.0)
rack (>= 3) rack (>= 3)
webrick (~> 1.8) webrick (~> 1.8)
rails (7.1.3.2) rails (7.1.3.3)
actioncable (= 7.1.3.2) actioncable (= 7.1.3.3)
actionmailbox (= 7.1.3.2) actionmailbox (= 7.1.3.3)
actionmailer (= 7.1.3.2) actionmailer (= 7.1.3.3)
actionpack (= 7.1.3.2) actionpack (= 7.1.3.3)
actiontext (= 7.1.3.2) actiontext (= 7.1.3.3)
actionview (= 7.1.3.2) actionview (= 7.1.3.3)
activejob (= 7.1.3.2) activejob (= 7.1.3.3)
activemodel (= 7.1.3.2) activemodel (= 7.1.3.3)
activerecord (= 7.1.3.2) activerecord (= 7.1.3.3)
activestorage (= 7.1.3.2) activestorage (= 7.1.3.3)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.3)
bundler (>= 1.15.0) bundler (>= 1.15.0)
railties (= 7.1.3.2) railties (= 7.1.3.3)
rails-dom-testing (2.2.0) rails-dom-testing (2.2.0)
activesupport (>= 5.0.0) activesupport (>= 5.0.0)
minitest minitest
@ -196,9 +201,9 @@ GEM
rails-html-sanitizer (1.6.0) rails-html-sanitizer (1.6.0)
loofah (~> 2.21) loofah (~> 2.21)
nokogiri (~> 1.14) nokogiri (~> 1.14)
railties (7.1.3.2) railties (7.1.3.3)
actionpack (= 7.1.3.2) actionpack (= 7.1.3.3)
activesupport (= 7.1.3.2) activesupport (= 7.1.3.3)
irb irb
rackup (>= 1.0.0) rackup (>= 1.0.0)
rake (>= 12.2) rake (>= 12.2)
@ -206,13 +211,14 @@ GEM
zeitwerk (~> 2.6) zeitwerk (~> 2.6)
rainbow (3.1.1) rainbow (3.1.1)
rake (13.2.1) rake (13.2.1)
rdoc (6.6.3.1) rdoc (6.7.0)
psych (>= 4.0.0) psych (>= 4.0.0)
regexp_parser (2.9.0) regexp_parser (2.9.2)
reline (0.5.1) reline (0.5.7)
io-console (~> 0.5) io-console (~> 0.5)
rexml (3.2.6) rexml (3.2.8)
rubocop (1.63.1) strscan (>= 3.0.9)
rubocop (1.64.0)
json (~> 2.3) json (~> 2.3)
language_server-protocol (>= 3.17.0) language_server-protocol (>= 3.17.0)
parallel (~> 1.10) parallel (~> 1.10)
@ -223,15 +229,15 @@ GEM
rubocop-ast (>= 1.31.1, < 2.0) rubocop-ast (>= 1.31.1, < 2.0)
ruby-progressbar (~> 1.7) ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0) unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.31.2) rubocop-ast (1.31.3)
parser (>= 3.3.0.4) parser (>= 3.3.1.0)
rubocop-minitest (0.35.0) rubocop-minitest (0.35.0)
rubocop (>= 1.61, < 2.0) rubocop (>= 1.61, < 2.0)
rubocop-ast (>= 1.31.1, < 2.0) rubocop-ast (>= 1.31.1, < 2.0)
rubocop-performance (1.21.0) rubocop-performance (1.21.0)
rubocop (>= 1.48.1, < 2.0) rubocop (>= 1.48.1, < 2.0)
rubocop-ast (>= 1.31.1, < 2.0) rubocop-ast (>= 1.31.1, < 2.0)
rubocop-rails (2.24.1) rubocop-rails (2.25.0)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
rack (>= 1.1) rack (>= 1.1)
rubocop (>= 1.33.0, < 2.0) rubocop (>= 1.33.0, < 2.0)
@ -243,21 +249,15 @@ GEM
rubocop-rails rubocop-rails
ruby-progressbar (1.13.0) ruby-progressbar (1.13.0)
rubyzip (2.3.2) rubyzip (2.3.2)
selenium-webdriver (4.19.0) selenium-webdriver (4.21.1)
base64 (~> 0.2) base64 (~> 0.2)
rexml (~> 3.2, >= 3.2.5) rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0) rubyzip (>= 1.2.2, < 3.0)
websocket (~> 1.0) websocket (~> 1.0)
sprockets (4.2.1)
concurrent-ruby (~> 1.0)
rack (>= 2.2.4, < 4)
sprockets-rails (3.4.2)
actionpack (>= 5.2)
activesupport (>= 5.2)
sprockets (>= 3.0.0)
stimulus-rails (1.3.3) stimulus-rails (1.3.3)
railties (>= 6.0.0) railties (>= 6.0.0)
stringio (3.1.0) stringio (3.1.0)
strscan (3.1.0)
thor (1.3.1) thor (1.3.1)
timeout (0.4.1) timeout (0.4.1)
turbo-rails (2.0.5) turbo-rails (2.0.5)
@ -279,7 +279,7 @@ GEM
websocket-extensions (0.1.5) websocket-extensions (0.1.5)
xpath (3.2.0) xpath (3.2.0)
nokogiri (~> 1.8) nokogiri (~> 1.8)
zeitwerk (2.6.13) zeitwerk (2.6.15)
PLATFORMS PLATFORMS
aarch64-linux aarch64-linux
@ -297,11 +297,11 @@ DEPENDENCIES
importmap-rails importmap-rails
jbuilder jbuilder
pg pg
propshaft
puma (>= 5.0) puma (>= 5.0)
rails (~> 7.1.3, >= 7.1.3.2) rails (~> 7.1.3, >= 7.1.3.2)
rubocop-rails-omakase rubocop-rails-omakase
selenium-webdriver selenium-webdriver
sprockets-rails
stimulus-rails stimulus-rails
turbo-rails turbo-rails
tzinfo-data tzinfo-data

View File

@ -1,17 +1,26 @@
# Forg # Tabletop Companion
Forg is a _Family ORGaniser_. Tabletop Companion is a lightweight way to assist you playing tabletop games, online with
the video chat software of your choice or offline at a physical table.
It will allow you to:
- Set and assign todos
- Includes regularly scheduled tasks, such as housework
- Set a meal plan
- Allows for advanced reminders to cook/purchase ingredients/defrost
- Manage a shopping list
## Development setup ## Development setup
Spin up the application with `./bin/setup`. Spin up the application with `./bin/setup`.
Run tests with `rails test`. Run tests with `rails test`.
### Git hooks
There is a pre-commit git hook stored in .git-hooks which can be installed by running `.git-hooks/install.bash`.
The hooks:
- Run Rubocop
- Run tests
- Run Brakeman
### Brakeman
If Brakeman issues are introduced, they should be immediately fixed or ignored with a note as to why it
is not a real issue. To do so, run `bundle exec brakeman -I`.

View File

@ -1,15 +0,0 @@
/*
* This is a manifest file that'll be compiled into application.css, which will include all the files
* listed below.
*
* Any CSS (and SCSS, if configured) file within this directory, lib/assets/stylesheets, or any plugin's
* vendor/assets/stylesheets directory can be referenced here using a relative path.
*
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
* compiled file so the styles you add here take precedence over styles defined in any other CSS
* files in this directory. Styles in this file should be added after the last require_* statement.
* It is generally better to create a new file per style scope.
*
*= require_tree .
*= require_self
*/

View File

@ -2,4 +2,12 @@
--inset-bg-color: #eee; --inset-bg-color: #eee;
--border-radius: .5em; --border-radius: .5em;
--button-bg-color: #333;
--button-text-color: #fff;
--button-hover-bg-color: #555;
--button-hover-text-color: #fff;
--notice-bg-color: #5cb85c;
--notice-text-color: #fff;
} }

View File

@ -13,6 +13,13 @@ main {
flex-direction: column; flex-direction: column;
} }
aside.flash {
background-color: var(--notice-bg-color);
color: var(--notice-text-color);
padding: 1em;
border-radius: var(--border-radius);
}
section.inset { section.inset {
width: 70%; width: 70%;
max-width: 60em; max-width: 60em;
@ -25,3 +32,25 @@ section.inset {
h1, h2 { h1, h2 {
text-align: center; text-align: center;
} }
header nav {
ul {
display: flex;
justify-content: center;
}
li {
list-style-type: none;
padding: 0 .5em;
}
a:link, a:visited {
background-color: var(--button-bg-color);
color: var(--button-text-color);
border-radius: var(--border-radius);
text-decoration: none;
padding: .5em;
}
a:hover {
background-color: var(--button-hover-bg-color);
color: var(--button-hover-text-color);
}
}

View File

@ -0,0 +1,57 @@
class Admin::GameSystemsController < AdminController
before_action :set_game_system, only: [ :show, :edit, :update, :destroy ]
def index
@game_systems = GameSystem.all
end
def show
end
def new
@game_system = GameSystem.new
end
def create
@game_system = GameSystem.new(game_system_params)
if @game_system.save
redirect_to admin_game_system_path(@game_system), notice: t(".success", name: @game_system.name)
else
flash.now[:alert] = t(".error", name: @game_system.name)
render :new, status: :unprocessable_entity
end
end
def edit
end
def update
if @game_system.update(game_system_params)
redirect_to admin_game_system_path(@game_system), notice: t(".success", name: @game_system.name)
else
flash.now[:alert] = t(".error", name: @game_system.name)
render :edit, status: :unprocessable_entity
end
end
def destroy
name = @game_system.name
if @game_system.destroy
redirect_to admin_game_systems_path, notice: t(".success", name:)
else
flash[:alert] = t(".error", name:)
redirect_to admin_game_systems_path, alert: t(".error")
end
end
private
def game_system_params
params.require(:game_system).permit(
:name,
)
end
def set_game_system
@game_system = GameSystem.find(params[:id])
end
end

View File

@ -0,0 +1,14 @@
class AdminController < ApplicationController
layout "admin"
before_action :authenticate_user_as_admin
def index
end
private
def authenticate_user_as_admin
head :forbidden unless Current.user&.admin?
end
end

View File

@ -4,7 +4,6 @@ class ApplicationController < ActionController::Base
private private
def authenticate def authenticate
Rails.logger.error "Session: #{session.inspect}"
if authenticated_user = User.find_by(id: session[:user_id]) if authenticated_user = User.find_by(id: session[:user_id])
Current.user = authenticated_user Current.user = authenticated_user
else else

View File

@ -0,0 +1,4 @@
class TablesController < ApplicationController
def index
end
end

View File

@ -1,4 +0,0 @@
class TodosController < ApplicationController
def index
end
end

View File

@ -5,12 +5,12 @@ class UserMailer < ApplicationMailer
@user = params[:user] @user = params[:user]
@token = params[:token] @token = params[:token]
mail(to: @user.email, subject: "[Forg] Verify your email") mail(to: @user.email, subject: "[Tabletop Companion] Verify your email")
end end
def email_verified def email_verified
@user = params[:user] @user = params[:user]
mail(to: @user.email, subject: "[Forg] Your email has been verified") mail(to: @user.email, subject: "[Tabletop Companion] Your email has been verified")
end end
end end

View File

@ -0,0 +1,5 @@
class GameSystem < ApplicationRecord
validates :name, presence: true,
uniqueness: true,
length: { maximum: 100 }
end

3
app/models/site_role.rb Normal file
View File

@ -0,0 +1,3 @@
class SiteRole < ApplicationRecord
validates :name, presence: true
end

View File

@ -1,4 +1,6 @@
class User < ApplicationRecord class User < ApplicationRecord
has_and_belongs_to_many :site_roles
has_secure_password has_secure_password
generates_token_for :password_reset, expires_in: 4.hours do generates_token_for :password_reset, expires_in: 4.hours do
password_salt.last(10) # Invalidates when password changed password_salt.last(10) # Invalidates when password changed
@ -35,4 +37,8 @@ class User < ApplicationRecord
"#{first_name} #{last_name}" "#{first_name} #{last_name}"
end end
def admin?
site_roles.include? SiteRole.find_by(name: "Admin")
end
end end

View File

@ -0,0 +1,8 @@
<% # locals: (game_system: @game_system, button_text:, url:) %>
<%= form_with model: @game_system, url: do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.submit button_text %>
<% end %>

View File

@ -0,0 +1,4 @@
<h2><%= t(".edit", name: @game_system.name) %></h2>
<%= render partial: "form",
locals: { button_text: t(".update", name: @game_system.name), url: admin_game_system_path(@game_system) } %>

View File

@ -0,0 +1,9 @@
<% content_for :title, t(".game_systems") %>
<%= link_to t(".new_game_system"), new_admin_game_system_path %>
<% @game_systems.each do |game_system| %>
<div id=<%= dom_id(game_system) %> class="game-system">
<%= link_to game_system.name, admin_game_system_path(game_system) %>
</div>
<% end %>

View File

@ -0,0 +1,3 @@
<h2><%= t(".new") %></h2>
<%= render partial: "form", locals: { button_text: t(".create"), url: admin_game_systems_path } %>

View File

@ -0,0 +1,6 @@
<h2><%= @game_system.name %></h2>
<%= link_to t("edit"), edit_admin_game_system_path(@game_system) %>
<%= link_to t("delete"), admin_game_system_path(@game_system),
data: { turbo_method: :delete, turbo_confirm: t(".confirm_delete", name: @game_system.name) } %>

View File

@ -0,0 +1,2 @@
<% content_for :title, t(".dashboard") %>
<%= t(".intro") %>

View File

@ -0,0 +1,11 @@
<% content_for :submenu do %>
<h2><%= t("administration") %>: <%= content_for :title %></h2>
<nav>
<ul>
<li><%= link_to t(".dashboard"), admin_index_path %></li>
<li><%= link_to t(".game_systems"), admin_game_systems_path %></li>
</ul>
</nav>
<% end %>
<%= render template: "layouts/application" %>

View File

@ -1,12 +1,12 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title><%= "#{yield(:title)} | " if content_for? :title %><%= t("forg") %></title> <title><%= "#{yield(:title)} | " if content_for? :title %><%= t("site_name") %></title>
<meta name="viewport" content="width=device-width,initial-scale=1"> <meta name="viewport" content="width=device-width,initial-scale=1">
<%= csrf_meta_tags %> <%= csrf_meta_tags %>
<%= csp_meta_tag %> <%= csp_meta_tag %>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> <%= stylesheet_link_tag :all, "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %> <%= javascript_importmap_tags %>
<meta name="turbo-refresh-method" content="morph"> <meta name="turbo-refresh-method" content="morph">
<meta name="turbo-refresh-scroll" content="preserve"> <meta name="turbo-refresh-scroll" content="preserve">
@ -14,19 +14,24 @@
<body> <body>
<header> <header>
<h1><%= link_to t("forg"), root_path %></h1> <h1><%= link_to t("site_name"), root_path %></h1>
<nav>
<ul>
<% if logged_in? %>
<li><%= link_to t("log_out"), logout_path, data: {turbo_method: :delete} %></li>
<% if Current.user.admin? %>
<li><%= link_to t("administration"), admin_index_path %></li>
<% end %>
<% else %>
<li><%= link_to t("log_in"), login_path %></li>
<li><%= link_to t("sign_up"), new_user_path %></li>
<% end %>
</ul>
</nav>
</header> </header>
<nav>
<ul>
<li><%= link_to t("forg"), root_path %></li>
<% if logged_in? %>
<li><%= link_to t("log_out"), logout_path, data: {turbo_method: :delete} %></li>
<% else %>
<li><%= link_to t("log_in"), login_path %></li>
<li><%= link_to t("sign_up"), new_user_path %></li>
<% end %>
<%= render partial: "shared/flash_messages" %> <%= render partial: "shared/flash_messages" %>
<main> <main>
<%= yield(:submenu) if content_for?(:submenu) %>
<%= yield %> <%= yield %>
</main> </main>
</body> </body>

View File

@ -6,7 +6,7 @@ require "rails/all"
# you've limited to :test, :development, or :production. # you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups) Bundler.require(*Rails.groups)
module Forg module TabletopCompanion
class Application < Rails::Application class Application < Rails::Application
# Initialize configuration defaults for originally generated Rails version. # Initialize configuration defaults for originally generated Rails version.
config.load_defaults 7.1 config.load_defaults 7.1

View File

@ -7,4 +7,4 @@ test:
production: production:
adapter: redis adapter: redis
url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
channel_prefix: forg_production channel_prefix: tabletop_companion_production

View File

@ -9,12 +9,12 @@ default: &default
development: development:
<<: *default <<: *default
database: forg_development database: tabletop_companion_development
test: test:
<<: *default <<: *default
database: forg_test database: tabletop_companion_test
production: production:
<<: *default <<: *default
database: forg_production database: tabletop_companion_production

View File

@ -1,24 +1,56 @@
en: en:
forg: Forg site_name: Tabletop Companion
administration: Administration
log_in: Log in log_in: Log in
log_out: Log out log_out: Log out
sign_up: Sign up sign_up: Sign up
not_authenticated: You need to log in to access this page. not_authenticated: You need to log in to access this page.
edit: Edit
delete: Delete
layouts: layouts:
admin:
dashboard: Dashboard
game_systems: Game Systems
mailer: mailer:
greeting: "Hello, %{name}" greeting: "Hello, %{name}"
sign_off: | sign_off: |
See you soon, See you soon,
The Forg team The Tabletop Companion team
sign_off_html: "<p>See you soon,<br>The Forg team</p>" sign_off_html: "<p>See you soon,<br>The Tabletop Companion team</p>"
account_verifications: account_verifications:
show: show:
success: "Thanks for verifying your email address! You can now log in." success: "Thanks for verifying your email address! You can now log in."
error: "Invalid token, could not verify your account." error: "Invalid token, could not verify your account."
admin:
index:
dashboard: Dashboard
intro: With great power comes great responsibility
game_systems:
error: Error
index:
game_systems: Game systems
new_game_system: New game system
new:
new: New game system
create: Add game system
create:
success: “%{name}” has been created.
error: “%{name}” could not be created.
show:
confirm_delete: Are you sure you want to delete “%{name}”?
edit:
edit: Edit %{name}
update: Update %{name}
update:
success: Successfully updated “%{name}”.
error: “%{name}” could not be updated.
destroy:
success: Successfully deleted “%{name}”.
error: “%{name}” could not be deleted.
sessions: sessions:
create: create:
success: "Hello, %{name}!" success: "Hello, %{name}!"
error: "Could not sign in. Please check your email and password." error: "Could not sign in. Please check your username and password."
new: new:
log_in: Log in log_in: Log in
destroy: destroy:
@ -31,16 +63,16 @@ en:
sign_up: Sign up sign_up: Sign up
create: create:
error: "Could not create account: %{error}" error: "Could not create account: %{error}"
success: "Thanks for joining Forg, %{name}! Please check your email to verify your address." success: "Thanks for joining Tabletop Companion, %{name}! Please check your email to verify your address."
user_mailer: user_mailer:
email_verified: email_verified:
content: |- content: |-
Thanks for verifying your email address, and welcome to Forg! Thanks for verifying your email address, and welcome to Tabletop Companion!
You can now go ahead and log in. Enjoy! You can now go ahead and log in. Enjoy!
email_verification: email_verification:
content: |- content: |-
If you did not sign up for Forg, please ignore this email. If you did not sign up for Tabletop Companion, please ignore this email.
Otherwise, please visit the link below to verify your email address. Otherwise, please visit the link below to verify your email address.

View File

@ -1,15 +1,21 @@
Rails.application.routes.draw do Rails.application.routes.draw do
get 'todos/index' default_url_options host: "summonplayer.com"
default_url_options host: "forg-app.com"
root "todos#index" root "tables#index"
get "login" => "sessions#new", as: :login get "login" => "sessions#new", as: :login
delete "logout" => "sessions#destroy", as: :logout delete "logout" => "sessions#destroy", as: :logout
resources :users, only: %i[new create] resources :users, only: [ :new, :create ]
resources :account_verifications, only: %i[show] resources :account_verifications, only: [ :show ]
resources :sessions, only: %i[new create destroy] resources :sessions, only: [ :new, :create, :destroy ]
resources :tables, only: [ :index ]
resources :admin, only: [ :index ]
namespace :admin do
resources :game_systems
end
get "up" => "rails/health#show", as: :rails_health_check get "up" => "rails/health#show", as: :rails_health_check
end end

View File

@ -0,0 +1,9 @@
class CreateGameSystems < ActiveRecord::Migration[7.1]
def change
create_table :game_systems do |t|
t.string :name, null: false
t.timestamps
end
end
end

View File

@ -0,0 +1,14 @@
class CreateUserRoles < ActiveRecord::Migration[7.1]
def change
create_table :site_roles do |t|
t.string :name, null: false
t.timestamps
end
create_table :site_roles_users do |t|
t.belongs_to :user
t.belongs_to :site_role
end
end
end

View File

@ -0,0 +1,7 @@
class AddUniqueIndexToGameSystemName < ActiveRecord::Migration[7.1]
def change
add_index :game_systems, :name, unique: true
add_check_constraint :game_systems, "length(name) <= 100", name: "chk_name_max_length"
end
end

23
db/schema.rb generated
View File

@ -10,10 +10,31 @@
# #
# 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_04_14_122652) do ActiveRecord::Schema[7.1].define(version: 2024_05_26_102908) 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"
create_table "game_systems", force: :cascade do |t|
t.string "name", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["name"], name: "index_game_systems_on_name", unique: true
t.check_constraint "length(name::text) <= 100", name: "chk_name_max_length"
end
create_table "site_roles", force: :cascade do |t|
t.string "name", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "site_roles_users", force: :cascade do |t|
t.bigint "user_id"
t.bigint "site_role_id"
t.index ["site_role_id"], name: "index_site_roles_users_on_site_role_id"
t.index ["user_id"], name: "index_site_roles_users_on_user_id"
end
create_table "users", force: :cascade do |t| create_table "users", force: :cascade do |t|
t.string "username", limit: 20, null: false t.string "username", limit: 20, null: false
t.string "password_digest", limit: 200, null: false t.string "password_digest", limit: 200, null: false

View File

@ -0,0 +1,20 @@
require "test_helper"
class AdminGameSystemsControllerTest < ActionDispatch::IntegrationTest
test "should get game systems index if signed in as admin" do
sign_in users(:admin)
get admin_game_systems_path
assert_response :success
end
test "should not get game systems index if signed in as non-admin user" do
sign_in users(:trevor)
get admin_game_systems_path
assert_response :forbidden
end
test "should not get game systems index if not signed in" do
get admin_game_systems_path
assert_redirected_to login_path
end
end

View File

@ -0,0 +1,20 @@
require "test_helper"
class AdminControllerTest < ActionDispatch::IntegrationTest
test "should get index if signed in as admin" do
sign_in users(:admin)
get admin_index_url
assert_response :success
end
test "should not get index if signed in as non-admin user" do
sign_in users(:trevor)
get admin_index_url
assert_response :forbidden
end
test "should not get index if not signed in" do
get admin_index_url
assert_redirected_to login_path
end
end

2
test/fixtures/game_systems.yml vendored Normal file
View File

@ -0,0 +1,2 @@
troika:
name: Troika

2
test/fixtures/site_roles.yml vendored Normal file
View File

@ -0,0 +1,2 @@
admin:
name: Admin

View File

@ -16,6 +16,12 @@ unverified:
last_name: User last_name: User
verified: false verified: false
admin:
<<: *DEFAULTS
first_name: Admin
last_name: User
site_roles: admin
<% 1.upto(10) do |i| %> <% 1.upto(10) do |i| %>
user_<%= i %>: user_<%= i %>:
<<: *DEFAULTS <<: *DEFAULTS

View File

@ -0,0 +1,64 @@
require "test_helper"
class AdminGameSystemsIntegrationTest < ActionDispatch::IntegrationTest
test "index should show most all game systems" do
sign_in users(:admin)
get admin_game_systems_path
assert_response :success
assert_select ".game-system", GameSystem.all.size
end
test "show should show game system" do
sign_in users(:admin)
get admin_game_system_path(game_systems(:troika))
assert_response :success
assert_select "h2", game_systems(:troika).name
end
test "new should show form to create new game system" do
sign_in users(:admin)
get new_admin_game_system_path
assert_response :success
assert_select "form[action=?][method=?]", admin_game_systems_path, "post"
end
test "create should create new game system" do
sign_in users(:admin)
assert_difference "GameSystem.count", 1 do
post admin_game_systems_path, params: { game_system: { name: "Test Game System" } }
end
assert_redirected_to admin_game_system_path(GameSystem.last)
end
test "create should fail if game system already exists" do
sign_in users(:admin)
assert_no_difference "GameSystem.count" do
post admin_game_systems_path, params: { game_system: { name: game_systems(:troika).name } }
end
assert_response :unprocessable_entity
end
test "edit should show form to edit game system" do
sign_in users(:admin)
get edit_admin_game_system_path(game_systems(:troika))
assert_response :success
assert_select "form[action=?][method=?]", admin_game_system_path(game_systems(:troika)), "post"
end
test "update should update game system" do
sign_in users(:admin)
patch admin_game_system_path(game_systems(:troika)), params: { game_system: { name: "Test Game System" } }
assert_redirected_to admin_game_system_path(game_systems(:troika))
end
test "destroy should destroy game system" do
sign_in users(:admin)
assert_difference "GameSystem.count", -1 do
delete admin_game_system_path(game_systems(:troika))
end
assert_redirected_to admin_game_systems_path
end
end

View File

@ -0,0 +1,11 @@
require "test_helper"
class PermissionsTest < ActionDispatch::IntegrationTest
test "admin? returns true for users with an admin role" do
user = users(:trevor)
assert_not user.admin?
user.site_roles << site_roles(:admin)
assert user.admin?
end
end

View File

@ -0,0 +1,7 @@
require "test_helper"
class GameSystemTest < ActiveSupport::TestCase
test "name must exist" do
assert_must_exist(game_systems(:troika), :name)
end
end

View File

@ -0,0 +1,7 @@
require "test_helper"
class UserRoleTest < ActiveSupport::TestCase
test "name must exist" do
assert_must_exist(site_roles(:admin), :name)
end
end

View File

@ -21,5 +21,9 @@ module ActiveSupport
def attr_name(klass, attr) def attr_name(klass, attr)
klass.human_attribute_name(attr) klass.human_attribute_name(attr)
end end
def sign_in(user, password: "password")
post sessions_path, params: { username: user.username, password: password }
end
end end
end end