Compare commits
8 commits
main
...
test-actio
Author | SHA1 | Date | |
---|---|---|---|
c29508999a | |||
72fc219e8d | |||
ecef64fa27 | |||
d0dc23b096 | |||
8ecac9a273 | |||
50347db4e1 | |||
38953380a1 | |||
b5af9e7c2b |
25 changed files with 213 additions and 385 deletions
|
@ -5,8 +5,12 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: cth-ubuntu-latest
|
runs-on: ruby-latest
|
||||||
|
container:
|
||||||
|
image: myoung34/github-runner:ubuntu-noble
|
||||||
steps:
|
steps:
|
||||||
|
- run: |
|
||||||
|
apt-get install -y autoconf patch build-essential rustc libssl-dev libyaml-dev libreadline6-dev zlib1g-dev libgmp-dev libncurses5-dev libffi-dev libgdbm6 libgdbm-dev libdb-dev uuid-dev
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: https://github.com/ruby/setup-ruby@bfefad842bb982ff05b233bcbc1571d97a87e69f
|
- uses: https://github.com/ruby/setup-ruby@bfefad842bb982ff05b233bcbc1571d97a87e69f
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v4
|
||||||
|
|
8
Gemfile
8
Gemfile
|
@ -20,7 +20,6 @@ gem "jbuilder"
|
||||||
# Use Redis adapter to run Action Cable in production
|
# Use Redis adapter to run Action Cable in production
|
||||||
# gem "redis", ">= 4.0.1"
|
# gem "redis", ">= 4.0.1"
|
||||||
|
|
||||||
gem "solid_cable"
|
|
||||||
gem "solid_cache"
|
gem "solid_cache"
|
||||||
gem "solid_queue"
|
gem "solid_queue"
|
||||||
|
|
||||||
|
@ -51,9 +50,6 @@ group :development, :test do
|
||||||
|
|
||||||
# Omakase Ruby styling [https://github.com/rails/rubocop-rails-omakase/]
|
# Omakase Ruby styling [https://github.com/rails/rubocop-rails-omakase/]
|
||||||
gem "rubocop-rails-omakase", require: false
|
gem "rubocop-rails-omakase", require: false
|
||||||
|
|
||||||
# Audit bundle for known vulnerabilities
|
|
||||||
gem "bundler-audit", require: false
|
|
||||||
end
|
end
|
||||||
|
|
||||||
group :development do
|
group :development do
|
||||||
|
@ -64,7 +60,7 @@ end
|
||||||
group :test do
|
group :test do
|
||||||
# Use system testing [https://guides.rubyonrails.org/testing.html#system-testing]
|
# Use system testing [https://guides.rubyonrails.org/testing.html#system-testing]
|
||||||
gem "capybara"
|
gem "capybara"
|
||||||
gem "cuprite"
|
gem "selenium-webdriver"
|
||||||
end
|
end
|
||||||
|
|
||||||
gem "tailwindcss-rails", "~> 3.3.1"
|
gem "tailwindcss-rails"
|
||||||
|
|
177
Gemfile.lock
177
Gemfile.lock
|
@ -1,6 +1,6 @@
|
||||||
GIT
|
GIT
|
||||||
remote: https://github.com/rails/rails.git
|
remote: https://github.com/rails/rails.git
|
||||||
revision: 4b7fb1d14b954d2348d2065ff955466479509656
|
revision: 1dcfe2316b4b239e2cf6cadd4efb7af4b4345e35
|
||||||
branch: main
|
branch: main
|
||||||
specs:
|
specs:
|
||||||
actioncable (8.1.0.alpha)
|
actioncable (8.1.0.alpha)
|
||||||
|
@ -34,7 +34,6 @@ GIT
|
||||||
rails-html-sanitizer (~> 1.6)
|
rails-html-sanitizer (~> 1.6)
|
||||||
useragent (~> 0.16)
|
useragent (~> 0.16)
|
||||||
actiontext (8.1.0.alpha)
|
actiontext (8.1.0.alpha)
|
||||||
action_text-trix (~> 2.1.15)
|
|
||||||
actionpack (= 8.1.0.alpha)
|
actionpack (= 8.1.0.alpha)
|
||||||
activerecord (= 8.1.0.alpha)
|
activerecord (= 8.1.0.alpha)
|
||||||
activestorage (= 8.1.0.alpha)
|
activestorage (= 8.1.0.alpha)
|
||||||
|
@ -101,24 +100,19 @@ GIT
|
||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
action_text-trix (2.1.15)
|
|
||||||
railties
|
|
||||||
addressable (2.8.7)
|
addressable (2.8.7)
|
||||||
public_suffix (>= 2.0.2, < 7.0)
|
public_suffix (>= 2.0.2, < 7.0)
|
||||||
ast (2.4.3)
|
ast (2.4.2)
|
||||||
base64 (0.3.0)
|
base64 (0.2.0)
|
||||||
bcrypt (3.1.20)
|
bcrypt (3.1.20)
|
||||||
benchmark (0.4.1)
|
benchmark (0.4.0)
|
||||||
bigdecimal (3.2.2)
|
bigdecimal (3.1.9)
|
||||||
bindex (0.8.1)
|
bindex (0.8.1)
|
||||||
bootsnap (1.18.6)
|
bootsnap (1.18.4)
|
||||||
msgpack (~> 1.2)
|
msgpack (~> 1.2)
|
||||||
brakeman (7.0.2)
|
brakeman (7.0.0)
|
||||||
racc
|
racc
|
||||||
builder (3.3.0)
|
builder (3.3.0)
|
||||||
bundler-audit (0.9.2)
|
|
||||||
bundler (>= 1.2.0, < 3)
|
|
||||||
thor (~> 1.0)
|
|
||||||
capybara (3.40.0)
|
capybara (3.40.0)
|
||||||
addressable
|
addressable
|
||||||
matrix
|
matrix
|
||||||
|
@ -129,50 +123,38 @@ GEM
|
||||||
regexp_parser (>= 1.5, < 3.0)
|
regexp_parser (>= 1.5, < 3.0)
|
||||||
xpath (~> 3.2)
|
xpath (~> 3.2)
|
||||||
concurrent-ruby (1.3.5)
|
concurrent-ruby (1.3.5)
|
||||||
connection_pool (2.5.3)
|
connection_pool (2.5.0)
|
||||||
crass (1.0.6)
|
crass (1.0.6)
|
||||||
cuprite (0.17)
|
|
||||||
capybara (~> 3.0)
|
|
||||||
ferrum (~> 0.17.0)
|
|
||||||
date (3.4.1)
|
date (3.4.1)
|
||||||
debug (1.10.0)
|
debug (1.10.0)
|
||||||
irb (~> 1.10)
|
irb (~> 1.10)
|
||||||
reline (>= 0.3.8)
|
reline (>= 0.3.8)
|
||||||
drb (2.2.3)
|
drb (2.2.1)
|
||||||
erb (5.0.1)
|
|
||||||
erubi (1.13.1)
|
erubi (1.13.1)
|
||||||
et-orbi (1.2.11)
|
et-orbi (1.2.11)
|
||||||
tzinfo
|
tzinfo
|
||||||
ferrum (0.17.1)
|
|
||||||
addressable (~> 2.5)
|
|
||||||
base64 (~> 0.2)
|
|
||||||
concurrent-ruby (~> 1.1)
|
|
||||||
webrick (~> 1.7)
|
|
||||||
websocket-driver (~> 0.7)
|
|
||||||
fugit (1.11.1)
|
fugit (1.11.1)
|
||||||
et-orbi (~> 1, >= 1.2.11)
|
et-orbi (~> 1, >= 1.2.11)
|
||||||
raabro (~> 1.4)
|
raabro (~> 1.4)
|
||||||
globalid (1.2.1)
|
globalid (1.2.1)
|
||||||
activesupport (>= 6.1)
|
activesupport (>= 6.1)
|
||||||
i18n (1.14.7)
|
i18n (1.14.6)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
importmap-rails (2.1.0)
|
importmap-rails (2.1.0)
|
||||||
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.8.0)
|
io-console (0.8.0)
|
||||||
irb (1.15.2)
|
irb (1.14.3)
|
||||||
pp (>= 0.6.0)
|
|
||||||
rdoc (>= 4.0.0)
|
rdoc (>= 4.0.0)
|
||||||
reline (>= 0.4.2)
|
reline (>= 0.4.2)
|
||||||
jbuilder (2.13.0)
|
jbuilder (2.13.0)
|
||||||
actionview (>= 5.0.0)
|
actionview (>= 5.0.0)
|
||||||
activesupport (>= 5.0.0)
|
activesupport (>= 5.0.0)
|
||||||
json (2.12.2)
|
json (2.9.1)
|
||||||
language_server-protocol (3.17.0.5)
|
language_server-protocol (3.17.0.3)
|
||||||
lint_roller (1.1.0)
|
logger (1.6.5)
|
||||||
logger (1.7.0)
|
loofah (2.24.0)
|
||||||
loofah (2.24.1)
|
|
||||||
crass (~> 1.0.2)
|
crass (~> 1.0.2)
|
||||||
nokogiri (>= 1.12.0)
|
nokogiri (>= 1.12.0)
|
||||||
mail (2.8.1)
|
mail (2.8.1)
|
||||||
|
@ -183,54 +165,49 @@ 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.25.5)
|
minitest (5.25.4)
|
||||||
msgpack (1.8.0)
|
msgpack (1.7.5)
|
||||||
net-imap (0.5.8)
|
net-imap (0.5.5)
|
||||||
date
|
date
|
||||||
net-protocol
|
net-protocol
|
||||||
net-pop (0.1.2)
|
net-pop (0.1.2)
|
||||||
net-protocol
|
net-protocol
|
||||||
net-protocol (0.2.2)
|
net-protocol (0.2.2)
|
||||||
timeout
|
timeout
|
||||||
net-smtp (0.5.1)
|
net-smtp (0.5.0)
|
||||||
net-protocol
|
|
||||||
nio4r (2.7.4)
|
nio4r (2.7.4)
|
||||||
nokogiri (1.18.8-aarch64-linux-gnu)
|
nokogiri (1.18.1-aarch64-linux-gnu)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.18.8-arm64-darwin)
|
nokogiri (1.18.1-arm64-darwin)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.18.8-x86_64-linux-gnu)
|
nokogiri (1.18.1-x86_64-linux-gnu)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
parallel (1.27.0)
|
parallel (1.26.3)
|
||||||
parser (3.3.8.0)
|
parser (3.3.7.0)
|
||||||
ast (~> 2.4.1)
|
ast (~> 2.4.1)
|
||||||
racc
|
racc
|
||||||
pp (0.6.2)
|
|
||||||
prettyprint
|
|
||||||
prettyprint (0.2.0)
|
|
||||||
prism (1.4.0)
|
|
||||||
propshaft (1.1.0)
|
propshaft (1.1.0)
|
||||||
actionpack (>= 7.0.0)
|
actionpack (>= 7.0.0)
|
||||||
activesupport (>= 7.0.0)
|
activesupport (>= 7.0.0)
|
||||||
rack
|
rack
|
||||||
railties (>= 7.0.0)
|
railties (>= 7.0.0)
|
||||||
psych (5.2.6)
|
psych (5.2.2)
|
||||||
date
|
date
|
||||||
stringio
|
stringio
|
||||||
public_suffix (6.0.2)
|
public_suffix (6.0.1)
|
||||||
puma (6.6.0)
|
puma (6.5.0)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
raabro (1.4.0)
|
raabro (1.4.0)
|
||||||
racc (1.8.1)
|
racc (1.8.1)
|
||||||
rack (3.1.16)
|
rack (3.1.8)
|
||||||
rack-session (2.1.1)
|
rack-session (2.1.0)
|
||||||
base64 (>= 0.1.0)
|
base64 (>= 0.1.0)
|
||||||
rack (>= 3.0.0)
|
rack (>= 3.0.0)
|
||||||
rack-test (2.2.0)
|
rack-test (2.2.0)
|
||||||
rack (>= 1.3)
|
rack (>= 1.3)
|
||||||
rackup (2.2.1)
|
rackup (2.2.1)
|
||||||
rack (>= 3)
|
rack (>= 3)
|
||||||
rails-dom-testing (2.3.0)
|
rails-dom-testing (2.2.0)
|
||||||
activesupport (>= 5.0.0)
|
activesupport (>= 5.0.0)
|
||||||
minitest
|
minitest
|
||||||
nokogiri (>= 1.6)
|
nokogiri (>= 1.6)
|
||||||
|
@ -238,96 +215,98 @@ GEM
|
||||||
loofah (~> 2.21)
|
loofah (~> 2.21)
|
||||||
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
|
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
|
||||||
rainbow (3.1.1)
|
rainbow (3.1.1)
|
||||||
rake (13.3.0)
|
rake (13.2.1)
|
||||||
rdoc (6.14.0)
|
rdoc (6.11.0)
|
||||||
erb
|
|
||||||
psych (>= 4.0.0)
|
psych (>= 4.0.0)
|
||||||
regexp_parser (2.10.0)
|
regexp_parser (2.10.0)
|
||||||
reline (0.6.1)
|
reline (0.6.0)
|
||||||
io-console (~> 0.5)
|
io-console (~> 0.5)
|
||||||
rubocop (1.76.0)
|
rexml (3.4.0)
|
||||||
|
rubocop (1.70.0)
|
||||||
json (~> 2.3)
|
json (~> 2.3)
|
||||||
language_server-protocol (~> 3.17.0.2)
|
language_server-protocol (>= 3.17.0)
|
||||||
lint_roller (~> 1.1.0)
|
|
||||||
parallel (~> 1.10)
|
parallel (~> 1.10)
|
||||||
parser (>= 3.3.0.2)
|
parser (>= 3.3.0.2)
|
||||||
rainbow (>= 2.2.2, < 4.0)
|
rainbow (>= 2.2.2, < 4.0)
|
||||||
regexp_parser (>= 2.9.3, < 3.0)
|
regexp_parser (>= 2.9.3, < 3.0)
|
||||||
rubocop-ast (>= 1.45.0, < 2.0)
|
rubocop-ast (>= 1.36.2, < 2.0)
|
||||||
ruby-progressbar (~> 1.7)
|
ruby-progressbar (~> 1.7)
|
||||||
unicode-display_width (>= 2.4.0, < 4.0)
|
unicode-display_width (>= 2.4.0, < 4.0)
|
||||||
rubocop-ast (1.45.0)
|
rubocop-ast (1.37.0)
|
||||||
parser (>= 3.3.7.2)
|
parser (>= 3.3.1.0)
|
||||||
prism (~> 1.4)
|
rubocop-minitest (0.36.0)
|
||||||
rubocop-performance (1.25.0)
|
rubocop (>= 1.61, < 2.0)
|
||||||
lint_roller (~> 1.1)
|
rubocop-ast (>= 1.31.1, < 2.0)
|
||||||
rubocop (>= 1.75.0, < 2.0)
|
rubocop-performance (1.23.1)
|
||||||
rubocop-ast (>= 1.38.0, < 2.0)
|
rubocop (>= 1.48.1, < 2.0)
|
||||||
rubocop-rails (2.32.0)
|
rubocop-ast (>= 1.31.1, < 2.0)
|
||||||
|
rubocop-rails (2.28.0)
|
||||||
activesupport (>= 4.2.0)
|
activesupport (>= 4.2.0)
|
||||||
lint_roller (~> 1.1)
|
|
||||||
rack (>= 1.1)
|
rack (>= 1.1)
|
||||||
rubocop (>= 1.75.0, < 2.0)
|
rubocop (>= 1.52.0, < 2.0)
|
||||||
rubocop-ast (>= 1.44.0, < 2.0)
|
rubocop-ast (>= 1.31.1, < 2.0)
|
||||||
rubocop-rails-omakase (1.1.0)
|
rubocop-rails-omakase (1.0.0)
|
||||||
rubocop (>= 1.72)
|
rubocop
|
||||||
rubocop-performance (>= 1.24)
|
rubocop-minitest
|
||||||
rubocop-rails (>= 2.30)
|
rubocop-performance
|
||||||
|
rubocop-rails
|
||||||
ruby-progressbar (1.13.0)
|
ruby-progressbar (1.13.0)
|
||||||
|
rubyzip (2.4.1)
|
||||||
securerandom (0.4.1)
|
securerandom (0.4.1)
|
||||||
solid_cable (3.0.8)
|
selenium-webdriver (4.27.0)
|
||||||
actioncable (>= 7.2)
|
base64 (~> 0.2)
|
||||||
|
logger (~> 1.4)
|
||||||
|
rexml (~> 3.2, >= 3.2.5)
|
||||||
|
rubyzip (>= 1.2.2, < 3.0)
|
||||||
|
websocket (~> 1.0)
|
||||||
|
solid_cache (1.0.6)
|
||||||
activejob (>= 7.2)
|
activejob (>= 7.2)
|
||||||
activerecord (>= 7.2)
|
activerecord (>= 7.2)
|
||||||
railties (>= 7.2)
|
railties (>= 7.2)
|
||||||
solid_cache (1.0.7)
|
solid_queue (1.1.2)
|
||||||
activejob (>= 7.2)
|
|
||||||
activerecord (>= 7.2)
|
|
||||||
railties (>= 7.2)
|
|
||||||
solid_queue (1.1.5)
|
|
||||||
activejob (>= 7.1)
|
activejob (>= 7.1)
|
||||||
activerecord (>= 7.1)
|
activerecord (>= 7.1)
|
||||||
concurrent-ruby (>= 1.3.1)
|
concurrent-ruby (>= 1.3.1)
|
||||||
fugit (~> 1.11.0)
|
fugit (~> 1.11.0)
|
||||||
railties (>= 7.1)
|
railties (>= 7.1)
|
||||||
thor (~> 1.3.1)
|
thor (~> 1.3.1)
|
||||||
sqlite3 (2.6.0-aarch64-linux-gnu)
|
sqlite3 (2.5.0-aarch64-linux-gnu)
|
||||||
sqlite3 (2.6.0-arm64-darwin)
|
sqlite3 (2.5.0-arm64-darwin)
|
||||||
sqlite3 (2.6.0-x86_64-linux-gnu)
|
sqlite3 (2.5.0-x86_64-linux-gnu)
|
||||||
stimulus-rails (1.3.4)
|
stimulus-rails (1.3.4)
|
||||||
railties (>= 6.0.0)
|
railties (>= 6.0.0)
|
||||||
stringio (3.1.7)
|
stringio (3.1.2)
|
||||||
tailwindcss-rails (3.3.2)
|
tailwindcss-rails (3.2.0)
|
||||||
railties (>= 7.0.0)
|
railties (>= 7.0.0)
|
||||||
tailwindcss-ruby (~> 3.0)
|
tailwindcss-ruby
|
||||||
tailwindcss-ruby (3.4.17-aarch64-linux)
|
tailwindcss-ruby (3.4.17-aarch64-linux)
|
||||||
tailwindcss-ruby (3.4.17-arm64-darwin)
|
tailwindcss-ruby (3.4.17-arm64-darwin)
|
||||||
tailwindcss-ruby (3.4.17-x86_64-linux)
|
tailwindcss-ruby (3.4.17-x86_64-linux)
|
||||||
thor (1.3.2)
|
thor (1.3.2)
|
||||||
timeout (0.4.3)
|
timeout (0.4.3)
|
||||||
turbo-rails (2.0.16)
|
turbo-rails (2.0.11)
|
||||||
actionpack (>= 7.1.0)
|
actionpack (>= 6.0.0)
|
||||||
railties (>= 7.1.0)
|
railties (>= 6.0.0)
|
||||||
tzinfo (2.0.6)
|
tzinfo (2.0.6)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
unicode-display_width (3.1.4)
|
unicode-display_width (3.1.4)
|
||||||
unicode-emoji (~> 4.0, >= 4.0.4)
|
unicode-emoji (~> 4.0, >= 4.0.4)
|
||||||
unicode-emoji (4.0.4)
|
unicode-emoji (4.0.4)
|
||||||
uri (1.0.3)
|
uri (1.0.2)
|
||||||
useragent (0.16.11)
|
useragent (0.16.11)
|
||||||
web-console (4.2.1)
|
web-console (4.2.1)
|
||||||
actionview (>= 6.0.0)
|
actionview (>= 6.0.0)
|
||||||
activemodel (>= 6.0.0)
|
activemodel (>= 6.0.0)
|
||||||
bindex (>= 0.4.0)
|
bindex (>= 0.4.0)
|
||||||
railties (>= 6.0.0)
|
railties (>= 6.0.0)
|
||||||
webrick (1.9.1)
|
websocket (1.2.11)
|
||||||
websocket-driver (0.8.0)
|
websocket-driver (0.7.7)
|
||||||
base64
|
base64
|
||||||
websocket-extensions (>= 0.1.0)
|
websocket-extensions (>= 0.1.0)
|
||||||
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.7.3)
|
zeitwerk (2.7.1)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
aarch64-linux
|
aarch64-linux
|
||||||
|
@ -339,9 +318,7 @@ DEPENDENCIES
|
||||||
bcrypt
|
bcrypt
|
||||||
bootsnap
|
bootsnap
|
||||||
brakeman
|
brakeman
|
||||||
bundler-audit
|
|
||||||
capybara
|
capybara
|
||||||
cuprite
|
|
||||||
debug
|
debug
|
||||||
importmap-rails
|
importmap-rails
|
||||||
jbuilder
|
jbuilder
|
||||||
|
@ -349,12 +326,12 @@ DEPENDENCIES
|
||||||
puma (>= 5.0)
|
puma (>= 5.0)
|
||||||
rails!
|
rails!
|
||||||
rubocop-rails-omakase
|
rubocop-rails-omakase
|
||||||
solid_cable
|
selenium-webdriver
|
||||||
solid_cache
|
solid_cache
|
||||||
solid_queue
|
solid_queue
|
||||||
sqlite3 (>= 1.4)
|
sqlite3 (>= 1.4)
|
||||||
stimulus-rails
|
stimulus-rails
|
||||||
tailwindcss-rails (~> 3.3.1)
|
tailwindcss-rails
|
||||||
turbo-rails
|
turbo-rails
|
||||||
tzinfo-data
|
tzinfo-data
|
||||||
web-console
|
web-console
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
class SessionsController < ApplicationController
|
class SessionsController < ApplicationController
|
||||||
require_unregistered_user only: %i[new create]
|
require_unregistered_user only: %i[new create]
|
||||||
rate_limit to: 5, within: 1.minute, only: :create, with: -> { redirect_to new_session_url, alert: "Cannot currently login" }
|
|
||||||
# GET /sessions/new
|
# GET /sessions/new
|
||||||
def new
|
def new
|
||||||
@session = Session.new
|
@session = Session.new
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
#!/usr/bin/env ruby
|
|
||||||
require_relative "../config/boot"
|
|
||||||
require "bundler/audit/cli"
|
|
||||||
|
|
||||||
ARGV.concat %w[ --config config/bundler-audit.yml ] if ARGV.empty? || ARGV.include?("check")
|
|
||||||
Bundler::Audit::CLI.start
|
|
6
bin/ci
6
bin/ci
|
@ -1,6 +0,0 @@
|
||||||
#!/usr/bin/env ruby
|
|
||||||
require_relative "../config/boot"
|
|
||||||
require "active_support/continuous_integration"
|
|
||||||
|
|
||||||
CI = ActiveSupport::ContinuousIntegration
|
|
||||||
require_relative "../config/ci.rb"
|
|
|
@ -22,7 +22,6 @@ FileUtils.chdir APP_ROOT do
|
||||||
|
|
||||||
puts "\n== Preparing database =="
|
puts "\n== Preparing database =="
|
||||||
system! "bin/rails db:prepare"
|
system! "bin/rails db:prepare"
|
||||||
system! "bin/rails db:reset" if ARGV.include?("--reset")
|
|
||||||
|
|
||||||
puts "\n== Removing old logs and tempfiles =="
|
puts "\n== Removing old logs and tempfiles =="
|
||||||
system! "bin/rails log:clear tmp:clear"
|
system! "bin/rails log:clear tmp:clear"
|
||||||
|
|
|
@ -9,7 +9,7 @@ Bundler.require(*Rails.groups)
|
||||||
module FamilyBudget
|
module FamilyBudget
|
||||||
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 8.1
|
config.load_defaults 8.0
|
||||||
|
|
||||||
# Please, add to the `ignore` list any other `lib` subdirectories that do
|
# Please, add to the `ignore` list any other `lib` subdirectories that do
|
||||||
# not contain `.rb` files, or that should not be reloaded or eager loaded.
|
# not contain `.rb` files, or that should not be reloaded or eager loaded.
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
# Audit all gems listed in the Gemfile for known security problems by running bin/bundler-audit.
|
|
||||||
# CVEs that are not relevant to the application can be enumerated on the ignore list below.
|
|
||||||
|
|
||||||
ignore:
|
|
||||||
- CVE-THAT-DOES-NOT-APPLY
|
|
|
@ -1,22 +1,10 @@
|
||||||
# Async adapter only works within the same process, so for manually triggering cable updates from a console,
|
|
||||||
# and seeing results in the browser, you must do so from the web console (running inside the dev process),
|
|
||||||
# not a terminal started via bin/rails console! Add "console" to any action or any ERB template view
|
|
||||||
# to make the web console appear.
|
|
||||||
development:
|
development:
|
||||||
adapter: solid_cable
|
adapter: async
|
||||||
connects_to:
|
|
||||||
database:
|
|
||||||
writing: cable
|
|
||||||
polling_interval: 0.1.seconds
|
|
||||||
message_retention: 1.day
|
|
||||||
|
|
||||||
test:
|
test:
|
||||||
adapter: test
|
adapter: test
|
||||||
|
|
||||||
production:
|
production:
|
||||||
adapter: solid_cable
|
adapter: redis
|
||||||
connects_to:
|
url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
|
||||||
database:
|
channel_prefix: family_budget_production
|
||||||
writing: cable
|
|
||||||
polling_interval: 0.1.seconds
|
|
||||||
message_retention: 1.day
|
|
||||||
|
|
23
config/ci.rb
23
config/ci.rb
|
@ -1,23 +0,0 @@
|
||||||
# Run using bin/ci
|
|
||||||
|
|
||||||
CI.run do
|
|
||||||
step "Setup", "bin/setup --skip-server"
|
|
||||||
|
|
||||||
step "Style: Ruby", "bin/rubocop"
|
|
||||||
|
|
||||||
step "Security: Gem audit", "bin/bundler-audit"
|
|
||||||
step "Security: Importmap vulnerability audit", "bin/importmap audit"
|
|
||||||
step "Security: Brakeman code analysis", "bin/brakeman --quiet --no-pager --exit-on-warn --exit-on-error"
|
|
||||||
|
|
||||||
step "Tests: Rails", "bin/rails test"
|
|
||||||
step "Tests: System", "bin/rails test:system"
|
|
||||||
step "Tests: Seeds", "env RAILS_ENV=test bin/rails db:seed:replant"
|
|
||||||
|
|
||||||
# Optional: set a green GitHub commit status to unblock PR merge.
|
|
||||||
# Requires the `gh` CLI and and `gh extension install basecamp/gh-signoff`.
|
|
||||||
# if success?
|
|
||||||
# step "Signoff: All systems go. Ready for merge and deploy.", "gh signoff"
|
|
||||||
# else
|
|
||||||
# failure "Signoff: CI failed. Do not merge or deploy.", "Fix the issues and try again."
|
|
||||||
# end
|
|
||||||
end
|
|
|
@ -21,10 +21,6 @@ development:
|
||||||
<<: *default
|
<<: *default
|
||||||
database: storage/development_queue.sqlite3
|
database: storage/development_queue.sqlite3
|
||||||
migrations_paths: db/queue_migrate
|
migrations_paths: db/queue_migrate
|
||||||
cable:
|
|
||||||
<<: *default
|
|
||||||
database: storage/development_cable.sqlite3
|
|
||||||
migrations_paths: db/cable_migrate
|
|
||||||
|
|
||||||
|
|
||||||
# Warning: The database defined as "test" will be erased and
|
# Warning: The database defined as "test" will be erased and
|
||||||
|
@ -46,7 +42,3 @@ production:
|
||||||
<<: *default
|
<<: *default
|
||||||
database: storage/production_queue.sqlite3
|
database: storage/production_queue.sqlite3
|
||||||
migrations_paths: db/queue_migrate
|
migrations_paths: db/queue_migrate
|
||||||
cable:
|
|
||||||
<<: *default
|
|
||||||
database: storage/production_cable.sqlite3
|
|
||||||
migrations_paths: db/cable_migrate
|
|
||||||
|
|
|
@ -72,4 +72,6 @@ Rails.application.configure do
|
||||||
|
|
||||||
# Apply autocorrection by RuboCop to files generated by `bin/rails generate`.
|
# Apply autocorrection by RuboCop to files generated by `bin/rails generate`.
|
||||||
# config.generators.apply_rubocop_autocorrect_after_generate!
|
# config.generators.apply_rubocop_autocorrect_after_generate!
|
||||||
|
# Use a real queuing backend for Active Job (and separate queues per environment).
|
||||||
|
config.active_job.queue_adapter = :solid_queue
|
||||||
end
|
end
|
||||||
|
|
|
@ -59,7 +59,7 @@ Rails.application.configure do
|
||||||
# Set host to be used by links generated in mailer templates.
|
# Set host to be used by links generated in mailer templates.
|
||||||
config.action_mailer.default_url_options = { host: "example.com" }
|
config.action_mailer.default_url_options = { host: "example.com" }
|
||||||
|
|
||||||
# Specify outgoing SMTP server. Remember to add smtp/* credentials via bin/rails credentials:edit.
|
# Specify outgoing SMTP server. Remember to add smtp/* credentials via rails credentials:edit.
|
||||||
# config.action_mailer.smtp_settings = {
|
# config.action_mailer.smtp_settings = {
|
||||||
# user_name: Rails.application.credentials.dig(:smtp, :user_name),
|
# user_name: Rails.application.credentials.dig(:smtp, :user_name),
|
||||||
# password: Rails.application.credentials.dig(:smtp, :password),
|
# password: Rails.application.credentials.dig(:smtp, :password),
|
||||||
|
@ -86,4 +86,10 @@ Rails.application.configure do
|
||||||
#
|
#
|
||||||
# Skip DNS rebinding protection for the default health check endpoint.
|
# Skip DNS rebinding protection for the default health check endpoint.
|
||||||
# config.host_authorization = { exclude: ->(request) { request.path == "/up" } }
|
# config.host_authorization = { exclude: ->(request) { request.path == "/up" } }
|
||||||
|
|
||||||
|
# Use a real queuing backend for Active Job (and separate queues per environment).
|
||||||
|
config.active_job.queue_adapter = :solid_queue
|
||||||
|
config.solid_queue.connects_to = { database: { writing: :queue } }
|
||||||
|
|
||||||
|
# config.active_job.queue_name_prefix = "family_budget_production"
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
# Use a real queuing backend for Active Job (and separate queues per environment).
|
|
||||||
Rails.application.config.active_job.queue_adapter = :solid_queue
|
|
||||||
Rails.application.config.solid_queue.connects_to = { database: { writing: :queue } }
|
|
|
@ -20,10 +20,6 @@
|
||||||
# config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s }
|
# config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s }
|
||||||
# config.content_security_policy_nonce_directives = %w(script-src style-src)
|
# config.content_security_policy_nonce_directives = %w(script-src style-src)
|
||||||
#
|
#
|
||||||
# # Automatically add `nonce` to `javascript_tag`, `javascript_include_tag`, and `stylesheet_link_tag`
|
|
||||||
# # if the corresponding directives are specified in `content_security_policy_nonce_directives`.
|
|
||||||
# # config.content_security_policy_nonce_auto = true
|
|
||||||
#
|
|
||||||
# # Report violations without enforcing the policy.
|
# # Report violations without enforcing the policy.
|
||||||
# # config.content_security_policy_report_only = true
|
# # config.content_security_policy_report_only = true
|
||||||
# end
|
# end
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
# Be sure to restart your server when you modify this file.
|
|
||||||
|
|
||||||
# Avoid CORS issues when API is called from the frontend app.
|
|
||||||
# Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin Ajax requests.
|
|
||||||
|
|
||||||
# Read more: https://github.com/cyu/rack-cors
|
|
||||||
|
|
||||||
# Rails.application.config.middleware.insert_before 0, Rack::Cors do
|
|
||||||
# allow do
|
|
||||||
# origins "example.com"
|
|
||||||
#
|
|
||||||
# resource "*",
|
|
||||||
# headers: :any,
|
|
||||||
# methods: [:get, :post, :put, :patch, :delete, :options, :head]
|
|
||||||
# end
|
|
||||||
# end
|
|
|
@ -8,21 +8,3 @@
|
||||||
#
|
#
|
||||||
# Read the Guide for Upgrading Ruby on Rails for more info on each option.
|
# Read the Guide for Upgrading Ruby on Rails for more info on each option.
|
||||||
# https://guides.rubyonrails.org/upgrading_ruby_on_rails.html
|
# https://guides.rubyonrails.org/upgrading_ruby_on_rails.html
|
||||||
|
|
||||||
###
|
|
||||||
# Skips escaping HTML entities and line separators. When set to `false`, the
|
|
||||||
# JSON renderer no longer escapes these to improve performance.
|
|
||||||
#
|
|
||||||
# Example:
|
|
||||||
# class PostsController < ApplicationController
|
|
||||||
# def index
|
|
||||||
# render json: { key: "\u2028\u2029<>&" }
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# Renders `{"key":"\u2028\u2029\u003c\u003e\u0026"}` with the previous default, but `{"key":"
<>&"}` with the config
|
|
||||||
# set to `false`.
|
|
||||||
#
|
|
||||||
# Applications that want to keep the escaping behavior can set the config to `true`.
|
|
||||||
#++
|
|
||||||
# Rails.configuration.action_controller.escape_json_responses = false
|
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
# production:
|
|
||||||
# periodic_cleanup:
|
|
||||||
# class: CleanSoftDeletedRecordsJob
|
|
||||||
# queue: background
|
|
||||||
# args: [ 1000, { batch_size: 500 } ]
|
|
||||||
# schedule: every hour
|
|
||||||
# periodic_command:
|
|
||||||
# command: "SoftDeletedRecord.due.delete_all"
|
|
||||||
# priority: 2
|
|
||||||
# schedule: at 5am every day
|
|
|
@ -1,23 +0,0 @@
|
||||||
# This file is auto-generated from the current state of the database. Instead
|
|
||||||
# of editing this file, please use the migrations feature of Active Record to
|
|
||||||
# incrementally modify your database, and then regenerate this schema definition.
|
|
||||||
#
|
|
||||||
# This file is the source Rails uses to define your schema when running `bin/rails
|
|
||||||
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
|
|
||||||
# be faster and is potentially less error prone than running all of your
|
|
||||||
# migrations from scratch. Old migrations may fail to apply correctly if those
|
|
||||||
# migrations use external dependencies or application code.
|
|
||||||
#
|
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
|
||||||
|
|
||||||
ActiveRecord::Schema[8.1].define(version: 1) do
|
|
||||||
create_table "solid_cable_messages", force: :cascade do |t|
|
|
||||||
t.binary "channel", limit: 1024, null: false
|
|
||||||
t.integer "channel_hash", limit: 8, null: false
|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.binary "payload", limit: 536870912, null: false
|
|
||||||
t.index ["channel"], name: "index_solid_cable_messages_on_channel"
|
|
||||||
t.index ["channel_hash"], name: "index_solid_cable_messages_on_channel_hash"
|
|
||||||
t.index ["created_at"], name: "index_solid_cable_messages_on_created_at"
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,22 +1,12 @@
|
||||||
# This file is auto-generated from the current state of the database. Instead
|
# frozen_string_literal: true
|
||||||
# of editing this file, please use the migrations feature of Active Record to
|
|
||||||
# incrementally modify your database, and then regenerate this schema definition.
|
|
||||||
#
|
|
||||||
# This file is the source Rails uses to define your schema when running `bin/rails
|
|
||||||
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
|
|
||||||
# be faster and is potentially less error prone than running all of your
|
|
||||||
# migrations from scratch. Old migrations may fail to apply correctly if those
|
|
||||||
# migrations use external dependencies or application code.
|
|
||||||
#
|
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
|
||||||
|
|
||||||
ActiveRecord::Schema[8.1].define(version: 1) do
|
ActiveRecord::Schema[7.1].define(version: 1) do
|
||||||
create_table "solid_cache_entries", force: :cascade do |t|
|
create_table "solid_cache_entries", force: :cascade do |t|
|
||||||
t.integer "byte_size", limit: 4, null: false
|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.binary "key", limit: 1024, null: false
|
t.binary "key", limit: 1024, null: false
|
||||||
t.integer "key_hash", limit: 8, null: false
|
|
||||||
t.binary "value", limit: 536870912, null: false
|
t.binary "value", limit: 536870912, null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.integer "key_hash", limit: 8, null: false
|
||||||
|
t.integer "byte_size", limit: 4, null: false
|
||||||
t.index ["byte_size"], name: "index_solid_cache_entries_on_byte_size"
|
t.index ["byte_size"], name: "index_solid_cache_entries_on_byte_size"
|
||||||
t.index ["key_hash", "byte_size"], name: "index_solid_cache_entries_on_key_hash_and_byte_size"
|
t.index ["key_hash", "byte_size"], name: "index_solid_cache_entries_on_key_hash_and_byte_size"
|
||||||
t.index ["key_hash"], name: "index_solid_cache_entries_on_key_hash", unique: true
|
t.index ["key_hash"], name: "index_solid_cache_entries_on_key_hash", unique: true
|
||||||
|
|
|
@ -1,135 +1,123 @@
|
||||||
# This file is auto-generated from the current state of the database. Instead
|
ActiveRecord::Schema[7.1].define(version: 2024_09_04_193154) do
|
||||||
# of editing this file, please use the migrations feature of Active Record to
|
|
||||||
# incrementally modify your database, and then regenerate this schema definition.
|
|
||||||
#
|
|
||||||
# This file is the source Rails uses to define your schema when running `bin/rails
|
|
||||||
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
|
|
||||||
# be faster and is potentially less error prone than running all of your
|
|
||||||
# migrations from scratch. Old migrations may fail to apply correctly if those
|
|
||||||
# migrations use external dependencies or application code.
|
|
||||||
#
|
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
|
||||||
|
|
||||||
ActiveRecord::Schema[8.1].define(version: 2024_09_04_193154) do
|
|
||||||
create_table "solid_queue_blocked_executions", force: :cascade do |t|
|
create_table "solid_queue_blocked_executions", force: :cascade do |t|
|
||||||
t.string "concurrency_key", null: false
|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.datetime "expires_at", null: false
|
|
||||||
t.bigint "job_id", null: false
|
t.bigint "job_id", null: false
|
||||||
t.integer "priority", default: 0, null: false
|
|
||||||
t.string "queue_name", null: false
|
t.string "queue_name", null: false
|
||||||
t.index ["concurrency_key", "priority", "job_id"], name: "index_solid_queue_blocked_executions_for_release"
|
t.integer "priority", default: 0, null: false
|
||||||
t.index ["expires_at", "concurrency_key"], name: "index_solid_queue_blocked_executions_for_maintenance"
|
t.string "concurrency_key", null: false
|
||||||
t.index ["job_id"], name: "index_solid_queue_blocked_executions_on_job_id", unique: true
|
t.datetime "expires_at", null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.index [ "concurrency_key", "priority", "job_id" ], name: "index_solid_queue_blocked_executions_for_release"
|
||||||
|
t.index [ "expires_at", "concurrency_key" ], name: "index_solid_queue_blocked_executions_for_maintenance"
|
||||||
|
t.index [ "job_id" ], name: "index_solid_queue_blocked_executions_on_job_id", unique: true
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "solid_queue_claimed_executions", force: :cascade do |t|
|
create_table "solid_queue_claimed_executions", force: :cascade do |t|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.bigint "job_id", null: false
|
t.bigint "job_id", null: false
|
||||||
t.bigint "process_id"
|
t.bigint "process_id"
|
||||||
t.index ["job_id"], name: "index_solid_queue_claimed_executions_on_job_id", unique: true
|
t.datetime "created_at", null: false
|
||||||
t.index ["process_id", "job_id"], name: "index_solid_queue_claimed_executions_on_process_id_and_job_id"
|
t.index [ "job_id" ], name: "index_solid_queue_claimed_executions_on_job_id", unique: true
|
||||||
|
t.index [ "process_id", "job_id" ], name: "index_solid_queue_claimed_executions_on_process_id_and_job_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "solid_queue_failed_executions", force: :cascade do |t|
|
create_table "solid_queue_failed_executions", force: :cascade do |t|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.text "error"
|
|
||||||
t.bigint "job_id", null: false
|
t.bigint "job_id", null: false
|
||||||
t.index ["job_id"], name: "index_solid_queue_failed_executions_on_job_id", unique: true
|
t.text "error"
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.index [ "job_id" ], name: "index_solid_queue_failed_executions_on_job_id", unique: true
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "solid_queue_jobs", force: :cascade do |t|
|
create_table "solid_queue_jobs", force: :cascade do |t|
|
||||||
t.string "active_job_id"
|
t.string "queue_name", null: false
|
||||||
t.text "arguments"
|
|
||||||
t.string "class_name", null: false
|
t.string "class_name", null: false
|
||||||
|
t.text "arguments"
|
||||||
|
t.integer "priority", default: 0, null: false
|
||||||
|
t.string "active_job_id"
|
||||||
|
t.datetime "scheduled_at"
|
||||||
|
t.datetime "finished_at"
|
||||||
t.string "concurrency_key"
|
t.string "concurrency_key"
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "finished_at"
|
|
||||||
t.integer "priority", default: 0, null: false
|
|
||||||
t.string "queue_name", null: false
|
|
||||||
t.datetime "scheduled_at"
|
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
t.index ["active_job_id"], name: "index_solid_queue_jobs_on_active_job_id"
|
t.index [ "active_job_id" ], name: "index_solid_queue_jobs_on_active_job_id"
|
||||||
t.index ["class_name"], name: "index_solid_queue_jobs_on_class_name"
|
t.index [ "class_name" ], name: "index_solid_queue_jobs_on_class_name"
|
||||||
t.index ["finished_at"], name: "index_solid_queue_jobs_on_finished_at"
|
t.index [ "finished_at" ], name: "index_solid_queue_jobs_on_finished_at"
|
||||||
t.index ["queue_name", "finished_at"], name: "index_solid_queue_jobs_for_filtering"
|
t.index [ "queue_name", "finished_at" ], name: "index_solid_queue_jobs_for_filtering"
|
||||||
t.index ["scheduled_at", "finished_at"], name: "index_solid_queue_jobs_for_alerting"
|
t.index [ "scheduled_at", "finished_at" ], name: "index_solid_queue_jobs_for_alerting"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "solid_queue_pauses", force: :cascade do |t|
|
create_table "solid_queue_pauses", force: :cascade do |t|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.string "queue_name", null: false
|
t.string "queue_name", null: false
|
||||||
t.index ["queue_name"], name: "index_solid_queue_pauses_on_queue_name", unique: true
|
t.datetime "created_at", null: false
|
||||||
|
t.index [ "queue_name" ], name: "index_solid_queue_pauses_on_queue_name", unique: true
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "solid_queue_processes", force: :cascade do |t|
|
create_table "solid_queue_processes", force: :cascade do |t|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.string "hostname"
|
|
||||||
t.string "kind", null: false
|
t.string "kind", null: false
|
||||||
t.datetime "last_heartbeat_at", null: false
|
t.datetime "last_heartbeat_at", null: false
|
||||||
t.text "metadata"
|
|
||||||
t.string "name", null: false
|
|
||||||
t.integer "pid", null: false
|
|
||||||
t.bigint "supervisor_id"
|
t.bigint "supervisor_id"
|
||||||
t.index ["last_heartbeat_at"], name: "index_solid_queue_processes_on_last_heartbeat_at"
|
t.integer "pid", null: false
|
||||||
t.index ["name", "supervisor_id"], name: "index_solid_queue_processes_on_name_and_supervisor_id", unique: true
|
t.string "hostname"
|
||||||
t.index ["supervisor_id"], name: "index_solid_queue_processes_on_supervisor_id"
|
t.text "metadata"
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.string "name", null: false
|
||||||
|
t.index [ "last_heartbeat_at" ], name: "index_solid_queue_processes_on_last_heartbeat_at"
|
||||||
|
t.index [ "name", "supervisor_id" ], name: "index_solid_queue_processes_on_name_and_supervisor_id", unique: true
|
||||||
|
t.index [ "supervisor_id" ], name: "index_solid_queue_processes_on_supervisor_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "solid_queue_ready_executions", force: :cascade do |t|
|
create_table "solid_queue_ready_executions", force: :cascade do |t|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.bigint "job_id", null: false
|
t.bigint "job_id", null: false
|
||||||
t.integer "priority", default: 0, null: false
|
|
||||||
t.string "queue_name", null: false
|
t.string "queue_name", null: false
|
||||||
t.index ["job_id"], name: "index_solid_queue_ready_executions_on_job_id", unique: true
|
t.integer "priority", default: 0, null: false
|
||||||
t.index ["priority", "job_id"], name: "index_solid_queue_poll_all"
|
t.datetime "created_at", null: false
|
||||||
t.index ["queue_name", "priority", "job_id"], name: "index_solid_queue_poll_by_queue"
|
t.index [ "job_id" ], name: "index_solid_queue_ready_executions_on_job_id", unique: true
|
||||||
|
t.index [ "priority", "job_id" ], name: "index_solid_queue_poll_all"
|
||||||
|
t.index [ "queue_name", "priority", "job_id" ], name: "index_solid_queue_poll_by_queue"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "solid_queue_recurring_executions", force: :cascade do |t|
|
create_table "solid_queue_recurring_executions", force: :cascade do |t|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.bigint "job_id", null: false
|
t.bigint "job_id", null: false
|
||||||
t.datetime "run_at", null: false
|
|
||||||
t.string "task_key", null: false
|
t.string "task_key", null: false
|
||||||
t.index ["job_id"], name: "index_solid_queue_recurring_executions_on_job_id", unique: true
|
t.datetime "run_at", null: false
|
||||||
t.index ["task_key", "run_at"], name: "index_solid_queue_recurring_executions_on_task_key_and_run_at", unique: true
|
t.datetime "created_at", null: false
|
||||||
|
t.index [ "job_id" ], name: "index_solid_queue_recurring_executions_on_job_id", unique: true
|
||||||
|
t.index [ "task_key", "run_at" ], name: "index_solid_queue_recurring_executions_on_task_key_and_run_at", unique: true
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "solid_queue_recurring_tasks", force: :cascade do |t|
|
create_table "solid_queue_recurring_tasks", force: :cascade do |t|
|
||||||
t.text "arguments"
|
|
||||||
t.string "class_name"
|
|
||||||
t.string "command", limit: 2048
|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.text "description"
|
|
||||||
t.string "key", null: false
|
t.string "key", null: false
|
||||||
t.integer "priority", default: 0
|
|
||||||
t.string "queue_name"
|
|
||||||
t.string "schedule", null: false
|
t.string "schedule", null: false
|
||||||
|
t.string "command", limit: 2048
|
||||||
|
t.string "class_name"
|
||||||
|
t.text "arguments"
|
||||||
|
t.string "queue_name"
|
||||||
|
t.integer "priority", default: 0
|
||||||
t.boolean "static", default: true, null: false
|
t.boolean "static", default: true, null: false
|
||||||
|
t.text "description"
|
||||||
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
t.index ["key"], name: "index_solid_queue_recurring_tasks_on_key", unique: true
|
t.index [ "key" ], name: "index_solid_queue_recurring_tasks_on_key", unique: true
|
||||||
t.index ["static"], name: "index_solid_queue_recurring_tasks_on_static"
|
t.index [ "static" ], name: "index_solid_queue_recurring_tasks_on_static"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "solid_queue_scheduled_executions", force: :cascade do |t|
|
create_table "solid_queue_scheduled_executions", force: :cascade do |t|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.bigint "job_id", null: false
|
t.bigint "job_id", null: false
|
||||||
t.integer "priority", default: 0, null: false
|
|
||||||
t.string "queue_name", null: false
|
t.string "queue_name", null: false
|
||||||
|
t.integer "priority", default: 0, null: false
|
||||||
t.datetime "scheduled_at", null: false
|
t.datetime "scheduled_at", null: false
|
||||||
t.index ["job_id"], name: "index_solid_queue_scheduled_executions_on_job_id", unique: true
|
t.datetime "created_at", null: false
|
||||||
t.index ["scheduled_at", "priority", "job_id"], name: "index_solid_queue_dispatch_all"
|
t.index [ "job_id" ], name: "index_solid_queue_scheduled_executions_on_job_id", unique: true
|
||||||
|
t.index [ "scheduled_at", "priority", "job_id" ], name: "index_solid_queue_dispatch_all"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "solid_queue_semaphores", force: :cascade do |t|
|
create_table "solid_queue_semaphores", force: :cascade do |t|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.datetime "expires_at", null: false
|
|
||||||
t.string "key", null: false
|
t.string "key", null: false
|
||||||
t.datetime "updated_at", null: false
|
|
||||||
t.integer "value", default: 1, null: false
|
t.integer "value", default: 1, null: false
|
||||||
t.index ["expires_at"], name: "index_solid_queue_semaphores_on_expires_at"
|
t.datetime "expires_at", null: false
|
||||||
t.index ["key", "value"], name: "index_solid_queue_semaphores_on_key_and_value"
|
t.datetime "created_at", null: false
|
||||||
t.index ["key"], name: "index_solid_queue_semaphores_on_key", unique: true
|
t.datetime "updated_at", null: false
|
||||||
|
t.index [ "expires_at" ], name: "index_solid_queue_semaphores_on_expires_at"
|
||||||
|
t.index [ "key", "value" ], name: "index_solid_queue_semaphores_on_key_and_value"
|
||||||
|
t.index [ "key" ], name: "index_solid_queue_semaphores_on_key", unique: true
|
||||||
end
|
end
|
||||||
|
|
||||||
add_foreign_key "solid_queue_blocked_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade
|
add_foreign_key "solid_queue_blocked_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade
|
||||||
|
|
88
db/schema.rb
generated
88
db/schema.rb
generated
|
@ -10,78 +10,78 @@
|
||||||
#
|
#
|
||||||
# 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[8.1].define(version: 2024_09_09_011044) do
|
ActiveRecord::Schema[8.0].define(version: 2024_09_09_011044) do
|
||||||
create_table "credit_card_bills", force: :cascade do |t|
|
create_table "credit_card_bills", force: :cascade do |t|
|
||||||
|
t.string "description"
|
||||||
t.decimal "amount"
|
t.decimal "amount"
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.string "description"
|
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "expenses", force: :cascade do |t|
|
create_table "expenses", force: :cascade do |t|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.boolean "credit_card", default: false, null: false
|
|
||||||
t.string "description", default: "", null: false
|
t.string "description", default: "", null: false
|
||||||
t.boolean "estimated", default: false, null: false
|
|
||||||
t.decimal "payment", precision: 8, scale: 2, default: "0.0", null: false
|
t.decimal "payment", precision: 8, scale: 2, default: "0.0", null: false
|
||||||
t.integer "period", default: 0, null: false
|
t.integer "period", default: 0, null: false
|
||||||
|
t.boolean "credit_card", default: false, null: false
|
||||||
|
t.boolean "estimated", default: false, null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "incomes", force: :cascade do |t|
|
create_table "incomes", force: :cascade do |t|
|
||||||
t.decimal "amount", precision: 9, scale: 2, default: "0.0", null: false
|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.string "description", default: "", null: false
|
t.string "description", default: "", null: false
|
||||||
t.boolean "included", default: true, null: false
|
t.boolean "included", default: true, null: false
|
||||||
|
t.decimal "amount", precision: 9, scale: 2, default: "0.0", null: false
|
||||||
t.integer "member_id", null: false
|
t.integer "member_id", null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
t.index ["member_id"], name: "index_incomes_on_member_id"
|
t.index ["member_id"], name: "index_incomes_on_member_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "members", force: :cascade do |t|
|
create_table "members", force: :cascade do |t|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.string "name", default: "", null: false
|
t.string "name", default: "", null: false
|
||||||
t.boolean "pays", default: true, null: false
|
t.boolean "pays", default: true, null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "solid_queue_blocked_executions", force: :cascade do |t|
|
create_table "solid_queue_blocked_executions", force: :cascade do |t|
|
||||||
t.string "concurrency_key", null: false
|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.datetime "expires_at", null: false
|
|
||||||
t.integer "job_id", null: false
|
t.integer "job_id", null: false
|
||||||
t.integer "priority", default: 0, null: false
|
|
||||||
t.string "queue_name", null: false
|
t.string "queue_name", null: false
|
||||||
|
t.integer "priority", default: 0, null: false
|
||||||
|
t.string "concurrency_key", null: false
|
||||||
|
t.datetime "expires_at", null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
t.index ["concurrency_key", "priority", "job_id"], name: "index_solid_queue_blocked_executions_for_release"
|
t.index ["concurrency_key", "priority", "job_id"], name: "index_solid_queue_blocked_executions_for_release"
|
||||||
t.index ["expires_at", "concurrency_key"], name: "index_solid_queue_blocked_executions_for_maintenance"
|
t.index ["expires_at", "concurrency_key"], name: "index_solid_queue_blocked_executions_for_maintenance"
|
||||||
t.index ["job_id"], name: "index_solid_queue_blocked_executions_on_job_id", unique: true
|
t.index ["job_id"], name: "index_solid_queue_blocked_executions_on_job_id", unique: true
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "solid_queue_claimed_executions", force: :cascade do |t|
|
create_table "solid_queue_claimed_executions", force: :cascade do |t|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.integer "job_id", null: false
|
t.integer "job_id", null: false
|
||||||
t.bigint "process_id"
|
t.bigint "process_id"
|
||||||
|
t.datetime "created_at", null: false
|
||||||
t.index ["job_id"], name: "index_solid_queue_claimed_executions_on_job_id", unique: true
|
t.index ["job_id"], name: "index_solid_queue_claimed_executions_on_job_id", unique: true
|
||||||
t.index ["process_id", "job_id"], name: "index_solid_queue_claimed_executions_on_process_id_and_job_id"
|
t.index ["process_id", "job_id"], name: "index_solid_queue_claimed_executions_on_process_id_and_job_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "solid_queue_failed_executions", force: :cascade do |t|
|
create_table "solid_queue_failed_executions", force: :cascade do |t|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.text "error"
|
|
||||||
t.integer "job_id", null: false
|
t.integer "job_id", null: false
|
||||||
|
t.text "error"
|
||||||
|
t.datetime "created_at", null: false
|
||||||
t.index ["job_id"], name: "index_solid_queue_failed_executions_on_job_id", unique: true
|
t.index ["job_id"], name: "index_solid_queue_failed_executions_on_job_id", unique: true
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "solid_queue_jobs", force: :cascade do |t|
|
create_table "solid_queue_jobs", force: :cascade do |t|
|
||||||
t.string "active_job_id"
|
t.string "queue_name", null: false
|
||||||
t.text "arguments"
|
|
||||||
t.string "class_name", null: false
|
t.string "class_name", null: false
|
||||||
|
t.text "arguments"
|
||||||
|
t.integer "priority", default: 0, null: false
|
||||||
|
t.string "active_job_id"
|
||||||
|
t.datetime "scheduled_at"
|
||||||
|
t.datetime "finished_at"
|
||||||
t.string "concurrency_key"
|
t.string "concurrency_key"
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "finished_at"
|
|
||||||
t.integer "priority", default: 0, null: false
|
|
||||||
t.string "queue_name", null: false
|
|
||||||
t.datetime "scheduled_at"
|
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
t.index ["active_job_id"], name: "index_solid_queue_jobs_on_active_job_id"
|
t.index ["active_job_id"], name: "index_solid_queue_jobs_on_active_job_id"
|
||||||
t.index ["class_name"], name: "index_solid_queue_jobs_on_class_name"
|
t.index ["class_name"], name: "index_solid_queue_jobs_on_class_name"
|
||||||
|
@ -91,85 +91,85 @@ ActiveRecord::Schema[8.1].define(version: 2024_09_09_011044) do
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "solid_queue_pauses", force: :cascade do |t|
|
create_table "solid_queue_pauses", force: :cascade do |t|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.string "queue_name", null: false
|
t.string "queue_name", null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
t.index ["queue_name"], name: "index_solid_queue_pauses_on_queue_name", unique: true
|
t.index ["queue_name"], name: "index_solid_queue_pauses_on_queue_name", unique: true
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "solid_queue_processes", force: :cascade do |t|
|
create_table "solid_queue_processes", force: :cascade do |t|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.string "hostname"
|
|
||||||
t.string "kind", null: false
|
t.string "kind", null: false
|
||||||
t.datetime "last_heartbeat_at", null: false
|
t.datetime "last_heartbeat_at", null: false
|
||||||
t.text "metadata"
|
|
||||||
t.string "name", null: false
|
|
||||||
t.integer "pid", null: false
|
|
||||||
t.bigint "supervisor_id"
|
t.bigint "supervisor_id"
|
||||||
|
t.integer "pid", null: false
|
||||||
|
t.string "hostname"
|
||||||
|
t.text "metadata"
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.string "name", null: false
|
||||||
t.index ["last_heartbeat_at"], name: "index_solid_queue_processes_on_last_heartbeat_at"
|
t.index ["last_heartbeat_at"], name: "index_solid_queue_processes_on_last_heartbeat_at"
|
||||||
t.index ["name", "supervisor_id"], name: "index_solid_queue_processes_on_name_and_supervisor_id", unique: true
|
t.index ["name", "supervisor_id"], name: "index_solid_queue_processes_on_name_and_supervisor_id", unique: true
|
||||||
t.index ["supervisor_id"], name: "index_solid_queue_processes_on_supervisor_id"
|
t.index ["supervisor_id"], name: "index_solid_queue_processes_on_supervisor_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "solid_queue_ready_executions", force: :cascade do |t|
|
create_table "solid_queue_ready_executions", force: :cascade do |t|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.integer "job_id", null: false
|
t.integer "job_id", null: false
|
||||||
t.integer "priority", default: 0, null: false
|
|
||||||
t.string "queue_name", null: false
|
t.string "queue_name", null: false
|
||||||
|
t.integer "priority", default: 0, null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
t.index ["job_id"], name: "index_solid_queue_ready_executions_on_job_id", unique: true
|
t.index ["job_id"], name: "index_solid_queue_ready_executions_on_job_id", unique: true
|
||||||
t.index ["priority", "job_id"], name: "index_solid_queue_poll_all"
|
t.index ["priority", "job_id"], name: "index_solid_queue_poll_all"
|
||||||
t.index ["queue_name", "priority", "job_id"], name: "index_solid_queue_poll_by_queue"
|
t.index ["queue_name", "priority", "job_id"], name: "index_solid_queue_poll_by_queue"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "solid_queue_recurring_executions", force: :cascade do |t|
|
create_table "solid_queue_recurring_executions", force: :cascade do |t|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.integer "job_id", null: false
|
t.integer "job_id", null: false
|
||||||
t.datetime "run_at", null: false
|
|
||||||
t.string "task_key", null: false
|
t.string "task_key", null: false
|
||||||
|
t.datetime "run_at", null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
t.index ["job_id"], name: "index_solid_queue_recurring_executions_on_job_id", unique: true
|
t.index ["job_id"], name: "index_solid_queue_recurring_executions_on_job_id", unique: true
|
||||||
t.index ["task_key", "run_at"], name: "index_solid_queue_recurring_executions_on_task_key_and_run_at", unique: true
|
t.index ["task_key", "run_at"], name: "index_solid_queue_recurring_executions_on_task_key_and_run_at", unique: true
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "solid_queue_recurring_tasks", force: :cascade do |t|
|
create_table "solid_queue_recurring_tasks", force: :cascade do |t|
|
||||||
t.text "arguments"
|
|
||||||
t.string "class_name"
|
|
||||||
t.string "command", limit: 2048
|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.text "description"
|
|
||||||
t.string "key", null: false
|
t.string "key", null: false
|
||||||
t.integer "priority", default: 0
|
|
||||||
t.string "queue_name"
|
|
||||||
t.string "schedule", null: false
|
t.string "schedule", null: false
|
||||||
|
t.string "command", limit: 2048
|
||||||
|
t.string "class_name"
|
||||||
|
t.text "arguments"
|
||||||
|
t.string "queue_name"
|
||||||
|
t.integer "priority", default: 0
|
||||||
t.boolean "static", default: true, null: false
|
t.boolean "static", default: true, null: false
|
||||||
|
t.text "description"
|
||||||
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
t.index ["key"], name: "index_solid_queue_recurring_tasks_on_key", unique: true
|
t.index ["key"], name: "index_solid_queue_recurring_tasks_on_key", unique: true
|
||||||
t.index ["static"], name: "index_solid_queue_recurring_tasks_on_static"
|
t.index ["static"], name: "index_solid_queue_recurring_tasks_on_static"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "solid_queue_scheduled_executions", force: :cascade do |t|
|
create_table "solid_queue_scheduled_executions", force: :cascade do |t|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.integer "job_id", null: false
|
t.integer "job_id", null: false
|
||||||
t.integer "priority", default: 0, null: false
|
|
||||||
t.string "queue_name", null: false
|
t.string "queue_name", null: false
|
||||||
|
t.integer "priority", default: 0, null: false
|
||||||
t.datetime "scheduled_at", null: false
|
t.datetime "scheduled_at", null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
t.index ["job_id"], name: "index_solid_queue_scheduled_executions_on_job_id", unique: true
|
t.index ["job_id"], name: "index_solid_queue_scheduled_executions_on_job_id", unique: true
|
||||||
t.index ["scheduled_at", "priority", "job_id"], name: "index_solid_queue_dispatch_all"
|
t.index ["scheduled_at", "priority", "job_id"], name: "index_solid_queue_dispatch_all"
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "solid_queue_semaphores", force: :cascade do |t|
|
create_table "solid_queue_semaphores", force: :cascade do |t|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.datetime "expires_at", null: false
|
|
||||||
t.string "key", null: false
|
t.string "key", null: false
|
||||||
t.datetime "updated_at", null: false
|
|
||||||
t.integer "value", default: 1, null: false
|
t.integer "value", default: 1, null: false
|
||||||
|
t.datetime "expires_at", null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
t.index ["expires_at"], name: "index_solid_queue_semaphores_on_expires_at"
|
t.index ["expires_at"], name: "index_solid_queue_semaphores_on_expires_at"
|
||||||
t.index ["key", "value"], name: "index_solid_queue_semaphores_on_key_and_value"
|
t.index ["key", "value"], name: "index_solid_queue_semaphores_on_key_and_value"
|
||||||
t.index ["key"], name: "index_solid_queue_semaphores_on_key", unique: true
|
t.index ["key"], name: "index_solid_queue_semaphores_on_key", unique: true
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "users", force: :cascade do |t|
|
create_table "users", force: :cascade do |t|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.string "email"
|
t.string "email"
|
||||||
t.string "password_digest"
|
t.string "password_digest"
|
||||||
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
t.index ["email"], name: "index_users_on_email", unique: true
|
t.index ["email"], name: "index_users_on_email", unique: true
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
require "test_helper"
|
require "test_helper"
|
||||||
require "capybara/cuprite"
|
|
||||||
|
|
||||||
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
|
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
|
||||||
driven_by :cuprite, options: { headless: ENV["VISIBLE_SYSTEM_TESTS"].blank? }
|
driven_by :selenium,
|
||||||
|
using: ENV["VISIBLE_SYSTEM_TESTS"].present? ? :chrome : :headless_chrome,
|
||||||
|
screen_size: [ 1400, 1400 ]
|
||||||
|
|
||||||
def login(email, password = "secret")
|
def login(email, password = "secret")
|
||||||
visit new_session_url
|
visit new_session_url
|
||||||
|
|
Loading…
Add table
Reference in a new issue