Compare commits

..

4 Commits

Author SHA1 Message Date
801e4a280c Merge branch 'main' into ractors
Some checks failed
Run unit tests / rubocop (pull_request) Failing after 52s
Run unit tests / check-licenses (pull_request) Successful in 57s
Run unit tests / copyright_notice (pull_request) Successful in 1m28s
Run unit tests / unit_tests (pull_request) Successful in 2m41s
Run unit tests / build-static-assets (pull_request) Has been skipped
2025-01-27 18:26:16 +00:00
3e62f2b08e Merge branch 'main' into ractors
Some checks failed
Run unit tests / unit_tests (pull_request) Failing after 5m0s
Build docker image / build-static-assets (pull_request) Failing after 1h10m27s
2024-08-12 08:21:57 +00:00
f3d7f76e79 Merge branch 'main' into ractors
Some checks failed
Run unit tests / unit_tests (pull_request) Failing after 2s
2024-08-02 16:46:29 +00:00
7b8cdd2276 WIP ractors
Some checks failed
Run unit tests / unit_tests (pull_request) Failing after 3s
2024-08-02 17:29:04 +02:00
65 changed files with 421 additions and 1247 deletions

View File

@ -23,7 +23,7 @@ jobs:
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
ref: ${{ github.head_ref }} # Checkout the actual branch, not the result if merged into the base ref: ${{ github.head_ref }} # Checkout the actual branch, not the result if merged into the base
- uses: ruby/setup-ruby@v1 - uses: ruby/setup-ruby@v1.207.0
- run: bundle install - run: bundle install
- &postgres_wait - &postgres_wait
name: Wait until Postgres is ready to accept connections name: Wait until Postgres is ready to accept connections
@ -65,29 +65,26 @@ jobs:
if: failure() if: failure()
run: docker ps --filter network=$JOB_CONTAINER_NAME-$GITHUB_JOB-network --filter name=$JOB_CONTAINER_NAME-* --format "{{.ID}}" | xargs docker rm -f run: docker ps --filter network=$JOB_CONTAINER_NAME-$GITHUB_JOB-network --filter name=$JOB_CONTAINER_NAME-* --format "{{.ID}}" | xargs docker rm -f
rubocop: rubocop:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- uses: ruby/setup-ruby@v1 - uses: ruby/setup-ruby@v1.207.0
- run: bundle install - run: bundle install
- run: bundle exec rubocop --force-exclusion --parallel - run: bundle exec rubocop --force-exclusion --parallel
check-licenses: check-licenses:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- uses: ruby/setup-ruby@v1 - uses: ruby/setup-ruby@v1.207.0
- name: Install project dependencies - name: Install project dependencies
run: bundle install --jobs `getconf _NPROCESSORS_ONLN` run: bundle install --jobs `getconf _NPROCESSORS_ONLN`
- name: Run license finder - name: Run license finder
run: license_finder run: license_finder
copyright_notice: copyright_notice:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -124,6 +121,9 @@ jobs:
timeout-minutes: 30 timeout-minutes: 30
needs: needs:
- unit_tests - unit_tests
- rubocop
- check-licenses
- copyright_notice
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:

1
.gitignore vendored
View File

@ -36,4 +36,3 @@
# Ignore swagger generated documentation # Ignore swagger generated documentation
swagger/v1/swagger.yaml swagger/v1/swagger.yaml
wedding-planner.code-workspace

View File

@ -1 +1 @@
ruby-3.4.3 ruby-3.4.1

View File

@ -1,7 +1,7 @@
# syntax = docker/dockerfile:1 # syntax = docker/dockerfile:1
# Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile # Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile
ARG RUBY_VERSION=3.4.3 ARG RUBY_VERSION=3.4.1
FROM registry.docker.com/library/ruby:$RUBY_VERSION-slim AS base FROM registry.docker.com/library/ruby:$RUBY_VERSION-slim AS base
# Rails app lives here # Rails app lives here
@ -13,7 +13,7 @@ ENV RAILS_ENV="production" \
BUNDLE_PATH="/usr/local/bundle" \ BUNDLE_PATH="/usr/local/bundle" \
BUNDLE_WITHOUT="development" BUNDLE_WITHOUT="development"
RUN apt-get update && apt-get install -y nodejs wkhtmltopdf RUN apt-get update && apt-get install -y nodejs
# Throw-away build stage to reduce size of final image # Throw-away build stage to reduce size of final image
FROM base AS build FROM base AS build

View File

@ -1,13 +1,13 @@
# syntax = docker/dockerfile:1 # syntax = docker/dockerfile:1
# Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile # Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile
ARG RUBY_VERSION=3.4.3 ARG RUBY_VERSION=3.4.1
FROM registry.docker.com/library/ruby:$RUBY_VERSION-slim as base FROM registry.docker.com/library/ruby:$RUBY_VERSION-slim as base
# Rails app lives here # Rails app lives here
WORKDIR /rails WORKDIR /rails
RUN apt-get update && apt-get install -y nodejs wkhtmltopdf RUN apt-get update && apt-get install -y nodejs
FROM base as build FROM base as build

View File

@ -2,7 +2,7 @@
source 'https://rubygems.org' source 'https://rubygems.org'
ruby '3.4.3' ruby '3.4.1'
gem 'bootsnap', require: false gem 'bootsnap', require: false
gem 'csv' gem 'csv'
gem 'importmap-rails' gem 'importmap-rails'
@ -33,7 +33,7 @@ group :development, :test do
gem 'factory_bot_rails' gem 'factory_bot_rails'
gem 'license_finder' gem 'license_finder'
gem 'pry' gem 'pry'
gem 'rspec-rails', '~> 8.0.0' gem 'rspec-rails', '~> 7.1.0'
gem 'shoulda-matchers', '~> 6.0' gem 'shoulda-matchers', '~> 6.0'
end end
@ -51,7 +51,3 @@ gem 'chroma'
gem 'solid_queue', '~> 1.0' gem 'solid_queue', '~> 1.0'
gem 'devise', '~> 4.9' gem 'devise', '~> 4.9'
gem 'wicked_pdf', '~> 2.8'
gem 'rqrcode', '~> 3.1'

View File

@ -1,29 +1,29 @@
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
actioncable (8.0.2) actioncable (8.0.1)
actionpack (= 8.0.2) actionpack (= 8.0.1)
activesupport (= 8.0.2) activesupport (= 8.0.1)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6) zeitwerk (~> 2.6)
actionmailbox (8.0.2) actionmailbox (8.0.1)
actionpack (= 8.0.2) actionpack (= 8.0.1)
activejob (= 8.0.2) activejob (= 8.0.1)
activerecord (= 8.0.2) activerecord (= 8.0.1)
activestorage (= 8.0.2) activestorage (= 8.0.1)
activesupport (= 8.0.2) activesupport (= 8.0.1)
mail (>= 2.8.0) mail (>= 2.8.0)
actionmailer (8.0.2) actionmailer (8.0.1)
actionpack (= 8.0.2) actionpack (= 8.0.1)
actionview (= 8.0.2) actionview (= 8.0.1)
activejob (= 8.0.2) activejob (= 8.0.1)
activesupport (= 8.0.2) activesupport (= 8.0.1)
mail (>= 2.8.0) mail (>= 2.8.0)
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
actionpack (8.0.2) actionpack (8.0.1)
actionview (= 8.0.2) actionview (= 8.0.1)
activesupport (= 8.0.2) activesupport (= 8.0.1)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
rack (>= 2.2.4) rack (>= 2.2.4)
rack-session (>= 1.0.1) rack-session (>= 1.0.1)
@ -31,35 +31,35 @@ GEM
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6) rails-html-sanitizer (~> 1.6)
useragent (~> 0.16) useragent (~> 0.16)
actiontext (8.0.2) actiontext (8.0.1)
actionpack (= 8.0.2) actionpack (= 8.0.1)
activerecord (= 8.0.2) activerecord (= 8.0.1)
activestorage (= 8.0.2) activestorage (= 8.0.1)
activesupport (= 8.0.2) activesupport (= 8.0.1)
globalid (>= 0.6.0) globalid (>= 0.6.0)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (8.0.2) actionview (8.0.1)
activesupport (= 8.0.2) activesupport (= 8.0.1)
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 (8.0.2) activejob (8.0.1)
activesupport (= 8.0.2) activesupport (= 8.0.1)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (8.0.2) activemodel (8.0.1)
activesupport (= 8.0.2) activesupport (= 8.0.1)
activerecord (8.0.2) activerecord (8.0.1)
activemodel (= 8.0.2) activemodel (= 8.0.1)
activesupport (= 8.0.2) activesupport (= 8.0.1)
timeout (>= 0.4.0) timeout (>= 0.4.0)
activestorage (8.0.2) activestorage (8.0.1)
actionpack (= 8.0.2) actionpack (= 8.0.1)
activejob (= 8.0.2) activejob (= 8.0.1)
activerecord (= 8.0.2) activerecord (= 8.0.1)
activesupport (= 8.0.2) activesupport (= 8.0.1)
marcel (~> 1.0) marcel (~> 1.0)
activesupport (8.0.2) activesupport (8.0.1)
base64 base64
benchmark (>= 0.3) benchmark (>= 0.3)
bigdecimal bigdecimal
@ -76,33 +76,30 @@ GEM
rails (>= 6.0) rails (>= 6.0)
addressable (2.8.7) addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0) public_suffix (>= 2.0.2, < 7.0)
annotaterb (4.18.0) annotaterb (4.13.0)
activerecord (>= 6.0.0) ast (2.4.2)
activesupport (>= 6.0.0)
ast (2.4.3)
babel-source (5.8.35) babel-source (5.8.35)
babel-transpiler (0.7.0) babel-transpiler (0.7.0)
babel-source (>= 4.0, < 6) babel-source (>= 4.0, < 6)
execjs (~> 2.0) execjs (~> 2.0)
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.3) 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)
builder (3.3.0) builder (3.3.0)
childprocess (5.1.0) childprocess (5.1.0)
logger (~> 1.5) logger (~> 1.5)
chroma (0.2.0) chroma (0.2.0)
chunky_png (1.4.0)
coderay (1.1.3) coderay (1.1.3)
concurrent-ruby (1.3.5) concurrent-ruby (1.3.5)
connection_pool (2.5.4) connection_pool (2.4.1)
crass (1.0.6) crass (1.0.6)
csv (3.3.5) csv (3.3.2)
date (3.4.1) date (3.4.1)
debug (1.11.0) debug (1.10.0)
irb (~> 1.10) irb (~> 1.10)
reline (>= 0.3.8) reline (>= 0.3.8)
devise (4.9.4) devise (4.9.4)
@ -111,44 +108,42 @@ GEM
railties (>= 4.1.0) railties (>= 4.1.0)
responders responders
warden (~> 1.2.3) warden (~> 1.2.3)
diff-lcs (1.6.2) diff-lcs (1.5.1)
drb (2.2.3) drb (2.2.1)
erb (5.0.2)
erubi (1.13.1) erubi (1.13.1)
et-orbi (1.2.11) et-orbi (1.2.11)
tzinfo tzinfo
execjs (2.9.1) execjs (2.9.1)
factory_bot (6.5.5) factory_bot (6.4.6)
activesupport (>= 6.1.0) activesupport (>= 5.0.0)
factory_bot_rails (6.5.1) factory_bot_rails (6.4.3)
factory_bot (~> 6.5) factory_bot (~> 6.4)
railties (>= 6.1.0) railties (>= 5.0.0)
faker (3.5.2) faker (3.5.1)
i18n (>= 1.8.11, < 2) i18n (>= 1.8.11, < 2)
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)
httparty (0.23.1) httparty (0.22.0)
csv csv
mini_mime (>= 1.0.0) mini_mime (>= 1.0.0)
multi_xml (>= 0.5.2) multi_xml (>= 0.5.2)
i18n (1.14.7) i18n (1.14.7)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
importmap-rails (2.2.2) 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.1) 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.13.2) json (2.9.1)
json-schema (5.0.1) json-schema (5.0.1)
addressable (~> 2.8) addressable (~> 2.8)
jsonapi-deserializable (0.2.0) jsonapi-deserializable (0.2.0)
@ -162,7 +157,7 @@ GEM
jsonapi-renderer (0.2.2) jsonapi-renderer (0.2.2)
jsonapi-serializable (0.3.1) jsonapi-serializable (0.3.1)
jsonapi-renderer (~> 0.2.0) jsonapi-renderer (~> 0.2.0)
language_server-protocol (3.17.0.5) language_server-protocol (3.17.0.3)
launchy (3.0.1) launchy (3.0.1)
addressable (~> 2.8) addressable (~> 2.8)
childprocess (~> 5.0) childprocess (~> 5.0)
@ -181,9 +176,8 @@ GEM
tomlrb (>= 1.3, < 2.1) tomlrb (>= 1.3, < 2.1)
with_env (= 1.1.0) with_env (= 1.1.0)
xml-simple (~> 1.1.9) xml-simple (~> 1.1.9)
lint_roller (1.1.0) logger (1.6.5)
logger (1.7.0) loofah (2.23.1)
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)
@ -194,109 +188,97 @@ GEM
marcel (1.0.4) marcel (1.0.4)
method_source (1.1.0) method_source (1.1.0)
mini_mime (1.1.5) mini_mime (1.1.5)
mini_portile2 (2.8.9) mini_portile2 (2.8.8)
minitest (5.25.5) minitest (5.25.4)
money (6.19.0) money (6.19.0)
i18n (>= 0.6.4, <= 2) i18n (>= 0.6.4, <= 2)
msgpack (1.7.5) msgpack (1.7.2)
multi_xml (0.7.1) multi_xml (0.7.1)
bigdecimal (~> 3.1) bigdecimal (~> 3.1)
net-imap (0.5.6) net-imap (0.5.2)
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 net-protocol
nio4r (2.7.4) nio4r (2.7.4)
nokogiri (1.18.9) nokogiri (1.18.1)
mini_portile2 (~> 2.8.2) mini_portile2 (~> 2.8.2)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.18.9-aarch64-linux-gnu) nokogiri (1.18.1-aarch64-linux-gnu)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.18.9-arm-linux-gnu) nokogiri (1.18.1-arm-linux-gnu)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.18.9-arm64-darwin) nokogiri (1.18.1-arm64-darwin)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.18.9-x86_64-darwin) nokogiri (1.18.1-x86_64-darwin)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.18.9-x86_64-linux-gnu) nokogiri (1.18.1-x86_64-linux-gnu)
racc (~> 1.4) racc (~> 1.4)
orm_adapter (0.5.0) orm_adapter (0.5.0)
ostruct (0.6.2) parallel (1.26.3)
parallel (1.27.0) parser (3.3.7.0)
parser (3.3.9.0)
ast (~> 2.4.1) ast (~> 2.4.1)
racc racc
pg (1.6.1) pg (1.5.9)
pg (1.6.1-aarch64-linux)
pg (1.6.1-arm64-darwin)
pg (1.6.1-x86_64-darwin)
pg (1.6.1-x86_64-linux)
pluck_to_hash (1.0.2) pluck_to_hash (1.0.2)
activerecord (>= 4.0.2) activerecord (>= 4.0.2)
activesupport (>= 4.0.2) activesupport (>= 4.0.2)
pp (0.6.2)
prettyprint
prettyprint (0.2.0)
prism (1.4.0)
pry (0.15.2) pry (0.15.2)
coderay (~> 1.1) coderay (~> 1.1)
method_source (~> 1.0) method_source (~> 1.0)
psych (5.2.6) psych (5.2.2)
date date
stringio stringio
public_suffix (6.0.1) public_suffix (6.0.1)
puma (6.6.1) 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.2.1) rack (3.1.8)
rack-cors (3.0.0) rack-cors (2.0.2)
logger rack (>= 2.0.0)
rack (>= 3.0.14) rack-session (2.0.0)
rack-session (2.1.1)
base64 (>= 0.1.0)
rack (>= 3.0.0) rack (>= 3.0.0)
rack-test (2.2.0) rack-test (2.1.0)
rack (>= 1.3) rack (>= 1.3)
rackup (2.2.1) rackup (2.2.1)
rack (>= 3) rack (>= 3)
rails (8.0.2) rails (8.0.1)
actioncable (= 8.0.2) actioncable (= 8.0.1)
actionmailbox (= 8.0.2) actionmailbox (= 8.0.1)
actionmailer (= 8.0.2) actionmailer (= 8.0.1)
actionpack (= 8.0.2) actionpack (= 8.0.1)
actiontext (= 8.0.2) actiontext (= 8.0.1)
actionview (= 8.0.2) actionview (= 8.0.1)
activejob (= 8.0.2) activejob (= 8.0.1)
activemodel (= 8.0.2) activemodel (= 8.0.1)
activerecord (= 8.0.2) activerecord (= 8.0.1)
activestorage (= 8.0.2) activestorage (= 8.0.1)
activesupport (= 8.0.2) activesupport (= 8.0.1)
bundler (>= 1.15.0) bundler (>= 1.15.0)
railties (= 8.0.2) railties (= 8.0.1)
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)
rails-html-sanitizer (1.6.2) rails-html-sanitizer (1.6.2)
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)
railties (8.0.2) railties (8.0.1)
actionpack (= 8.0.2) actionpack (= 8.0.1)
activesupport (= 8.0.2) activesupport (= 8.0.1)
irb (~> 1.13) irb (~> 1.13)
rackup (>= 1.0.0) rackup (>= 1.0.0)
rake (>= 12.2) rake (>= 12.2)
thor (~> 1.0, >= 1.2.2) thor (~> 1.0, >= 1.2.2)
zeitwerk (~> 2.6) zeitwerk (~> 2.6)
rainbow (3.1.1) rainbow (3.1.1)
rake (13.3.0) rake (13.2.1)
rdoc (6.14.2) rdoc (6.10.0)
erb
psych (>= 4.0.0) psych (>= 4.0.0)
react-rails (3.2.1) react-rails (3.2.1)
babel-transpiler (>= 0.7.0) babel-transpiler (>= 0.7.0)
@ -304,38 +286,34 @@ GEM
execjs execjs
railties (>= 3.2) railties (>= 3.2)
tilt tilt
redis (5.4.1) redis (5.3.0)
redis-client (>= 0.22.0) redis-client (>= 0.22.0)
redis-client (0.23.2) redis-client (0.22.2)
connection_pool connection_pool
regexp_parser (2.11.0) regexp_parser (2.10.0)
reline (0.6.2) reline (0.6.0)
io-console (~> 0.5) io-console (~> 0.5)
responders (3.1.1) responders (3.1.1)
actionpack (>= 5.2) actionpack (>= 5.2)
railties (>= 5.2) railties (>= 5.2)
rexml (3.3.9) rexml (3.3.9)
rqrcode (3.1.0) rspec-core (3.13.2)
chunky_png (~> 1.0)
rqrcode_core (~> 2.0)
rqrcode_core (2.0.0)
rspec-core (3.13.5)
rspec-support (~> 3.13.0) rspec-support (~> 3.13.0)
rspec-expectations (3.13.5) rspec-expectations (3.13.3)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0) rspec-support (~> 3.13.0)
rspec-mocks (3.13.5) rspec-mocks (3.13.2)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0) rspec-support (~> 3.13.0)
rspec-rails (8.0.2) rspec-rails (7.1.0)
actionpack (>= 7.2) actionpack (>= 7.0)
activesupport (>= 7.2) activesupport (>= 7.0)
railties (>= 7.2) railties (>= 7.0)
rspec-core (~> 3.13) rspec-core (~> 3.13)
rspec-expectations (~> 3.13) rspec-expectations (~> 3.13)
rspec-mocks (~> 3.13) rspec-mocks (~> 3.13)
rspec-support (~> 3.13) rspec-support (~> 3.13)
rspec-support (3.13.5) rspec-support (3.13.1)
rswag (2.16.0) rswag (2.16.0)
rswag-api (= 2.16.0) rswag-api (= 2.16.0)
rswag-specs (= 2.16.0) rswag-specs (= 2.16.0)
@ -351,50 +329,44 @@ GEM
rswag-ui (2.16.0) rswag-ui (2.16.0)
actionpack (>= 5.2, < 8.1) actionpack (>= 5.2, < 8.1)
railties (>= 5.2, < 8.1) railties (>= 5.2, < 8.1)
rubocop (1.79.2) rubocop (1.71.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.46.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.46.0) rubocop-ast (1.37.0)
parser (>= 3.3.7.2) parser (>= 3.3.1.0)
prism (~> 1.4) rubocop-factory_bot (2.26.1)
rubocop-factory_bot (2.27.1) rubocop (~> 1.61)
lint_roller (~> 1.1) rubocop-rails (2.29.1)
rubocop (~> 1.72, >= 1.72.1)
rubocop-rails (2.32.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-rspec (3.6.0) rubocop-rspec (3.4.0)
lint_roller (~> 1.1) rubocop (~> 1.61)
rubocop (~> 1.72, >= 1.72.1) rubocop-rspec_rails (2.30.0)
rubocop-rspec_rails (2.31.0) rubocop (~> 1.61)
lint_roller (~> 1.1) rubocop-rspec (~> 3, >= 3.0.1)
rubocop (~> 1.72, >= 1.72.1)
rubocop-rspec (~> 3.5)
ruby-progressbar (1.13.0) ruby-progressbar (1.13.0)
rubytree (2.1.1) rubytree (2.1.1)
json (~> 2.0, > 2.9) json (~> 2.0, > 2.9)
rubyzip (2.3.2) rubyzip (2.3.2)
securerandom (0.4.1) securerandom (0.4.1)
shoulda-matchers (6.5.0) shoulda-matchers (6.4.0)
activesupport (>= 5.2.0) activesupport (>= 5.2.0)
solid_queue (1.2.1) solid_queue (1.1.2)
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)
sprockets (4.2.1) sprockets (4.2.1)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
rack (>= 2.2.4, < 4) rack (>= 2.2.4, < 4)
@ -404,18 +376,18 @@ GEM
sprockets (>= 3.0.0) sprockets (>= 3.0.0)
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)
thor (1.4.0) thor (1.3.2)
tilt (2.4.0) tilt (2.4.0)
timeout (0.4.3) timeout (0.4.3)
tomlrb (2.0.3) tomlrb (2.0.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 (2.6.0) unicode-display_width (2.6.0)
uri (1.0.3) uri (1.0.2)
useragent (0.16.11) useragent (0.16.11)
warden (1.2.9) warden (1.2.9)
rack (>= 2.0.9) rack (>= 2.0.9)
@ -424,17 +396,13 @@ GEM
activemodel (>= 6.0.0) activemodel (>= 6.0.0)
bindex (>= 0.4.0) bindex (>= 0.4.0)
railties (>= 6.0.0) railties (>= 6.0.0)
websocket-driver (0.7.7) websocket-driver (0.7.6)
base64
websocket-extensions (>= 0.1.0) websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5) websocket-extensions (0.1.5)
wicked_pdf (2.8.2)
activesupport
ostruct
with_env (1.1.0) with_env (1.1.0)
xml-simple (1.1.9) xml-simple (1.1.9)
rexml rexml
zeitwerk (2.7.3) zeitwerk (2.7.1)
PLATFORMS PLATFORMS
aarch64-linux aarch64-linux
@ -469,8 +437,7 @@ DEPENDENCIES
rails (~> 8.0.0, >= 8.0.0) rails (~> 8.0.0, >= 8.0.0)
react-rails react-rails
redis (>= 4.0.1) redis (>= 4.0.1)
rqrcode (~> 3.1) rspec-rails (~> 7.1.0)
rspec-rails (~> 8.0.0)
rswag rswag
rubocop rubocop
rubocop-factory_bot rubocop-factory_bot
@ -485,62 +452,59 @@ DEPENDENCIES
turbo-rails turbo-rails
tzinfo-data tzinfo-data
web-console web-console
wicked_pdf (~> 2.8)
CHECKSUMS CHECKSUMS
actioncable (8.0.2) sha256=7bcce2df62e91a80143592600e16583c273e98aab50ae40a9f6a2604bb3289a0 actioncable (8.0.1) sha256=808bff2a4e3aba36f66f0cd65d7a1579ad52fb65e99304442c46051a79689d9b
actionmailbox (8.0.2) sha256=3d8fb3453913e6257da4d02004bbfa2b997dfd10672f8d990e95013983e2cedb actionmailbox (8.0.1) sha256=bbc7db779be857fb6eb5b53f313d3881cd8cda38a150c3aa25f89f2f9977b08c
actionmailer (8.0.2) sha256=b0c968b38576ec56a3dc2795931818e0aaae6a18cc9801f53f175c12d4b277d0 actionmailer (8.0.1) sha256=7b074e9590e4ec5cebd2fc91d1f9ba4c61bbd4bbd4376f731527da187cd39952
actionpack (8.0.2) sha256=93e703064f3815295ccf820f57acbca719aec836749597da9262781c9b2f4b78 actionpack (8.0.1) sha256=c764e4bfc0ad9d3505c09ef9b6fbf9eca4292793550c6b7e2ea93167181bfcba
actiontext (8.0.2) sha256=a40db32032ee2fbb479d5d69318c4284344c1cda73836fd73ffcdb917d203abf actiontext (8.0.1) sha256=f232d303e854db2098f34d7331fe493a72dc2e53dfce80fbd517c7b93d4b05b2
actionview (8.0.2) sha256=e038e1405cdfc18f04f17243da4fb8eeda3a4992f63a6d70a7281d255cf7cebb actionview (8.0.1) sha256=3005e3de5ca49ea789bf1ad46002d63fe5aa543c61c341239d3c533757e64f8a
activejob (8.0.2) sha256=b0228b45e36b1ef3a081c684e81494147e094a6baf729018756ccf125b1853ca activejob (8.0.1) sha256=95acd9a32d498d3a458efbb317f6191fb678758cde0ebb6c68f0b25e0fe3477f
activemodel (8.0.2) sha256=0ae1fb7fa1fae0699ba041a9e97702df42ea3b13f2d39f2d0fde51fca5f0656c activemodel (8.0.1) sha256=f46292fd6dcc128e18d588854298a933fd9eb22544c412b414ec02821062dc78
activerecord (8.0.2) sha256=793470b92c44e4198d0262ac60086b7822f0ea585079ad67e32a6e4c86f2d90a activerecord (8.0.1) sha256=34a7f0610660bb704f0363025d4b8d35ffe8ddc8f5b8147e0809171f724b5306
activestorage (8.0.2) sha256=f83d221e0f06ae38f2200e55490bd155c76d0add330f6e300e8646048d672977 activestorage (8.0.1) sha256=91a8f156638568fac971ff25962a617d9c58fdc0e44eb6bd0edff36aff7df205
activesupport (8.0.2) sha256=8565cddba31b900cdc17682fd66ecd020441e3eef320a9930285394e8c07a45e activesupport (8.0.1) sha256=fd5bc74641c24ac3541055c2879789198ff42adee3e39c2933289ba008912e37
acts_as_tenant (1.0.1) sha256=6944e4d64533337938a8817a6b4ff9b11189c9dcc0b1333bb89f3821a4c14c53 acts_as_tenant (1.0.1) sha256=6944e4d64533337938a8817a6b4ff9b11189c9dcc0b1333bb89f3821a4c14c53
addressable (2.8.7) sha256=462986537cf3735ab5f3c0f557f14155d778f4b43ea4f485a9deb9c8f7c58232 addressable (2.8.7) sha256=462986537cf3735ab5f3c0f557f14155d778f4b43ea4f485a9deb9c8f7c58232
annotaterb (4.18.0) sha256=a07ec5d3e8f063308dbbf17970a74155434504a3c3887511cd94fbc83c4f4294 annotaterb (4.13.0) sha256=6f472912002fefa735665b4132de47d0134ebf1efb76a7ef05f579cc4a6b2ff1
ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383 ast (2.4.2) sha256=1e280232e6a33754cde542bc5ef85520b74db2aac73ec14acef453784447cc12
babel-source (5.8.35) sha256=79ef222a9dcb867ac2efa3b0da35b4bcb15a4bfa67b6b2dcbf1e9a29104498d9 babel-source (5.8.35) sha256=79ef222a9dcb867ac2efa3b0da35b4bcb15a4bfa67b6b2dcbf1e9a29104498d9
babel-transpiler (0.7.0) sha256=4c06f4ad9e8e1cabe94f99e11df2f140bb72aca9ba067dbb49dc14d9b98d1570 babel-transpiler (0.7.0) sha256=4c06f4ad9e8e1cabe94f99e11df2f140bb72aca9ba067dbb49dc14d9b98d1570
base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b base64 (0.2.0) sha256=0f25e9b21a02a0cc0cea8ef92b2041035d39350946e8789c562b2d1a3da01507
bcrypt (3.1.20) sha256=8410f8c7b3ed54a3c00cd2456bf13917d695117f033218e2483b2e40b0784099 bcrypt (3.1.20) sha256=8410f8c7b3ed54a3c00cd2456bf13917d695117f033218e2483b2e40b0784099
benchmark (0.4.1) sha256=d4ef40037bba27f03b28013e219b950b82bace296549ec15a78016552f8d2cce benchmark (0.4.0) sha256=0f12f8c495545e3710c3e4f0480f63f06b4c842cc94cec7f33a956f5180e874a
bigdecimal (3.2.3) sha256=ffd11d1ac67a0d3b2f44aec0a6487210b3f813f363dd11f1fcccf5ba00da4e1b bigdecimal (3.1.9) sha256=2ffc742031521ad69c2dfc815a98e426a230a3d22aeac1995826a75dabfad8cc
bindex (0.8.1) sha256=7b1ecc9dc539ed8bccfc8cb4d2732046227b09d6f37582ff12e50a5047ceb17e bindex (0.8.1) sha256=7b1ecc9dc539ed8bccfc8cb4d2732046227b09d6f37582ff12e50a5047ceb17e
bootsnap (1.18.6) sha256=0ae2393c1e911e38be0f24e9173e7be570c3650128251bf06240046f84a07d00 bootsnap (1.18.4) sha256=ac4c42af397f7ee15521820198daeff545e4c360d2772c601fbdc2c07d92af55
builder (3.3.0) sha256=497918d2f9dca528fdca4b88d84e4ef4387256d984b8154e9d5d3fe5a9c8835f builder (3.3.0) sha256=497918d2f9dca528fdca4b88d84e4ef4387256d984b8154e9d5d3fe5a9c8835f
childprocess (5.1.0) sha256=9a8d484be2fd4096a0e90a0cd3e449a05bc3aa33f8ac9e4d6dcef6ac1455b6ec childprocess (5.1.0) sha256=9a8d484be2fd4096a0e90a0cd3e449a05bc3aa33f8ac9e4d6dcef6ac1455b6ec
chroma (0.2.0) sha256=64bdcd36a4765fbcd45adc64960cc153101300b4918f90ffdd89f4e2eb954b54 chroma (0.2.0) sha256=64bdcd36a4765fbcd45adc64960cc153101300b4918f90ffdd89f4e2eb954b54
chunky_png (1.4.0) sha256=89d5b31b55c0cf4da3cf89a2b4ebc3178d8abe8cbaf116a1dba95668502fdcfe
coderay (1.1.3) sha256=dc530018a4684512f8f38143cd2a096c9f02a1fc2459edcfe534787a7fc77d4b coderay (1.1.3) sha256=dc530018a4684512f8f38143cd2a096c9f02a1fc2459edcfe534787a7fc77d4b
concurrent-ruby (1.3.5) sha256=813b3e37aca6df2a21a3b9f1d497f8cbab24a2b94cab325bffe65ee0f6cbebc6 concurrent-ruby (1.3.5) sha256=813b3e37aca6df2a21a3b9f1d497f8cbab24a2b94cab325bffe65ee0f6cbebc6
connection_pool (2.5.4) sha256=e9e1922327416091f3f6542f5f4446c2a20745276b9aa796dd0bb2fd0ea1e70a connection_pool (2.4.1) sha256=0f40cf997091f1f04ff66da67eabd61a9fe0d4928b9a3645228532512fab62f4
crass (1.0.6) sha256=dc516022a56e7b3b156099abc81b6d2b08ea1ed12676ac7a5657617f012bd45d crass (1.0.6) sha256=dc516022a56e7b3b156099abc81b6d2b08ea1ed12676ac7a5657617f012bd45d
csv (3.3.5) sha256=6e5134ac3383ef728b7f02725d9872934f523cb40b961479f69cf3afa6c8e73f csv (3.3.2) sha256=6ff0c135e65e485d1864dde6c1703b60d34cc9e19bed8452834a0b28a519bd4e
date (3.4.1) sha256=bf268e14ef7158009bfeaec40b5fa3c7271906e88b196d958a89d4b408abe64f date (3.4.1) sha256=bf268e14ef7158009bfeaec40b5fa3c7271906e88b196d958a89d4b408abe64f
debug (1.11.0) sha256=1425db64cfa0130c952684e3dc974985be201dd62899bf4bbe3f8b5d6cf1aef2 debug (1.10.0) sha256=11e28ca74875979e612444104f3972bd5ffb9e79179907d7ad46dba44bd2e7a4
devise (4.9.4) sha256=920042fe5e704c548aa4eb65ebdd65980b83ffae67feb32c697206bfd975a7f8 devise (4.9.4) sha256=920042fe5e704c548aa4eb65ebdd65980b83ffae67feb32c697206bfd975a7f8
diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962 diff-lcs (1.5.1) sha256=273223dfb40685548436d32b4733aa67351769c7dea621da7d9dd4813e63ddfe
drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373 drb (2.2.1) sha256=e9d472bf785f558b96b25358bae115646da0dbfd45107ad858b0bc0d935cb340
erb (5.0.2) sha256=d30f258143d4300fb4ecf430042ac12970c9bb4b33c974a545b8f58c1ec26c0f
erubi (1.13.1) sha256=a082103b0885dbc5ecf1172fede897f9ebdb745a4b97a5e8dc63953db1ee4ad9 erubi (1.13.1) sha256=a082103b0885dbc5ecf1172fede897f9ebdb745a4b97a5e8dc63953db1ee4ad9
et-orbi (1.2.11) sha256=d26e868cc21db88280a9ec1a50aa3da5d267eb9b2037ba7b831d6c2731f5df64 et-orbi (1.2.11) sha256=d26e868cc21db88280a9ec1a50aa3da5d267eb9b2037ba7b831d6c2731f5df64
execjs (2.9.1) sha256=e8fd066f6df60c8e8fbebc32c6fb356b5212c77374e8416a9019ca4bb154dcfb execjs (2.9.1) sha256=e8fd066f6df60c8e8fbebc32c6fb356b5212c77374e8416a9019ca4bb154dcfb
factory_bot (6.5.5) sha256=ce59295daee1b4704dab8a2fee6816f513d467c6aa3bc587860767d74a66efbe factory_bot (6.4.6) sha256=1a9486ce98d318d740d8f5804b885a8265a28f326ecf2bcd4ce9fb27a71a6e04
factory_bot_rails (6.5.1) sha256=d3cc4851eae4dea8a665ec4a4516895045e710554d2b5ac9e68b94d351bc6d68 factory_bot_rails (6.4.3) sha256=ea73ceac1c0ff3dc11fff390bf2ea8a2604066525ed8ecd3b3bc2c267226dcc8
faker (3.5.2) sha256=f9a80291b2e3f259801d1dd552f0732fe04dce5d1f74e798365bc0413789c473 faker (3.5.1) sha256=1ad1fbea279d882f486059c23fe3ddb816ccd1d7052c05a45014b4450d859bfc
fugit (1.11.1) sha256=e89485e7be22226d8e9c6da411664d0660284b4b1c08cacb540f505907869868 fugit (1.11.1) sha256=e89485e7be22226d8e9c6da411664d0660284b4b1c08cacb540f505907869868
globalid (1.2.1) sha256=70bf76711871f843dbba72beb8613229a49429d1866828476f9c9d6ccc327ce9 globalid (1.2.1) sha256=70bf76711871f843dbba72beb8613229a49429d1866828476f9c9d6ccc327ce9
httparty (0.23.1) sha256=3ac1dd62f2010f6ece551716f5ceec2b2012011d89f1751917ab7f724e966b55 httparty (0.22.0) sha256=78652a5c9471cf0093d3b2083c2295c9c8f12b44c65112f1846af2b71430fa6c
i18n (1.14.7) sha256=ceba573f8138ff2c0915427f1fc5bdf4aa3ab8ae88c8ce255eb3ecf0a11a5d0f i18n (1.14.7) sha256=ceba573f8138ff2c0915427f1fc5bdf4aa3ab8ae88c8ce255eb3ecf0a11a5d0f
importmap-rails (2.2.2) sha256=729f5b1092f832780829ade1d0b46c7e53d91c556f06da7254da2977e93fe614 importmap-rails (2.1.0) sha256=9f10c67d60651a547579f448100d033df311c5d5db578301374aeb774faae741
io-console (0.8.1) sha256=1e15440a6b2f67b6ea496df7c474ed62c860ad11237f29b3bd187f054b925fcb io-console (0.8.0) sha256=cd6a9facbc69871d69b2cb8b926fc6ea7ef06f06e505e81a64f14a470fddefa2
irb (1.15.2) sha256=222f32952e278da34b58ffe45e8634bf4afc2dc7aa9da23fed67e581aa50fdba irb (1.14.3) sha256=c457f1f2f1438ae9ce5c5be3981ae2138dec7fb894c7d73777eeeb0a6c0d0752
jbuilder (2.13.0) sha256=7200a38a1c0081aa81b7a9757e7a299db75bc58cf1fd45ca7919a91627d227d6 jbuilder (2.13.0) sha256=7200a38a1c0081aa81b7a9757e7a299db75bc58cf1fd45ca7919a91627d227d6
json (2.13.2) sha256=02e1f118d434c6b230a64ffa5c8dee07e3ec96244335c392eaed39e1199dbb68 json (2.9.1) sha256=d2bdef4644052fad91c1785d48263756fe32fcac08b96a20bb15840e96550d11
json-schema (5.0.1) sha256=bef71a82c600a42594911553522e143f7634affc198ed507ef3ded2f920a74a9 json-schema (5.0.1) sha256=bef71a82c600a42594911553522e143f7634affc198ed507ef3ded2f920a74a9
jsonapi-deserializable (0.2.0) sha256=5f0ca2d3f8404cce1584a314e8a3753be32a56054c942adfe997b87e92bce147 jsonapi-deserializable (0.2.0) sha256=5f0ca2d3f8404cce1584a314e8a3753be32a56054c942adfe997b87e92bce147
jsonapi-parser (0.1.1) sha256=9ee0dc031e88fc7548d56fab66f9716d1e1c06f972b529b8c4617bc42a097020 jsonapi-parser (0.1.1) sha256=9ee0dc031e88fc7548d56fab66f9716d1e1c06f972b529b8c4617bc42a097020
@ -548,119 +512,107 @@ CHECKSUMS
jsonapi-rb (0.5.0) sha256=7922a164278f506c43d56277f6bd0800a0b603cc985f7f63fe7241b2628bd105 jsonapi-rb (0.5.0) sha256=7922a164278f506c43d56277f6bd0800a0b603cc985f7f63fe7241b2628bd105
jsonapi-renderer (0.2.2) sha256=b5c44b033d61b4abdb6500fa4ab84807ca0b36ea0e59e47a2c3ca7095a6e447b jsonapi-renderer (0.2.2) sha256=b5c44b033d61b4abdb6500fa4ab84807ca0b36ea0e59e47a2c3ca7095a6e447b
jsonapi-serializable (0.3.1) sha256=221e657677659d798e268a33ec97a83ec5ea0e4233f931358db84e88056552e9 jsonapi-serializable (0.3.1) sha256=221e657677659d798e268a33ec97a83ec5ea0e4233f931358db84e88056552e9
language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc language_server-protocol (3.17.0.3) sha256=3d5c58c02f44a20d972957a9febe386d7e7468ab3900ce6bd2b563dd910c6b3f
launchy (3.0.1) sha256=b7fa60bda0197cf57614e271a250a8ca1f6a34ab889a3c73f67ec5d57c8a7f2c launchy (3.0.1) sha256=b7fa60bda0197cf57614e271a250a8ca1f6a34ab889a3c73f67ec5d57c8a7f2c
letter_opener (1.10.0) sha256=2ff33f2e3b5c3c26d1959be54b395c086ca6d44826e8bf41a14ff96fdf1bdbb2 letter_opener (1.10.0) sha256=2ff33f2e3b5c3c26d1959be54b395c086ca6d44826e8bf41a14ff96fdf1bdbb2
letter_opener_web (3.0.0) sha256=3f391efe0e8b9b24becfab5537dfb17a5cf5eb532038f947daab58cb4b749860 letter_opener_web (3.0.0) sha256=3f391efe0e8b9b24becfab5537dfb17a5cf5eb532038f947daab58cb4b749860
license_finder (7.2.1) sha256=179ead19b64b170638b72fd16024233813673ac9d20d5ba75ae0b4444887ef14 license_finder (7.2.1) sha256=179ead19b64b170638b72fd16024233813673ac9d20d5ba75ae0b4444887ef14
lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87 logger (1.6.5) sha256=c3cfe56d01656490ddd103d38b8993d73d86296adebc5f58cefc9ec03741e56b
logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203 loofah (2.23.1) sha256=d0a07422cb3b69272e124afa914ef6d517e30d5496b7f1c1fc5b95481f13f75e
loofah (2.24.1) sha256=655a30842b70ec476410b347ab1cd2a5b92da46a19044357bbd9f401b009a337
mail (2.8.1) sha256=ec3b9fadcf2b3755c78785cb17bc9a0ca9ee9857108a64b6f5cfc9c0b5bfc9ad mail (2.8.1) sha256=ec3b9fadcf2b3755c78785cb17bc9a0ca9ee9857108a64b6f5cfc9c0b5bfc9ad
marcel (1.0.4) sha256=0d5649feb64b8f19f3d3468b96c680bae9746335d02194270287868a661516a4 marcel (1.0.4) sha256=0d5649feb64b8f19f3d3468b96c680bae9746335d02194270287868a661516a4
method_source (1.1.0) sha256=181301c9c45b731b4769bc81e8860e72f9161ad7d66dd99103c9ab84f560f5c5 method_source (1.1.0) sha256=181301c9c45b731b4769bc81e8860e72f9161ad7d66dd99103c9ab84f560f5c5
mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef
mini_portile2 (2.8.9) sha256=0cd7c7f824e010c072e33f68bc02d85a00aeb6fce05bb4819c03dfd3c140c289 mini_portile2 (2.8.8) sha256=8e47136cdac04ce81750bb6c09733b37895bf06962554e4b4056d78168d70a75
minitest (5.25.5) sha256=391b6c6cb43a4802bfb7c93af1ebe2ac66a210293f4a3fb7db36f2fc7dc2c756 minitest (5.25.4) sha256=9cf2cae25ac4dfc90c988ebc3b917f53c054978b673273da1bd20bcb0778f947
money (6.19.0) sha256=ec936fa1e42f2783719241ed9fd52725d0efa628f928feea1eb5c37d5de7daf3 money (6.19.0) sha256=ec936fa1e42f2783719241ed9fd52725d0efa628f928feea1eb5c37d5de7daf3
msgpack (1.7.5) sha256=ffb04979f51e6406823c03abe50e1da2c825c55a37dee138518cdd09d9d3aea8 msgpack (1.7.2) sha256=59ab62fd8a4d0dfbde45009f87eb6f158ab2628a7c48886b0256f175166baaa8
multi_xml (0.7.1) sha256=4fce100c68af588ff91b8ba90a0bb3f0466f06c909f21a32f4962059140ba61b multi_xml (0.7.1) sha256=4fce100c68af588ff91b8ba90a0bb3f0466f06c909f21a32f4962059140ba61b
net-imap (0.5.6) sha256=1ede8048ee688a14206060bf37a716d18cb6ea00855f6c9b15daee97ee51fbe5 net-imap (0.5.2) sha256=e955b55e539712518bdb4eb747c6514f9c8d56ec4eb8eb573a82a6885a9effea
net-pop (0.1.2) sha256=848b4e982013c15b2f0382792268763b748cce91c9e91e36b0f27ed26420dff3 net-pop (0.1.2) sha256=848b4e982013c15b2f0382792268763b748cce91c9e91e36b0f27ed26420dff3
net-protocol (0.2.2) sha256=aa73e0cba6a125369de9837b8d8ef82a61849360eba0521900e2c3713aa162a8 net-protocol (0.2.2) sha256=aa73e0cba6a125369de9837b8d8ef82a61849360eba0521900e2c3713aa162a8
net-smtp (0.5.1) sha256=ed96a0af63c524fceb4b29b0d352195c30d82dd916a42f03c62a3a70e5b70736 net-smtp (0.5.0) sha256=5fc0415e6ea1cc0b3dfea7270438ec22b278ca8d524986a3ae4e5ae8d087b42a
nio4r (2.7.4) sha256=d95dee68e0bb251b8ff90ac3423a511e3b784124e5db7ff5f4813a220ae73ca9 nio4r (2.7.4) sha256=d95dee68e0bb251b8ff90ac3423a511e3b784124e5db7ff5f4813a220ae73ca9
nokogiri (1.18.9) sha256=ac5a7d93fd0e3cef388800b037407890882413feccca79eb0272a2715a82fa33 nokogiri (1.18.1) sha256=df18be7e96c34736b6abfdeda80c6e845134fb9afe2fe5d4fbc1cf1f89c68475
nokogiri (1.18.9-aarch64-linux-gnu) sha256=5bcfdf7aa8d1056a7ad5e52e1adffc64ef53d12d0724fbc6f458a3af1a4b9e32 nokogiri (1.18.1-aarch64-linux-gnu) sha256=35837013800e34342fcbaca305f8c49231f6bd4f779bfa23fe7b4686ae82d5b8
nokogiri (1.18.9-arm-linux-gnu) sha256=fe611ae65880e445a9c0f650d52327db239f3488626df4173c05beafd161d46e nokogiri (1.18.1-arm-linux-gnu) sha256=3b873fd6b0cd1ad7c77e87af701075bdfd14c9a6b2f2965c5e00ed29a5627a37
nokogiri (1.18.9-arm64-darwin) sha256=eea3f1f06463ff6309d3ff5b88033c4948d0da1ab3cc0a3a24f63c4d4a763979 nokogiri (1.18.1-arm64-darwin) sha256=d75193f284c899d225943a8944479faedd995a7573ddd5c8308ffbdf2ec55204
nokogiri (1.18.9-x86_64-darwin) sha256=e0d2deb03d3d7af8016e8c9df5ff4a7d692159cefb135cbb6a4109f265652348 nokogiri (1.18.1-x86_64-darwin) sha256=d94e3aa6483577495fc8969d6b4b5c075840ce6b1ab09636a6d4177ad171051d
nokogiri (1.18.9-x86_64-linux-gnu) sha256=b52f5defedc53d14f71eeaaf990da66b077e1918a2e13088b6a96d0230f44360 nokogiri (1.18.1-x86_64-linux-gnu) sha256=e516cf16ccde67ed4cc595a2621ca5ddd42562ecb24928914b0045a20a41620e
orm_adapter (0.5.0) sha256=aa5d0be5d540cbb46d3a93e88061f4ece6a25f6e97d6a47122beb84fe595e9b9 orm_adapter (0.5.0) sha256=aa5d0be5d540cbb46d3a93e88061f4ece6a25f6e97d6a47122beb84fe595e9b9
ostruct (0.6.2) sha256=6d7302a299e400a2c248d6ce0dad18fc3a5714e8096facc25ffd0c54ee57cfc0 parallel (1.26.3) sha256=d86babb7a2b814be9f4b81587bf0b6ce2da7d45969fab24d8ae4bf2bb4d4c7ef
parallel (1.27.0) sha256=4ac151e1806b755fb4e2dc2332cbf0e54f2e24ba821ff2d3dcf86bf6dc4ae130 parser (3.3.7.0) sha256=7449011771e3e7881297859b849de26a6f4fccd515bece9520a87e7d2116119b
parser (3.3.9.0) sha256=94d6929354b1a6e3e1f89d79d4d302cc8f5aa814431a6c9c7e0623335d7687f2 pg (1.5.9) sha256=761efbdf73b66516f0c26fcbe6515dc7500c3f0aa1a1b853feae245433c64fdc
pg (1.6.1) sha256=e210a75e5f702954537e73bb82f90dfbe0c6d9273c018cd0e93e779181028e6b
pg (1.6.1-aarch64-linux) sha256=2dc057589c4df67240bd52c68303a00a91299329bc7573b7447faee42331e214
pg (1.6.1-arm64-darwin) sha256=3b502915de30cf5983d62aabae927cb7c3628b0ca46cdf3a6b888af4ff7f42b3
pg (1.6.1-x86_64-darwin) sha256=c8930170622c39ee24b318a2265655b5f8f34628444ee00e4ae44068865309f7
pg (1.6.1-x86_64-linux) sha256=6ac0d5c8efafc3f22a7eca2a264300037598fabe27a88e5029bc0e6d90caeb1f
pluck_to_hash (1.0.2) sha256=1599906239716f98262a41493dd7d4cb72e8d83ad3d76d666deacfc5de50a47e pluck_to_hash (1.0.2) sha256=1599906239716f98262a41493dd7d4cb72e8d83ad3d76d666deacfc5de50a47e
pp (0.6.2) sha256=947ec3120c6f92195f8ee8aa25a7b2c5297bb106d83b41baa02983686577b6ff
prettyprint (0.2.0) sha256=2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193
prism (1.4.0) sha256=dc0e3e00e93160213dc2a65519d9002a4a1e7b962db57d444cf1a71565bb703e
pry (0.15.2) sha256=12d54b8640d3fa29c9211dd4ffb08f3fd8bf7a4fd9b5a73ce5b59c8709385b6b pry (0.15.2) sha256=12d54b8640d3fa29c9211dd4ffb08f3fd8bf7a4fd9b5a73ce5b59c8709385b6b
psych (5.2.6) sha256=814328aa5dcb6d604d32126a20bc1cbcf05521a5b49dbb1a8b30a07e580f316e psych (5.2.2) sha256=a4a9477c85d3e858086c38cf64e7096abe40d1b1eed248b01020dec0ff9906ab
public_suffix (6.0.1) sha256=61d44e1cab5cbbbe5b31068481cf16976dd0dc1b6b07bd95617ef8c5e3e00c6f public_suffix (6.0.1) sha256=61d44e1cab5cbbbe5b31068481cf16976dd0dc1b6b07bd95617ef8c5e3e00c6f
puma (6.6.1) sha256=b9b56e4a4ea75d1bfa6d9e1972ee2c9f43d0883f011826d914e8e37b3694ea1e puma (6.5.0) sha256=94d1b75cab7f356d52e4f1b17b9040a090889b341dbeee6ee3703f441dc189f2
raabro (1.4.0) sha256=d4fa9ff5172391edb92b242eed8be802d1934b1464061ae5e70d80962c5da882 raabro (1.4.0) sha256=d4fa9ff5172391edb92b242eed8be802d1934b1464061ae5e70d80962c5da882
racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
rack (3.2.1) sha256=30af3f7e5ec21b0d14d822cf24446048dba5f651b617c7e97405b604f20a9e33 rack (3.1.8) sha256=d3fbcbca43dc2b43c9c6d7dfbac01667ae58643c42cea10013d0da970218a1b1
rack-cors (3.0.0) sha256=7b95be61db39606906b61b83bd7203fa802b0ceaaad8fcb2fef39e097bf53f68 rack-cors (2.0.2) sha256=415d4e1599891760c5dc9ef0349c7fecdf94f7c6a03e75b2e7c2b54b82adda1b
rack-session (2.1.1) sha256=0b6dc07dea7e4b583f58a48e8b806d4c9f1c6c9214ebc202ec94562cbea2e4e9 rack-session (2.0.0) sha256=db04b2063e180369192a9046b4559af311990af38c6a93d4c600cee4eb6d4e81
rack-test (2.2.0) sha256=005a36692c306ac0b4a9350355ee080fd09ddef1148a5f8b2ac636c720f5c463 rack-test (2.1.0) sha256=0c61fc61904049d691922ea4bb99e28004ed3f43aa5cfd495024cc345f125dfb
rackup (2.2.1) sha256=f737191fd5c5b348b7f0a4412a3b86383f88c43e13b8217b63d4c8d90b9e798d rackup (2.2.1) sha256=f737191fd5c5b348b7f0a4412a3b86383f88c43e13b8217b63d4c8d90b9e798d
rails (8.0.2) sha256=fdfaa5a83ec0388e02864e88d515959caedc88053b5f701c4deb1652d8f164c6 rails (8.0.1) sha256=c86f4cd7834a67c1e5d04a77d35c88a5f56a20e2022ec416fa52c1af2cdc9491
rails-dom-testing (2.3.0) sha256=8acc7953a7b911ca44588bf08737bc16719f431a1cc3091a292bca7317925c1d rails-dom-testing (2.2.0) sha256=e515712e48df1f687a1d7c380fd7b07b8558faa26464474da64183a7426fa93b
rails-html-sanitizer (1.6.2) sha256=35fce2ca8242da8775c83b6ba9c1bcaad6751d9eb73c1abaa8403475ab89a560 rails-html-sanitizer (1.6.2) sha256=35fce2ca8242da8775c83b6ba9c1bcaad6751d9eb73c1abaa8403475ab89a560
railties (8.0.2) sha256=0d7c3f40c49ba74980f1bac1d4bb153a9331c5ee8a9631d89c7bf79db82e5cf9 railties (8.0.1) sha256=8f653c6b1b0721b553045bd0deda1f22074b9ddc2209526e6f7285fcf607ac51
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
rake (13.3.0) sha256=96f5092d786ff412c62fde76f793cc0541bd84d2eb579caa529aa8a059934493 rake (13.2.1) sha256=46cb38dae65d7d74b6020a4ac9d48afed8eb8149c040eccf0523bec91907059d
rdoc (6.14.2) sha256=9fdd44df130f856ae70cc9a264dfd659b9b40de369b16581f4ab746e42439226 rdoc (6.10.0) sha256=db665021883ac9df3ba29cdf71aece960749888db1bf9615b4a584cfa3fa3eda
react-rails (3.2.1) sha256=2235db0b240517596b1cb3e26177ab5bc64d3a56579b0415ee242b1691f81f64 react-rails (3.2.1) sha256=2235db0b240517596b1cb3e26177ab5bc64d3a56579b0415ee242b1691f81f64
redis (5.4.1) sha256=b5e675b57ad22b15c9bcc765d5ac26f60b675408af916d31527af9bd5a81faae redis (5.3.0) sha256=6bf810c5ae889187f0c45f77db503310980310afa57cf1640d57f419ccda72b1
redis-client (0.23.2) sha256=e33bab6682c8155cfef95e6dd296936bb9c2981a89fb578ace27a076fa2836fa redis-client (0.22.2) sha256=31fee4b7cf04109b227327fabeaaf1fc5b652cf48a186a03bc607e40767bacc0
regexp_parser (2.11.0) sha256=d9dd78b475d18893ce3da55ea1a913499b75f26180a3463e9233d7e419c0cd40 regexp_parser (2.10.0) sha256=cb6f0ddde88772cd64bff1dbbf68df66d376043fe2e66a9ef77fcb1b0c548c61
reline (0.6.2) sha256=1dad26a6008872d59c8e05244b119347c9f2ddaf4a53dce97856cd5f30a02846 reline (0.6.0) sha256=57620375dcbe56ec09bac7192bfb7460c716bbf0054dc94345ecaa5438e539d2
responders (3.1.1) sha256=92f2a87e09028347368639cfb468f5fefa745cb0dc2377ef060db1cdd79a341a responders (3.1.1) sha256=92f2a87e09028347368639cfb468f5fefa745cb0dc2377ef060db1cdd79a341a
rexml (3.3.9) sha256=d71875b85299f341edf47d44df0212e7658cbdf35aeb69cefdb63f57af3137c9 rexml (3.3.9) sha256=d71875b85299f341edf47d44df0212e7658cbdf35aeb69cefdb63f57af3137c9
rqrcode (3.1.0) sha256=e2d5996375f6e9a013823c289ed575dbea678b8e0388574302c1fac563f098af rspec-core (3.13.2) sha256=94fbda6e4738e478f1c7532b7cc241272fcdc8b9eac03a97338b1122e4573300
rqrcode_core (2.0.0) sha256=1e40b823ab57a96482a417fff5dd5c33645a00cea6ef5d9e342fecc5ef91d9ab rspec-expectations (3.13.3) sha256=0e6b5af59b900147698ea0ff80456c4f2e69cac4394fbd392fbd1ca561f66c58
rspec-core (3.13.5) sha256=ab3f682897c6131c67f9a17cfee5022a597f283aebe654d329a565f9937a4fa3 rspec-mocks (3.13.2) sha256=2327335def0e1665325a9b617e3af9ae20272741d80ac550336309a7c59abdef
rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836 rspec-rails (7.1.0) sha256=94585b69c4086ca79afae5cc8d2c5e314f6ad32a88c927f9c065b99596e3ee47
rspec-mocks (3.13.5) sha256=e4338a6f285ada9fe56f5893f5457783af8194f5d08884d17a87321d5195ea81 rspec-support (3.13.1) sha256=48877d4f15b772b7538f3693c22225f2eda490ba65a0515c4e7cf6f2f17de70f
rspec-rails (8.0.2) sha256=113139a53f5d068d4f48d1c29ad5f982013ed9b0daa69d7f7b266eda5d433ace
rspec-support (3.13.5) sha256=add745af535dd14b18f1209ab41ef987fdfad12786176b6a3b3619b9a7279fbf
rswag (2.16.0) sha256=f07ce41548b9bb51464c38bc7b95af22fee84b90f2d1197a515a623906353086 rswag (2.16.0) sha256=f07ce41548b9bb51464c38bc7b95af22fee84b90f2d1197a515a623906353086
rswag-api (2.16.0) sha256=b653f7bd92e98be18b01ab4525d88950d7b0960e293a99f856b9efcee3ae6074 rswag-api (2.16.0) sha256=b653f7bd92e98be18b01ab4525d88950d7b0960e293a99f856b9efcee3ae6074
rswag-specs (2.16.0) sha256=8ba26085c408b0bd2ed21dc8015c80f417c7d34c63720ab7133c2549b5bd2a91 rswag-specs (2.16.0) sha256=8ba26085c408b0bd2ed21dc8015c80f417c7d34c63720ab7133c2549b5bd2a91
rswag-ui (2.16.0) sha256=a1f49e927dceda92e6e6e7c1000f1e217ee66c565f69e28131dc98b33cd3a04f rswag-ui (2.16.0) sha256=a1f49e927dceda92e6e6e7c1000f1e217ee66c565f69e28131dc98b33cd3a04f
rubocop (1.79.2) sha256=d3f42a7d197952c2a163719c5462fea827710a435b18bfb7070c6eedd2e90391 rubocop (1.71.0) sha256=e19679efd447346ac476122313d3788ae23c38214790bcf660e984c747608bf0
rubocop-ast (1.46.0) sha256=0da7f6ad5b98614f89b74f11873c191059c823eae07d6ffd40a42a3338f2232b rubocop-ast (1.37.0) sha256=9513ac88aaf113d04b52912533ffe46475de1362d4aa41141b51b2455827c080
rubocop-factory_bot (2.27.1) sha256=9d744b5916778c1848e5fe6777cc69855bd96548853554ec239ba9961b8573fe rubocop-factory_bot (2.26.1) sha256=8de13cd4edcee5ca800f255188167ecef8dbfc3d1fae9f15734e9d2e755392aa
rubocop-rails (2.32.0) sha256=9fcc623c8722fe71e835e99c4a18b740b5b0d3fb69915d7f0777f00794b30490 rubocop-rails (2.29.1) sha256=41c2fcf48d5d62f4a5f574d5f1c97bbaf4cba88ee367936c98b3422d047b17aa
rubocop-rspec (3.6.0) sha256=c0e4205871776727e54dee9cc91af5fd74578001551ba40e1fe1a1ab4b404479 rubocop-rspec (3.4.0) sha256=8721c13b6a8c9530a7ac481cea9423022f946fcf72428bda8289f8b57e4d4885
rubocop-rspec_rails (2.31.0) sha256=775375e18a26a1184a812ef3054b79d218e85601b9ae897f38f8be24dddf1f45 rubocop-rspec_rails (2.30.0) sha256=888112e83f9d7ef7ad2397e9d69a0b9614a4bae24f072c399804a180f80c4c46
ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33 ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33
rubytree (2.1.1) sha256=4925016356a81730e982f1f8c3b5f8da461f18906c77d238bad4c4ba896abd41 rubytree (2.1.1) sha256=4925016356a81730e982f1f8c3b5f8da461f18906c77d238bad4c4ba896abd41
rubyzip (2.3.2) sha256=3f57e3935dc2255c414484fbf8d673b4909d8a6a57007ed754dde39342d2373f rubyzip (2.3.2) sha256=3f57e3935dc2255c414484fbf8d673b4909d8a6a57007ed754dde39342d2373f
securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1 securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1
shoulda-matchers (6.5.0) sha256=ef6b572b2bed1ac4aba6ab2c5ff345a24b6d055a93a3d1c3bfc86d9d499e3f44 shoulda-matchers (6.4.0) sha256=9055bb7f4bb342125fb860809798855c630e05ef5e75837b3168b8e6ee1608b0
solid_queue (1.2.1) sha256=7976b3690a08080ef63d1b11281f0b77398f7697dbeda0e2c5532682639d4b15 solid_queue (1.1.2) sha256=178c9396d1cf0dac595c7508da90ddb397d25848ca007b5c5ed48e6ac6fc360c
sprockets (4.2.1) sha256=951b13dd2f2fcae840a7184722689a803e0ff9d2702d902bd844b196da773f97 sprockets (4.2.1) sha256=951b13dd2f2fcae840a7184722689a803e0ff9d2702d902bd844b196da773f97
sprockets-rails (3.5.2) sha256=a9e88e6ce9f8c912d349aa5401509165ec42326baf9e942a85de4b76dbc4119e sprockets-rails (3.5.2) sha256=a9e88e6ce9f8c912d349aa5401509165ec42326baf9e942a85de4b76dbc4119e
stimulus-rails (1.3.4) sha256=765676ffa1f33af64ce026d26b48e8ffb2e0b94e0f50e9119e11d6107d67cb06 stimulus-rails (1.3.4) sha256=765676ffa1f33af64ce026d26b48e8ffb2e0b94e0f50e9119e11d6107d67cb06
stringio (3.1.7) sha256=5b78b7cb242a315fb4fca61a8255d62ec438f58da2b90be66048546ade4507fa stringio (3.1.2) sha256=204f1828f85cdb39d57cac4abc6dc44b04505a223f131587f2e20ae3729ba131
thor (1.4.0) sha256=8763e822ccb0f1d7bee88cde131b19a65606657b847cc7b7b4b82e772bcd8a3d thor (1.3.2) sha256=eef0293b9e24158ccad7ab383ae83534b7ad4ed99c09f96f1a6b036550abbeda
tilt (2.4.0) sha256=df74f29a451daed26591a85e8e0cebb198892cb75b6573394303acda273fba4d tilt (2.4.0) sha256=df74f29a451daed26591a85e8e0cebb198892cb75b6573394303acda273fba4d
timeout (0.4.3) sha256=9509f079b2b55fe4236d79633bd75e34c1c1e7e3fb4b56cb5fda61f80a0fe30e timeout (0.4.3) sha256=9509f079b2b55fe4236d79633bd75e34c1c1e7e3fb4b56cb5fda61f80a0fe30e
tomlrb (2.0.3) sha256=c2736acf24919f793334023a4ff396c0647d93fce702a73c9d348deaa815d4f7 tomlrb (2.0.3) sha256=c2736acf24919f793334023a4ff396c0647d93fce702a73c9d348deaa815d4f7
turbo-rails (2.0.16) sha256=d24e1b60f0c575b3549ecda967e5391027143f8220d837ed792c8d48ea0ea38d turbo-rails (2.0.11) sha256=fc47674736372780abd2a4dc0d84bef242f5ca156a457cd7fa6308291e397fcf
tzinfo (2.0.6) sha256=8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b tzinfo (2.0.6) sha256=8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b
unicode-display_width (2.6.0) sha256=12279874bba6d5e4d2728cef814b19197dbb10d7a7837a869bab65da943b7f5a unicode-display_width (2.6.0) sha256=12279874bba6d5e4d2728cef814b19197dbb10d7a7837a869bab65da943b7f5a
uri (1.0.3) sha256=e9f2244608eea2f7bc357d954c65c910ce0399ca5e18a7a29207ac22d8767011 uri (1.0.2) sha256=b303504ceb7e5905771fa7fa14b649652fa949df18b5880d69cfb12494791e27
useragent (0.16.11) sha256=700e6413ad4bb954bb63547fa098dddf7b0ebe75b40cc6f93b8d54255b173844 useragent (0.16.11) sha256=700e6413ad4bb954bb63547fa098dddf7b0ebe75b40cc6f93b8d54255b173844
warden (1.2.9) sha256=46684f885d35a69dbb883deabf85a222c8e427a957804719e143005df7a1efd0 warden (1.2.9) sha256=46684f885d35a69dbb883deabf85a222c8e427a957804719e143005df7a1efd0
web-console (4.2.1) sha256=e7bcf37a10ea2b4ec4281649d1cee461b32232d0a447e82c786e6841fd22fe20 web-console (4.2.1) sha256=e7bcf37a10ea2b4ec4281649d1cee461b32232d0a447e82c786e6841fd22fe20
websocket-driver (0.7.7) sha256=056d99f2cd545712cfb1291650fde7478e4f2661dc1db6a0fa3b966231a146b4 websocket-driver (0.7.6) sha256=f69400be7bc197879726ad8e6f5869a61823147372fd8928836a53c2c741d0db
websocket-extensions (0.1.5) sha256=1c6ba63092cda343eb53fc657110c71c754c56484aad42578495227d717a8241 websocket-extensions (0.1.5) sha256=1c6ba63092cda343eb53fc657110c71c754c56484aad42578495227d717a8241
wicked_pdf (2.8.2) sha256=648d9b0cec5a34adbc9bbf809731052a78119e2d6d323b9e4aa1383e1d683824
with_env (1.1.0) sha256=50b3e4f0a6cda8f90d8a6bd87a6261f6c381429abafb161c4c69ad4a0cd0b6e4 with_env (1.1.0) sha256=50b3e4f0a6cda8f90d8a6bd87a6261f6c381429abafb161c4c69ad4a0cd0b6e4
xml-simple (1.1.9) sha256=d21131e519c86f1a5bc2b6d2d57d46e6998e47f18ed249b25cad86433dbd695d xml-simple (1.1.9) sha256=d21131e519c86f1a5bc2b6d2d57d46e6998e47f18ed249b25cad86433dbd695d
zeitwerk (2.7.3) sha256=b2e86b4a9b57d26ba68a15230dcc7fe6f040f06831ce64417b0621ad96ba3e85 zeitwerk (2.7.1) sha256=0945986050e4907140895378e74df1fe882a2271ed087cc6c6d6b00d415a2756
RUBY VERSION RUBY VERSION
ruby 3.4.3p32 ruby 3.4.1p0
BUNDLED WITH BUNDLED WITH
2.6.1 2.6.1

View File

@ -57,12 +57,6 @@ The backend service will seed the database with fake data. It's worth noting tha
The backend, frontend and workers have hot-reloading enabled, so changes made to the codebase should be reflected in the application on the next request. The backend, frontend and workers have hot-reloading enabled, so changes made to the codebase should be reflected in the application on the next request.
Please, include this in your `/etc/hosts` file:
```
127.0.0.1 libre-wedding-planner.app.localhost
```
Once all containers have started, visit http://libre-wedding-planner.app.localhost/default/dashboard to load the application. Once all containers have started, visit http://libre-wedding-planner.app.localhost/default/dashboard to load the application.
## Multitenancy ## Multitenancy

View File

@ -5,31 +5,21 @@
require 'csv' require 'csv'
class GuestsController < ApplicationController class GuestsController < ApplicationController
GUEST_PARAMS = { only: %i[id name status], include: { group: { only: %i[id name] } } }.freeze
skip_before_action :authenticate_user!, only: :update
def index def index
render json: Guest.includes(:group) render json: Guest.includes(:group)
.left_joins(:group) .left_joins(:group)
.order('groups.name' => :asc, invitation_id: :asc, name: :asc) .order('groups.name' => :asc, name: :asc)
.as_json(GUEST_PARAMS) .as_json(only: %i[id name status], include: { group: { only: %i[id name] } })
end end
def create def create
guest = Guest.create!(guest_params) Guest.create!(guest_params)
render json: guest.as_json(GUEST_PARAMS), status: :created render json: {}, status: :created
end end
def update def update
guest = Guest.find(params[:id]) Guest.find(params[:id]).update!(guest_params)
guest.update!(guest_params) render json: {}, status: :ok
if !user_signed_in? && guest.saved_change_to_status?
AdminMailer.with(guest_id: guest.id).attendance_change_email.deliver_later
end
render json: guest.as_json(GUEST_PARAMS), status: :ok
end end
def destroy def destroy
@ -40,6 +30,6 @@ class GuestsController < ApplicationController
private private
def guest_params def guest_params
user_signed_in? ? params.expect(guest: %i[name group_id status]) : params.expect(guest: %i[status]) params.expect(guest: %i[name group_id status])
end end
end end

View File

@ -1,76 +0,0 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# frozen_string_literal: true
class InvitationsController < ApplicationController
skip_before_action :authenticate_user!, only: :show
def index
@invitations = Invitation.includes(:guests).all
respond_to do |format|
format.json do
render json: @invitations.as_json(
only: :id,
include: { guests: { only: %i[id name] } }
)
end
format.pdf do
pdf_html = ActionController::Base.new.render_to_string(
template: 'invitations/sheet',
layout: 'pdf',
locals: { invitations: @invitations }
)
pdf = WickedPdf.new.pdf_from_string(pdf_html)
send_data pdf, filename: "invitations_#{Time.current.strftime('%Y%m%d_%H%M%S')}.pdf"
end
end
end
def email
AdminMailer.with(wedding_id: ActsAsTenant.current_tenant.id).invitations_pdf_email.deliver_later
head :ok
end
def sheet; end
def show
invitation = Invitation.includes(:guests).find(params[:id])
if invitation
render json: invitation, only: :id, include: { guests: { only: %i[id name status] } }, status: :ok
else
render json: { error: 'Invitation not found' }, status: :not_found
end
end
def create
invitation = Invitation.create
if invitation.persisted?
render json: invitation, only: :id, status: :created
else
render json: { errors: invitation.errors.full_messages }, status: :unprocessable_entity
end
end
def update
invitation = Invitation.find(params[:id])
if invitation.update(guest_ids: params[:invitation][:guest_ids])
render json: invitation, only: :id, include: { guests: { only: %i[id name] } }, status: :ok
else
render json: { errors: invitation.errors.full_messages }, status: :unprocessable_entity
end
end
def destroy
invitation = Invitation.find(params[:id])
if invitation.destroy
head :no_content
else
render json: { errors: invitation.errors.full_messages }, status: :unprocessable_entity
end
end
end

View File

@ -9,10 +9,10 @@ class TablesArrangementsController < ApplicationController
render json: TablesArrangement render json: TablesArrangement
.order(valid: :desc) .order(valid: :desc)
.order(discomfort: :asc) .order(discomfort: :asc)
.select(:id, :name, :discomfort, :status, :progress) .select(:id, :name, :discomfort)
.select("digest = '#{current_digest}'::uuid OR discomfort IS NULL as valid") .select("digest = '#{current_digest}'::uuid as valid")
.limit(20) .limit(20)
.as_json(only: %i[id name discomfort valid status progress]) .as_json(only: %i[id name discomfort valid])
end end
def show def show
@ -25,10 +25,7 @@ class TablesArrangementsController < ApplicationController
end end
def create def create
ActiveRecord::Base.transaction do TableSimulatorJob.perform_later(current_tenant.id)
tables_arrangement = TablesArrangement.create!(status: :not_started)
TableSimulatorJob.perform_later(current_tenant.id, tables_arrangement.id)
end
render json: {}, status: :created render json: {}, status: :created
end end

View File

@ -1,25 +0,0 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# frozen_string_literal: true
class WebsitesController < ApplicationController
skip_before_action :authenticate_user!, only: :show
def show
render json: current_tenant.website.as_json(only: %i[content]) || {}, status: :ok
end
def update
ActiveRecord::Base.transaction do
website = current_tenant.website || current_tenant.create_website
website.update!(website_params)
render json: website.as_json(only: %i[content]), status: :ok
end
end
private
def website_params
params.expect(website: [:content])
end
end

View File

@ -8,35 +8,16 @@ class TableSimulatorJob < ApplicationJob
MIN_PER_TABLE = 8 MIN_PER_TABLE = 8
MAX_PER_TABLE = 10 MAX_PER_TABLE = 10
def perform(wedding_id, tables_arrangement_id) # rubocop:disable Metrics/MethodLength def perform(wedding_id)
Rails.logger.info "Starting table simulation #{tables_arrangement_id} for wedding #{wedding_id}"
ActsAsTenant.with_tenant(Wedding.find(wedding_id)) do ActsAsTenant.with_tenant(Wedding.find(wedding_id)) do
engine = VNS::Engine.new engine = VNS::Engine.new
engine.add_optimization(Tables::Swap) engine.add_perturbation(Tables::Swap)
engine.add_optimization(Tables::Shift) engine.add_perturbation(Tables::Shift)
tables_arrangement = TablesArrangement.find(tables_arrangement_id)
initial_solution = Tables::Distribution.new(
min_per_table: MIN_PER_TABLE,
max_per_table: MAX_PER_TABLE,
tables_arrangement_id:
)
initial_solution = Tables::Distribution.new(min_per_table: MIN_PER_TABLE, max_per_table: MAX_PER_TABLE)
initial_solution.random_distribution(Guest.potential.shuffle) initial_solution.random_distribution(Guest.potential.shuffle)
initial_solution.save!
engine.notify_progress do |current_progress|
tables_arrangement.update_columns(status: :in_progress, progress: current_progress)
end
engine.on_better_solution do |better_solution|
better_solution.save!
tables_arrangement.update_columns(discomfort: better_solution.discomfort) # TODO: remove?
end
engine.initial_solution = initial_solution engine.initial_solution = initial_solution
engine.target_function(&:discomfort) engine.target_function(&:discomfort)
@ -44,8 +25,6 @@ class TableSimulatorJob < ApplicationJob
best_solution = engine.run best_solution = engine.run
best_solution.save! best_solution.save!
tables_arrangement.update_columns(status: :completed)
end end
end end
end end

View File

@ -1,45 +0,0 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# frozen_string_literal: true
class AdminMailer < ApplicationMailer
def attendance_change_email
@guest = Guest.find(params[:guest_id])
ActsAsTenant.with_tenant(@guest.wedding) do
mail(
to: recipients,
subject: I18n.t(
'admin_mailer.attendance_change_email.subject',
name: @guest.name,
status: I18n.t("active_record.attributes.guest/status.#{@guest.status}")
)
)
end
end
def invitations_pdf_email
ActsAsTenant.with_tenant(Wedding.find(params[:wedding_id])) do
invitations = Invitation.includes(:guests).all
pdf_html = ActionController::Base.new.render_to_string(
template: 'invitations/sheet',
layout: 'pdf',
locals: { invitations: }
)
pdf = WickedPdf.new.pdf_from_string(pdf_html)
attachments["invitations_#{Time.current.strftime('%Y%m%d_%H%M%S')}.pdf"] = pdf
mail(
to: recipients,
subject: I18n.t('admin_mailer.invitations_pdf_email.subject')
)
end
end
private
def recipients
ActsAsTenant.current_tenant.users.pluck(:email)
end
end

View File

@ -3,16 +3,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class ApplicationMailer < ActionMailer::Base class ApplicationMailer < ActionMailer::Base
class << self default from: 'from@example.com'
private
def default_from
File.read('/run/secrets/smtp_user_name').strip
rescue Errno::ENOENT
'development@example.com'
end
end
default from: default_from
layout 'mailer' layout 'mailer'
end end

View File

@ -20,7 +20,7 @@
# #
# Foreign Keys # Foreign Keys
# #
# fk_rails_... (wedding_id => weddings.id) ON DELETE => cascade # fk_rails_... (wedding_id => weddings.id)
# #
class Expense < ApplicationRecord class Expense < ApplicationRecord
acts_as_tenant :wedding acts_as_tenant :wedding

View File

@ -25,7 +25,7 @@
# Foreign Keys # Foreign Keys
# #
# fk_rails_... (parent_id => groups.id) # fk_rails_... (parent_id => groups.id)
# fk_rails_... (wedding_id => weddings.id) ON DELETE => cascade # fk_rails_... (wedding_id => weddings.id)
# #
class Group < ApplicationRecord class Group < ApplicationRecord
acts_as_tenant :wedding acts_as_tenant :wedding

View File

@ -6,32 +6,28 @@
# #
# Table name: guests # Table name: guests
# #
# id :uuid not null, primary key # id :uuid not null, primary key
# name :string # name :string
# phone :string # phone :string
# status :integer default("considered") # status :integer default("considered")
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# group_id :uuid # group_id :uuid
# invitation_id :uuid # wedding_id :uuid not null
# wedding_id :uuid not null
# #
# Indexes # Indexes
# #
# index_guests_on_group_id (group_id) # index_guests_on_group_id (group_id)
# index_guests_on_invitation_id (invitation_id) # index_guests_on_wedding_id (wedding_id)
# index_guests_on_wedding_id (wedding_id)
# #
# Foreign Keys # Foreign Keys
# #
# fk_rails_... (group_id => groups.id) # fk_rails_... (group_id => groups.id)
# fk_rails_... (invitation_id => invitations.id) # fk_rails_... (wedding_id => weddings.id)
# fk_rails_... (wedding_id => weddings.id) ON DELETE => cascade
# #
class Guest < ApplicationRecord class Guest < ApplicationRecord
acts_as_tenant :wedding acts_as_tenant :wedding
belongs_to :group, optional: true belongs_to :group, optional: true
belongs_to :invitation, optional: true
enum :status, { enum :status, {
considered: 0, considered: 0,

View File

@ -1,29 +0,0 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# frozen_string_literal: true
# == Schema Information
#
# Table name: invitations
#
# id :uuid not null, primary key
# created_at :datetime not null
# updated_at :datetime not null
# wedding_id :uuid not null
#
# Indexes
#
# index_invitations_on_wedding_id (wedding_id)
#
# Foreign Keys
#
# fk_rails_... (wedding_id => weddings.id) ON DELETE => cascade
#
class Invitation < ApplicationRecord
acts_as_tenant :wedding
has_many :guests, dependent: :nullify
def url
"#{Rails.application.routes.url_helpers.root_url(slug: ActsAsTenant.current_tenant.slug)}/site/invitation/#{id}"
end
end

View File

@ -24,10 +24,10 @@
# #
# fk_rails_... (guest_id => guests.id) # fk_rails_... (guest_id => guests.id)
# fk_rails_... (tables_arrangement_id => tables_arrangements.id) ON DELETE => cascade # fk_rails_... (tables_arrangement_id => tables_arrangements.id) ON DELETE => cascade
# fk_rails_... (wedding_id => weddings.id) ON DELETE => cascade # fk_rails_... (wedding_id => weddings.id)
# #
class Seat < ApplicationRecord class Seat < ApplicationRecord
acts_as_tenant :wedding acts_as_tenant :wedding
belongs_to :guest belongs_to :guest
belongs_to :tables_arrangement belongs_to :table_arrangement
end end

View File

@ -10,8 +10,6 @@
# digest :uuid not null # digest :uuid not null
# discomfort :integer # discomfort :integer
# name :string not null # name :string not null
# progress :float default(0.0), not null
# status :string default("complete"), not null
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# wedding_id :uuid not null # wedding_id :uuid not null
@ -22,7 +20,7 @@
# #
# Foreign Keys # Foreign Keys
# #
# fk_rails_... (wedding_id => weddings.id) ON DELETE => cascade # fk_rails_... (wedding_id => weddings.id)
# #
class TablesArrangement < ApplicationRecord class TablesArrangement < ApplicationRecord
acts_as_tenant :wedding acts_as_tenant :wedding

View File

@ -32,7 +32,7 @@
# #
# Foreign Keys # Foreign Keys
# #
# fk_rails_... (wedding_id => weddings.id) ON DELETE => cascade # fk_rails_... (wedding_id => weddings.id)
# #
class User < ApplicationRecord class User < ApplicationRecord
acts_as_tenant :wedding acts_as_tenant :wedding

View File

@ -1,25 +0,0 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# frozen_string_literal: true
# == Schema Information
#
# Table name: websites
#
# id :bigint not null, primary key
# content :text
# created_at :datetime not null
# updated_at :datetime not null
# wedding_id :uuid not null
#
# Indexes
#
# index_websites_on_wedding_id (wedding_id)
#
# Foreign Keys
#
# fk_rails_... (wedding_id => weddings.id)
#
class Website < ApplicationRecord
belongs_to :wedding
end

View File

@ -21,8 +21,4 @@ class Wedding < ApplicationRecord
validates :slug, presence: true, uniqueness: true, format: { with: /\A#{SLUG_REGEX}\z/ } validates :slug, presence: true, uniqueness: true, format: { with: /\A#{SLUG_REGEX}\z/ }
has_many :guests, dependent: :delete_all has_many :guests, dependent: :delete_all
has_many :groups, dependent: :delete_all
has_many :invitations, dependent: :delete_all
has_many :users, dependent: :delete_all
has_one :website, dependent: :destroy
end end

View File

@ -5,12 +5,16 @@
class SerializableGuest < JSONAPI::Serializable::Resource class SerializableGuest < JSONAPI::Serializable::Resource
type 'guest' type 'guest'
attributes :id, :status attributes :id, :group_id, :status
attribute :name do attribute :name do
@object.name @object.name
end end
attribute :group_name do
@object.group.name
end
attribute :status do attribute :status do
@object.status.capitalize @object.status.capitalize
end end

View File

@ -16,7 +16,6 @@ class AffinityGroupsHierarchy < Array
end end
discomforts discomforts
invitation_counts
freeze freeze
end end
@ -55,16 +54,8 @@ class AffinityGroupsHierarchy < Array
Rational(dist, dist + 1) Rational(dist, dist + 1)
end end
def guest_count(invitation_id)
@invitation_counts[invitation_id] || 0
end
private private
def invitation_counts
@invitation_counts = Guest.where.not(invitation_id: nil).group(:invitation_id).count
end
def discomforts def discomforts
@discomforts ||= GroupAffinity.pluck(:group_a_id, :group_b_id, @discomforts ||= GroupAffinity.pluck(:group_a_id, :group_b_id,
:discomfort).each_with_object({}) do |(id_a, id_b, discomfort), acc| :discomfort).each_with_object({}) do |(id_a, id_b, discomfort), acc|

View File

@ -15,7 +15,7 @@ module Tables
end end
def breakdown def breakdown
@breakdown ||= { table_size_penalty:, cohesion_penalty:, invitations_penalty: } @breakdown ||= { table_size_penalty:, cohesion_penalty: }
end end
private private
@ -39,12 +39,6 @@ module Tables
10 * (cohesion_discomfort * 1.0 / table.size) 10 * (cohesion_discomfort * 1.0 / table.size)
end end
def invitations_penalty
2 * table.map(&:invitation_id)
.tally
.sum { |invitation_id, guests_in_table| hierarchy.guest_count(invitation_id) - guests_in_table }
end
# #
# Calculates the discomfort of the table based on the cohesion of the guests. The total discomfort # Calculates the discomfort of the table based on the cohesion of the guests. The total discomfort
# is calculated as the sum of the discomfort of each pair of guests. The discomfort of a pair of # is calculated as the sum of the discomfort of each pair of guests. The discomfort of a pair of

View File

@ -12,21 +12,19 @@ module Tables
end end
end end
attr_accessor :tables, :min_per_table, :max_per_table, :hierarchy, :tables_arrangement_id attr_accessor :tables, :min_per_table, :max_per_table, :hierarchy
def initialize(min_per_table:, max_per_table:, tables_arrangement_id:, hierarchy: AffinityGroupsHierarchy.new) def initialize(min_per_table:, max_per_table:)
@min_per_table = min_per_table @min_per_table = min_per_table
@max_per_table = max_per_table @max_per_table = max_per_table
@hierarchy = hierarchy @hierarchy = AffinityGroupsHierarchy.new
@tables = [] @tables = []
@tables_arrangement_id = tables_arrangement_id
end end
def random_distribution(people, random: Random.new) def random_distribution(people)
min_tables = (people.count * 1.0 / @max_per_table).ceil min_tables = (people.count * 1.0 / @max_per_table).ceil
max_tables = (people.count * 1.0 / @min_per_table).ceil max_tables = (people.count * 1.0 / @min_per_table).ceil
table_size = random.rand(min_tables..max_tables) @tables = people.in_groups(rand(min_tables..max_tables), false)
@tables = people.in_groups(table_size, false)
.map { |group| Table.new(group) } .map { |group| Table.new(group) }
.each { |table| table.min_per_table = @min_per_table } .each { |table| table.min_per_table = @min_per_table }
.each { |table| table.max_per_table = @max_per_table } .each { |table| table.max_per_table = @max_per_table }
@ -43,23 +41,14 @@ module Tables
end end
def deep_dup def deep_dup
self.class.new( self.class.new(min_per_table: @min_per_table, max_per_table: @max_per_table).tap do |new_distribution|
min_per_table: @min_per_table,
max_per_table: @max_per_table,
hierarchy: @hierarchy,
tables_arrangement_id: @tables_arrangement_id
).tap do |new_distribution|
new_distribution.tables = @tables.map(&:dup) new_distribution.tables = @tables.map(&:dup)
end end
end end
def save! def save!
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
arrangement = TablesArrangement.find(tables_arrangement_id) arrangement = TablesArrangement.create!
self.tables_arrangement_id = arrangement.id
arrangement.seats.delete_all
records_to_store = [] records_to_store = []

View File

@ -1,33 +0,0 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# frozen_string_literal: true
module Tables
class WheelSwap
private attr_reader :initial_solution
def initialize(initial_solution)
@initial_solution = initial_solution
end
def call(size = 1)
Rails.logger.debug { "WheelSwap with size: #{size}" }
new_solution = @initial_solution.deep_dup
selected_guests = []
size.times do
selected_guests += new_solution.tables.map(&:pop)
end
selected_guests.shuffle!
tables = new_solution.tables.cycle
tables.next << selected_guests.pop while selected_guests.any?
new_solution.tables.each(&:reset)
new_solution
end
end
end

View File

@ -4,8 +4,6 @@
module VNS module VNS
class Engine class Engine
PERTURBATION_SIZES = [1, 1, 1, 2, 2, 3].freeze
ITERATIONS = 50
class << self class << self
def sequence(elements) def sequence(elements)
elements = elements.to_a elements = elements.to_a
@ -13,95 +11,45 @@ module VNS
end end
end end
def initialize
@perturbations = Set.new
end
def target_function(&function) def target_function(&function)
@target_function = function @target_function = function
end end
def add_optimization(klass)
@optimizations ||= Set.new
@optimizations << klass
end
def add_perturbation(klass) def add_perturbation(klass)
@perturbations ||= Set.new
@perturbations << klass @perturbations << klass
end end
def notify_progress(&block)
@progress_notifier = block
end
def on_better_solution(&block)
@better_solution_notifier = block
end
attr_writer :initial_solution attr_writer :initial_solution
def run def run
check_preconditions! raise 'No target function defined' unless @target_function
raise 'No perturbations defined' unless @perturbations
raise 'No initial solution defined' unless @initial_solution
@current_solution = @initial_solution @best_solution = @initial_solution
@best_score = @target_function.call(@current_solution) @best_score = @target_function.call(@best_solution)
run_all_optimizations self.class.sequence(@perturbations).each do |perturbation|
optimize(perturbation)
@progress_notifier&.call(Rational(1, ITERATIONS + 1))
best_solution = @current_solution
(1..ITERATIONS).each do |iteration|
@current_solution = Tables::WheelSwap.new(best_solution).call(PERTURBATION_SIZES.sample)
@best_score = @target_function.call(@current_solution)
Rails.logger.debug { "After perturbation: #{@best_score}" }
run_all_optimizations
@progress_notifier&.call(Rational(iteration + 1, ITERATIONS + 1))
next unless best_solution.discomfort > @current_solution.discomfort
best_solution = @current_solution
@better_solution_notifier&.call(best_solution)
Rails.logger.debug do
"Found better solution after perturbation optimization: #{@current_solution.discomfort}"
end
end end
best_solution @best_solution
end end
private private
def check_preconditions! def optimize(perturbation_klass)
raise 'No target function defined' unless @target_function
raise 'No optimizations defined' unless @optimizations
raise 'No initial solution defined' unless @initial_solution
end
def run_all_optimizations
self.class.sequence(@optimizations).each do |optimization|
optimize(optimization)
Rails.logger.debug { "Finished optimization phase: #{optimization}" }
end
Rails.logger.debug { 'Finished all optimization phases' }
end
def optimize(optimization_klass)
loop do loop do
optimized = false optimized = false
optimization_klass.new(@current_solution).each do |alternative_solution| perturbation_klass.new(@best_solution).each do |alternative_solution|
score = @target_function.call(alternative_solution) score = @target_function.call(alternative_solution)
next if score >= @best_score next if score >= @best_score
@current_solution = alternative_solution.deep_dup @best_solution = alternative_solution.deep_dup
@best_score = score @best_score = score
optimized = true optimized = true
Rails.logger.debug { "[#{optimization_klass}] Found better solution with score: #{score}" }
break break
end end

View File

@ -1,17 +0,0 @@
<%# Copyright (C) 2024-2025 LibreWeddingPlanner contributors %>
<p><%= I18n.t('admin_mailer.greeting') %>,</p>
<p>
<%= I18n.t('admin_mailer.attendance_change_email.paragraph_1', name: @guest.name) %>
</p>
<ul>
<li>
<strong><%= I18n.t("active_record.attributes.guest.status") %>:</strong> <%= I18n.t("active_record.attributes.guest/status.#{@guest.status}") %>
</li>
</ul>
<p>
<%= I18n.t("admin_mailer.attendance_change_email.notify_on_updates") %>
</p>

View File

@ -1,9 +0,0 @@
<%# Copyright (C) 2024-2025 LibreWeddingPlanner contributors %>
<%= I18n.t('admin_mailer.greeting') %>,
<%= I18n.t('admin_mailer.attendance_change_email.paragraph_1', name: @guest.name) %>
- <%= I18n.t("active_record.attributes.guest.status") %>: <%= I18n.t("active_record.attributes.guest/status.#{@guest.status}") %>
<%= I18n.t("admin_mailer.attendance_change_email.notify_on_updates") %>

View File

@ -1,7 +0,0 @@
<%# Copyright (C) 2024-2025 LibreWeddingPlanner contributors %>
<p><%= I18n.t('admin_mailer.greeting') %>,</p>
<p>
<%= I18n.t('admin_mailer.invitations_pdf_email.paragraph_1') %>
</p>

View File

@ -1,5 +0,0 @@
<%# Copyright (C) 2024-2025 LibreWeddingPlanner contributors %>
<%= I18n.t('admin_mailer.greeting') %>,
<%= I18n.t('admin_mailer.invitations_pdf_email.paragraph_1') %>

View File

@ -1,33 +0,0 @@
<%# Copyright (C) 2024-2025 LibreWeddingPlanner contributors %>
<% invitations.each_slice(4) do |invitation_group| %>
<table style="width: 100%; border-collapse: separate; border-spacing: 0 20px; margin-bottom: 40px;">
<% invitation_group.each do |invitation| %>
<tr>
<td style="width: 270px; height: 270px; text-align: center; vertical-align: middle; padding: 10px;">
<%= image_tag(RQRCode::QRCode.new(invitation.url).as_png(
bit_depth: 1,
border_modules: 4,
color_mode: ChunkyPNG::COLOR_GRAYSCALE,
color: "black",
file: nil,
fill: "white",
module_px_size: 6,
resize_exactly_to: false,
resize_gte_to: false,
size: 250
).to_data_url)
%>
</td>
<td style="vertical-align: middle; padding: 10px;">
<ul style="margin: 0; padding-left: 20px;">
<% invitation.guests.each do |guest| %>
<%= content_tag(:li, guest.name) %>
<% end %>
</ul>
</td>
</tr>
<% end %>
</table>
<div style="page-break-after: always;"></div>
<% end %>

View File

@ -1,12 +0,0 @@
<%# Copyright (C) 2024-2025 LibreWeddingPlanner contributors %>
<!doctype html>
<html>
<head>
<meta charset='utf-8' />
</head>
<div id="content">
<%= yield %>
</div>
</body>
</html>

View File

@ -3,5 +3,4 @@
require_relative "../config/environment" require_relative "../config/environment"
require "solid_queue/cli" require "solid_queue/cli"
SolidQueue.logger = ActiveSupport::Logger.new($stdout)
SolidQueue::Cli.start(ARGV) SolidQueue::Cli.start(ARGV)

View File

@ -79,5 +79,4 @@ Rails.application.configure do
config.action_controller.raise_on_missing_callback_actions = true config.action_controller.raise_on_missing_callback_actions = true
config.hosts << "libre-wedding-planner.app.localhost" config.hosts << "libre-wedding-planner.app.localhost"
Rails.application.routes.default_url_options[:host] = "libre-wedding-planner.app.localhost"
end end

View File

@ -72,20 +72,6 @@ Rails.application.configure do
# config.active_job.queue_name_prefix = "wedding_planner_production" # config.active_job.queue_name_prefix = "wedding_planner_production"
config.action_mailer.perform_caching = false config.action_mailer.perform_caching = false
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = begin
{
address: File.read("/run/secrets/smtp_address").strip,
port: File.read("/run/secrets/smtp_port").strip.to_i,
user_name: File.read("/run/secrets/smtp_user_name").strip,
password: File.read("/run/secrets/smtp_password").strip,
authentication: File.read("/run/secrets/smtp_authentication").strip.to_sym,
tls: true
}
rescue Errno::ENOENT
{}
end
# Ignore bad email addresses and do not raise email delivery errors. # Ignore bad email addresses and do not raise email delivery errors.
# Set this to true and configure the email server for immediate delivery to raise delivery errors. # Set this to true and configure the email server for immediate delivery to raise delivery errors.
@ -108,7 +94,6 @@ Rails.application.configure do
# ] # ]
config.hosts << "app.libreweddingplanner.org" config.hosts << "app.libreweddingplanner.org"
Rails.application.routes.default_url_options[:host] = "app.libreweddingplanner.org"
# 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" } }

View File

@ -2,8 +2,4 @@
ActsAsTenant.configure do |config| ActsAsTenant.configure do |config|
config.require_tenant = !Rails.env.test? config.require_tenant = !Rails.env.test?
end end
Rails.application.console do
ActsAsTenant.current_tenant = Wedding.first
end

View File

@ -10,7 +10,7 @@ Rswag::Ui.configure do |c|
# (under openapi_root) as JSON or YAML endpoints, then the list below should # (under openapi_root) as JSON or YAML endpoints, then the list below should
# correspond to the relative paths for those endpoints. # correspond to the relative paths for those endpoints.
c.openapi_endpoint '/api/api-docs/v1/swagger.yaml', 'API V1 Docs' c.swagger_endpoint '/api/api-docs/v1/swagger.yaml', 'API V1 Docs'
# Add Basic Auth in case your API is private # Add Basic Auth in case your API is private
# c.basic_auth_enabled = true # c.basic_auth_enabled = true

View File

@ -10,10 +10,4 @@ class Set
def to_table def to_table
Tables::Table.new(self) Tables::Table.new(self)
end end
def pop
element = self.to_a.sample
self.delete(element)
element
end
end end

View File

@ -1,32 +0,0 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# WickedPDF Global Configuration
#
# Use this to set up shared configuration options for your entire application.
# Any of the configuration options shown here can also be applied to single
# models by passing arguments to the `render :pdf` call.
#
# To learn more, check out the README:
#
# https://github.com/mileszs/wicked_pdf/blob/master/README.md
WickedPdf.configure do |config|
# Path to the wkhtmltopdf executable: This usually isn't needed if using
# one of the wkhtmltopdf-binary family of gems.
# config.exe_path = '/usr/local/bin/wkhtmltopdf'
# or
# config.exe_path = Gem.bin_path('wkhtmltopdf-binary', 'wkhtmltopdf')
# Needed for wkhtmltopdf 0.12.6+ to use many wicked_pdf asset helpers
# config.enable_local_file_access = true
# Layout file to be used for all PDFs
# (but can be overridden in `render :pdf` calls)
# config.layout = 'pdf.html'
# Using wkhtmltopdf without an X server can be achieved by enabling the
# 'use_xvfb' flag. This will wrap all wkhtmltopdf commands around the
# 'xvfb-run' command, in order to simulate an X server.
#
# config.use_xvfb = true
end

View File

@ -1,22 +1,31 @@
# Files in the config/locales directory are used for internationalization and
# are automatically loaded by Rails. If you want to use locales other than
# English, add the necessary files in this directory.
#
# To use the locales, use `I18n.t`:
#
# I18n.t "hello"
#
# In views, this is aliased to just `t`:
#
# <%= t("hello") %>
#
# To use a different locale, set it with `I18n.locale`:
#
# I18n.locale = :es
#
# This would use the information in config/locales/es.yml.
#
# To learn more about the API, please read the Rails Internationalization guide
# at https://guides.rubyonrails.org/i18n.html.
#
# Be aware that YAML interprets the following case-insensitive strings as
# booleans: `true`, `false`, `on`, `off`, `yes`, `no`. Therefore, these strings
# must be quoted to be interpreted as strings. For example:
#
# en:
# "yes": yup
# enabled: "ON"
en: en:
hello: "Hello world" hello: "Hello world"
active_record:
attributes:
guest:
status: Status
guest/status:
considered: Considered
invited: Invited
confirmed: Confirmed
declined: Declined
tentative: Tentative
admin_mailer:
greeting: "Dear user"
attendance_change_email:
subject: "%{name} has changed their attendance status: %{status}"
paragraph_1: "The guest %{name} has changed their attendance for the wedding."
notify_on_updates: "You will be notified of any further changes to their attendance status."
invitations_pdf_email:
subject: "Your wedding invitations are ready"
paragraph_1: "Your wedding invitations are ready. Please, find them attached to this email."

View File

@ -37,14 +37,8 @@ Rails.application.routes.draw do
resources :expenses, only: %i[index create update destroy] do resources :expenses, only: %i[index create update destroy] do
get :summary, on: :collection get :summary, on: :collection
end end
resource :website, only: [:show, :update]
resources :tables_arrangements, only: %i[index show create] resources :tables_arrangements, only: %i[index show create]
resources :summary, only: :index resources :summary, only: :index
resources :invitations, only: %i[show index create update destroy] do
post :email, on: :collection
end
root to: redirect("/%{slug}") root to: redirect("/%{slug}")
end end

View File

@ -1,10 +0,0 @@
class CreateInvitations < ActiveRecord::Migration[8.0]
def change
create_table :invitations, id: :uuid do |t|
t.references :wedding, null: false, foreign_key: { on_delete: :cascade }, type: :uuid
t.timestamps
end
add_reference :guests, :invitation, foreign_key: true, type: :uuid
end
end

View File

@ -1,8 +0,0 @@
class FixCascadingWeddingDeletion < ActiveRecord::Migration[8.0]
def change
[:expenses, :groups, :guests, :seats, :tables_arrangements, :users].each do |table|
remove_foreign_key table, :weddings, column: :wedding_id
add_foreign_key table, :weddings, on_delete: :cascade
end
end
end

View File

@ -1,10 +0,0 @@
class CreateWebsites < ActiveRecord::Migration[8.0]
def change
create_table :websites do |t|
t.text :content
t.references :wedding, null: false, foreign_key: true, type: :uuid
t.timestamps
end
end
end

View File

@ -1,5 +0,0 @@
class AddStatusColumnToTablesArrangements < ActiveRecord::Migration[8.0]
def change
add_column :tables_arrangements, :status, :string, default: :complete, null: false
end
end

View File

@ -1,5 +0,0 @@
class AddProgressToTablesArrangements < ActiveRecord::Migration[8.0]
def change
add_column :tables_arrangements, :progress, :float, default: 0, null: false
end
end

36
db/schema.rb generated
View File

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.0].define(version: 2025_09_08_145119) do ActiveRecord::Schema[8.0].define(version: 2025_01_26_091823) 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 "pg_catalog.plpgsql" enable_extension "pg_catalog.plpgsql"
@ -63,19 +63,10 @@ ActiveRecord::Schema[8.0].define(version: 2025_09_08_145119) do
t.integer "status", default: 0 t.integer "status", default: 0
t.string "name" t.string "name"
t.uuid "wedding_id", null: false t.uuid "wedding_id", null: false
t.uuid "invitation_id"
t.index ["group_id"], name: "index_guests_on_group_id" t.index ["group_id"], name: "index_guests_on_group_id"
t.index ["invitation_id"], name: "index_guests_on_invitation_id"
t.index ["wedding_id"], name: "index_guests_on_wedding_id" t.index ["wedding_id"], name: "index_guests_on_wedding_id"
end end
create_table "invitations", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.uuid "wedding_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["wedding_id"], name: "index_invitations_on_wedding_id"
end
create_table "seats", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| create_table "seats", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.uuid "guest_id", null: false t.uuid "guest_id", null: false
t.uuid "tables_arrangement_id", null: false t.uuid "tables_arrangement_id", null: false
@ -216,8 +207,6 @@ ActiveRecord::Schema[8.0].define(version: 2025_09_08_145119) do
t.string "name", null: false t.string "name", null: false
t.uuid "wedding_id", null: false t.uuid "wedding_id", null: false
t.uuid "digest", default: -> { "gen_random_uuid()" }, null: false t.uuid "digest", default: -> { "gen_random_uuid()" }, null: false
t.string "status", default: "complete", null: false
t.float "progress", default: 0.0, null: false
t.index ["wedding_id"], name: "index_tables_arrangements_on_wedding_id" t.index ["wedding_id"], name: "index_tables_arrangements_on_wedding_id"
end end
@ -243,14 +232,6 @@ ActiveRecord::Schema[8.0].define(version: 2025_09_08_145119) do
t.index ["wedding_id"], name: "index_users_on_wedding_id" t.index ["wedding_id"], name: "index_users_on_wedding_id"
end end
create_table "websites", force: :cascade do |t|
t.text "content"
t.uuid "wedding_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["wedding_id"], name: "index_websites_on_wedding_id"
end
create_table "weddings", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| create_table "weddings", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "slug", null: false t.string "slug", null: false
t.datetime "created_at", null: false t.datetime "created_at", null: false
@ -258,25 +239,22 @@ ActiveRecord::Schema[8.0].define(version: 2025_09_08_145119) do
t.index ["slug"], name: "index_weddings_on_slug", unique: true t.index ["slug"], name: "index_weddings_on_slug", unique: true
end end
add_foreign_key "expenses", "weddings", on_delete: :cascade add_foreign_key "expenses", "weddings"
add_foreign_key "group_affinities", "groups", column: "group_a_id" add_foreign_key "group_affinities", "groups", column: "group_a_id"
add_foreign_key "group_affinities", "groups", column: "group_b_id" add_foreign_key "group_affinities", "groups", column: "group_b_id"
add_foreign_key "groups", "groups", column: "parent_id" add_foreign_key "groups", "groups", column: "parent_id"
add_foreign_key "groups", "weddings", on_delete: :cascade add_foreign_key "groups", "weddings"
add_foreign_key "guests", "groups" add_foreign_key "guests", "groups"
add_foreign_key "guests", "invitations" add_foreign_key "guests", "weddings"
add_foreign_key "guests", "weddings", on_delete: :cascade
add_foreign_key "invitations", "weddings", on_delete: :cascade
add_foreign_key "seats", "guests" add_foreign_key "seats", "guests"
add_foreign_key "seats", "tables_arrangements", on_delete: :cascade add_foreign_key "seats", "tables_arrangements", on_delete: :cascade
add_foreign_key "seats", "weddings", on_delete: :cascade add_foreign_key "seats", "weddings"
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
add_foreign_key "solid_queue_claimed_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade add_foreign_key "solid_queue_claimed_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade
add_foreign_key "solid_queue_failed_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade add_foreign_key "solid_queue_failed_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade
add_foreign_key "solid_queue_ready_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade add_foreign_key "solid_queue_ready_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade
add_foreign_key "solid_queue_recurring_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade add_foreign_key "solid_queue_recurring_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade
add_foreign_key "solid_queue_scheduled_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade add_foreign_key "solid_queue_scheduled_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade
add_foreign_key "tables_arrangements", "weddings", on_delete: :cascade add_foreign_key "tables_arrangements", "weddings"
add_foreign_key "users", "weddings", on_delete: :cascade add_foreign_key "users", "weddings"
add_foreign_key "websites", "weddings"
end end

View File

@ -1,33 +1,33 @@
# frozen_string_literal: true
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors # Copyright (C) 2024-2025 LibreWeddingPlanner contributors
NUMBER_OF_GUESTS = 200 NUMBER_OF_GUESTS = 50
ActsAsTenant.without_tenant do ActsAsTenant.without_tenant do
GroupAffinity.delete_all TablesArrangement.delete_all
Expense.delete_all
Guest.delete_all
Group.delete_all
Wedding.delete_all Wedding.delete_all
end end
wedding = Wedding.create!(slug: :default) wedding = Wedding.create!(slug: :default)
ActsAsTenant.with_tenant(wedding) do ActsAsTenant.with_tenant(wedding) do
[ Expense.create!(name: 'Photographer', amount: 3000, pricing_type: 'fixed')
{ name: 'Photographer', amount: 3000, pricing_type: 'fixed' }, Expense.create!(name: 'Country house', amount: 6000, pricing_type: 'fixed')
{ name: 'Country house', amount: 6000, pricing_type: 'fixed' }, Expense.create!(name: 'Catering', amount: 200, pricing_type: 'per_person')
{ name: 'Catering', amount: 200, pricing_type: 'per_person' }, Expense.create!(name: 'Flowers', amount: 500, pricing_type: 'fixed')
{ name: 'Flowers', amount: 500, pricing_type: 'fixed' }, Expense.create!(name: 'Band', amount: 1000, pricing_type: 'fixed')
{ name: 'Band', amount: 1000, pricing_type: 'fixed' }, Expense.create!(name: 'Wedding planner', amount: 2000, pricing_type: 'fixed')
{ name: 'Wedding planner', amount: 2000, pricing_type: 'fixed' }, Expense.create!(name: 'Dress', amount: 1000, pricing_type: 'fixed')
{ name: 'Dress', amount: 1000, pricing_type: 'fixed' }, Expense.create!(name: 'Suit', amount: 500, pricing_type: 'fixed')
{ name: 'Suit', amount: 500, pricing_type: 'fixed' }, Expense.create!(name: 'Rings', amount: 1000, pricing_type: 'fixed')
{ name: 'Rings', amount: 1000, pricing_type: 'fixed' }, Expense.create!(name: 'Makeup', amount: 200, pricing_type: 'fixed')
{ name: 'Makeup', amount: 200, pricing_type: 'fixed' }, Expense.create!(name: 'Hair', amount: 200, pricing_type: 'fixed')
{ name: 'Hair', amount: 200, pricing_type: 'fixed' }, Expense.create!(name: 'Transportation', amount: 3000, pricing_type: 'fixed')
{ name: 'Transportation', amount: 3000, pricing_type: 'fixed' }, Expense.create!(name: 'Invitations', amount: 200, pricing_type: 'fixed')
{ name: 'Invitations', amount: 200, pricing_type: 'fixed' }, Expense.create!(name: 'Cake', amount: 500, pricing_type: 'fixed')
{ name: 'Cake', amount: 500, pricing_type: 'fixed' }
].then { Expense.insert_all!(it) }
Group.create!(name: "Jim's guests", icon: 'pi pi-heart').tap do |parent| Group.create!(name: "Jim's guests", icon: 'pi pi-heart').tap do |parent|
parent.children.create!(name: "Jim's family", icon: 'pi pi-users').tap do |family| parent.children.create!(name: "Jim's family", icon: 'pi pi-users').tap do |family|
@ -61,36 +61,18 @@ ActsAsTenant.with_tenant(wedding) do
groups = Group.all groups = Group.all
NUMBER_OF_GUESTS.times.map do |i| NUMBER_OF_GUESTS.times do
{ Guest.create!(
name: Faker::Name.name, name: Faker::Name.name,
phone: Faker::PhoneNumber.cell_phone, phone: Faker::PhoneNumber.cell_phone,
group_id: groups.sample.id, group: groups.sample,
status: Guest.statuses.keys.sample, status: Guest.statuses.keys.sample
} )
end.then { Guest.insert_all!(it) }
Group.includes(:guests).each do |group|
guests = group.guests.potential.to_a
while guests.any?
invitation = Invitation.create!
guests.shift(rand(1..3)).each do |guest|
guest.update!(invitation:)
end
guests.shift(1) if rand < 0.3 # Leave a percentage of guests without an invitation
end
end end
# TODO: Clean up invitations with no guests ActiveJob.perform_all_later(3.times.map { TableSimulatorJob.new(wedding.id) })
3.times { TablesArrangement.create! } 'red'.paint.palette.triad(as: :hex).zip(Group.roots).each { |(color, group)| group.update!(color: color.paint.desaturate(40)) }
.map { |arrangement| TableSimulatorJob.new(wedding.id, arrangement.id) }
.then { |jobs| ActiveJob.perform_all_later }
"red".dup.paint.palette.triad(as: :hex).zip(Group.roots).each { |(color, group)| group.update!(color: color.paint.desaturate(40)) }
Group.roots.each(&:colorize_children) Group.roots.each(&:colorize_children)
@ -98,6 +80,6 @@ ActsAsTenant.with_tenant(wedding) do
email: 'development@example.com', email: 'development@example.com',
confirmed_at: Time.zone.now, confirmed_at: Time.zone.now,
password: 'supersecretpassword', password: 'supersecretpassword',
password_confirmation: 'supersecretpassword' password_confirmation: 'supersecretpassword',
) )
end end

51
lib/tasks/ractors.rake Normal file
View File

@ -0,0 +1,51 @@
namespace :ractors do
desc 'Run the Ractors example'
task run: :environment do
list = (1..20)
number_of_ractors = Concurrent.processor_count
ractors = number_of_ractors.times.map do |i|
Ractor.new(name: "Ractor #{i}") do
loop do
number = Ractor.receive
puts "Processing #{number} in #{Ractor.current.name}"
Ractor.yield "eureka" if number == 17
end
end
end.cycle
list.each do |i|
ractors.next.send(i)
end
puts "all enqueued"
binding.pry
Ractor.select(*ractors)
# binding.pry
# number_of_ractors.times do |i|
# r = Ractor.new(name: "Ractor #{i}") do
# Ractor.receive.each do |i|
# puts "Processing index #{i} in #{Ractor.current.name}"
# end
# end
# r.send((i * 250)...((i + 1) * 250))
# end
# ractor = Ractor.new do
# loop do
# puts 'I will receive a message soon'
# msg = Ractor.receive
# puts 'I will return a tripple of what I receive'
# Ractor.yield(msg * 3)
# end
# end
# heavy_calculation.send(15)
# puts heavy_calculation.take
end
end

View File

@ -1,37 +0,0 @@
# frozen_string_literal: true
namespace :vns do
desc 'Benchmarks the efficiency of the VNS implementation'
task benchmark: :environment do
ActsAsTenant.with_tenant(Wedding.first) do
Rails.logger.info "There are #{Guest.potential.count} potential guests"
engine = VNS::Engine.new
engine.add_optimization(Tables::Swap)
engine.add_optimization(Tables::Shift)
hierarchy = AffinityGroupsHierarchy.new
initial_solution = Tables::Distribution.new(min_per_table: 8, max_per_table: 10, hierarchy:)
random = Random.new(561_163)
initial_solution.random_distribution(Guest.potential.shuffle(random:), random:)
engine.initial_solution = initial_solution
engine.target_function(&:discomfort)
engine.notify_progress do |current_progress|
Rails.logger.info "Progress: #{(current_progress * 100.0).round(2)}%"
end
engine.on_better_solution do |better_solution|
Rails.logger.info "New best solution found with discomfort: #{better_solution.discomfort}"
end
solution = Rails.benchmark('VNS Benchmarking') { engine.run }
Rails.logger.info "Best solution found with discomfort: #{solution.discomfort}"
end
end
end

View File

@ -12,11 +12,6 @@ server {
proxy_set_header Host $http_host; proxy_set_header Host $http_host;
} }
location /jobs/ {
proxy_pass http://backend:3000/jobs/;
proxy_set_header Host $http_host;
}
location /captcha/v2/media/ { location /captcha/v2/media/ {
proxy_pass http://libre-captcha:8888/v2/media/; proxy_pass http://libre-captcha:8888/v2/media/;
proxy_set_header Host $http_host; proxy_set_header Host $http_host;

View File

@ -1,9 +0,0 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# frozen_string_literal: true
FactoryBot.define do
factory :invitation do
wedding
end
end

View File

@ -1,10 +0,0 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# frozen_string_literal: true
FactoryBot.define do
factory :website do
content { 'MyText' }
wedding { nil }
end
end

View File

@ -1,9 +0,0 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Invitation do
pending "add some examples to (or delete) #{__FILE__}"
end

View File

@ -1,9 +0,0 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Website do
it { is_expected.to belong_to(:wedding) }
end

View File

@ -19,8 +19,7 @@ RSpec.describe 'tables_arrangements' do
id: { type: :string, format: :uuid }, id: { type: :string, format: :uuid },
name: { type: :string }, name: { type: :string },
discomfort: { type: :integer }, discomfort: { type: :integer },
valid: { type: :boolean }, valid: { type: :boolean }
status: { type: :string, enum: %w[complete in_progress] }
} }
} }
xit xit

View File

@ -14,14 +14,14 @@ module Tables
describe '#calculate' do describe '#calculate' do
before do before do
allow(calculator).to receive_messages(table_size_penalty: 2, cohesion_penalty: 5, invitations_penalty: 4) allow(calculator).to receive_messages(table_size_penalty: 2, cohesion_discomfort: 3)
end end
let(:table) { Table.new(create_list(:guest, 6)) } let(:table) { Table.new(create_list(:guest, 6)) }
it 'returns the sum of the table size penalty and the average cohesion penalty', :aggregate_failures do it 'returns the sum of the table size penalty and the average cohesion penalty', :aggregate_failures do
expect(calculator.calculate).to eq(11) expect(calculator.calculate).to eq(7)
expect(calculator.breakdown).to eq(table_size_penalty: 2, cohesion_penalty: 5, invitations_penalty: 4) expect(calculator.breakdown).to eq(table_size_penalty: 2, cohesion_penalty: 5)
end end
end end
@ -73,102 +73,5 @@ module Tables
it { expect(calculator.send(:table_size_penalty)).to eq(10) } it { expect(calculator.send(:table_size_penalty)).to eq(10) }
end end
end end
describe '#cohesion_penalty' do
let(:calculator) { described_class.new(table:, hierarchy:) }
let(:table) { Table.new(guests) }
context 'when all guests belong to the same group' do
let(:guests) { create_list(:guest, 6, group: family) }
let(:hierarchy) { instance_double(AffinityGroupsHierarchy, discomfort: 0) }
it 'returns 0 as cohesion penalty' do
expect(calculator.send(:cohesion_penalty)).to eq(0)
end
end
context 'when guests belong to two different groups with discomfort 1' do
let(:guests) do
create_list(:guest, 3, group: family) +
create_list(:guest, 3, group: friends)
end
let(:hierarchy) do
instance_double(AffinityGroupsHierarchy, discomfort: 1)
end
it 'calculates the correct cohesion penalty' do
# 3 from family, 3 from friends: 3*3*1 = 9 discomfort
expect(calculator.send(:cohesion_penalty)).to eq(10 * (9.0 / 6))
end
end
context 'when guests belong to three groups with different discomforts' do
let(:guests) do
create_list(:guest, 2, group: family) +
create_list(:guest, 2, group: friends) +
create_list(:guest, 2, group: work)
end
let(:hierarchy) do
instance_double(AffinityGroupsHierarchy)
end
before do
allow(hierarchy).to receive(:discomfort).with(family.id, friends.id).and_return(0.5)
allow(hierarchy).to receive(:discomfort).with(family.id, work.id).and_return(1)
allow(hierarchy).to receive(:discomfort).with(friends.id, work.id).and_return(0.2)
allow(hierarchy).to receive(:discomfort).with(friends.id, family.id).and_return(0.5)
allow(hierarchy).to receive(:discomfort).with(work.id, family.id).and_return(1)
allow(hierarchy).to receive(:discomfort).with(work.id, friends.id).and_return(0.2)
end
it 'calculates the correct cohesion penalty' do
# pairs: (family, friends): 2*2*0.5 = 2
# (family, work): 2*2*1 = 4
# (friends, work): 2*2*0.2 = 0.8
# total discomfort = 2 + 4 + 0.8 = 6.8
expect(calculator.send(:cohesion_penalty)).to eq(10 * (6.8 / 6))
end
end
end
describe '#invitations_penalty' do
let(:invitation_a) { create(:invitation) }
let(:invitation_b) { create(:invitation) }
let(:invitation_c) { create(:invitation) }
let(:table) do
create_list(:guest, 2, invitation: invitation_a) +
create_list(:guest, 3, invitation: invitation_b) +
create_list(:guest, 4, invitation: invitation_c)
end
context 'when the table contains all members of an invitation' do
it 'returns 0 as penalty' do
expect(calculator.send(:invitations_penalty)).to eq(0)
end
end
context 'when there is an additional guest of one of the invitations that is not included' do
before do
create(:guest, invitation: invitation_a)
end
it 'returns the penalty for the missing guest' do
expect(calculator.send(:invitations_penalty)).to eq(2)
end
end
context 'when there are multiple guests missing from different invitations' do
before do
create(:guest, invitation: invitation_b)
create(:guest, invitation: invitation_c)
end
it 'returns 2x # of guests left out as the total penalty for all missing guests' do
expect(calculator.send(:invitations_penalty)).to eq(4)
end
end
end
end end
end end

View File

@ -6,43 +6,8 @@ require 'rails_helper'
module Tables module Tables
RSpec.describe Distribution do RSpec.describe Distribution do
let(:tables_arrangement) { TablesArrangement.create! }
around do |example|
ActsAsTenant.with_tenant(create(:wedding)) do
example.run
end
end
describe '#save!' do
let(:people) { create_list(:guest, 2, status: :invited) }
let(:distribution) do
described_class.new(min_per_table: 5, max_per_table: 10, tables_arrangement_id: tables_arrangement.id)
.tap { |d| d.random_distribution(people) }
end
context 'when tables_arrangement_id is nil' do
it { expect { distribution.save! }.to change(TablesArrangement, :count).by(1) }
it { expect { distribution.save! }.to change(Seat, :count).by(2) }
end
context 'when tables_arrangement_id is set' do
before do
existing_arrangement = TablesArrangement.create!
existing_arrangement.seats.create!(guest: people.first, table_number: 1)
distribution.tables_arrangement_id = existing_arrangement.id
end
it { expect { distribution.save! }.not_to(change(TablesArrangement, :count)) }
it { expect { distribution.save! }.to change(Seat, :count).by(1) }
end
end
describe '#random_distribution' do describe '#random_distribution' do
subject(:distribution) do subject(:distribution) { described_class.new(min_per_table: 5, max_per_table: 10) }
described_class.new(min_per_table: 5, max_per_table: 10, tables_arrangement_id: tables_arrangement.id)
end
context 'when there are fewer people than the minimum per table' do context 'when there are fewer people than the minimum per table' do
it 'creates one table' do it 'creates one table' do

View File

@ -17,7 +17,7 @@ module Tables
context 'when there are two tables with two people each' do context 'when there are two tables with two people each' do
let(:initial_solution) do let(:initial_solution) do
Distribution.new(min_per_table: 2, max_per_table: 2, tables_arrangement_id: nil).tap do |distribution| Distribution.new(min_per_table: 2, max_per_table: 2).tap do |distribution|
distribution.tables << Set[:a, :b].to_table distribution.tables << Set[:a, :b].to_table
distribution.tables << Set[:c, :d].to_table distribution.tables << Set[:c, :d].to_table
end end
@ -35,7 +35,7 @@ module Tables
context 'when there are two tables with three people each' do context 'when there are two tables with three people each' do
let(:initial_solution) do let(:initial_solution) do
Distribution.new(min_per_table: 3, max_per_table: 3, tables_arrangement_id: nil).tap do |distribution| Distribution.new(min_per_table: 3, max_per_table: 3).tap do |distribution|
distribution.tables << Set[:a, :b, :c].to_table distribution.tables << Set[:a, :b, :c].to_table
distribution.tables << Set[:d, :e, :f].to_table distribution.tables << Set[:d, :e, :f].to_table
end end

View File

@ -17,7 +17,7 @@ module Tables
context 'when there are two tables with two people each' do context 'when there are two tables with two people each' do
let(:initial_solution) do let(:initial_solution) do
Distribution.new(min_per_table: 2, max_per_table: 2, tables_arrangement_id: nil).tap do |distribution| Distribution.new(min_per_table: 2, max_per_table: 2).tap do |distribution|
distribution.tables << Set[:a, :b].to_table distribution.tables << Set[:a, :b].to_table
distribution.tables << Set[:c, :d].to_table distribution.tables << Set[:c, :d].to_table
end end
@ -35,7 +35,7 @@ module Tables
context 'when there are two tables with three people each' do context 'when there are two tables with three people each' do
let(:initial_solution) do let(:initial_solution) do
Distribution.new(min_per_table: 3, max_per_table: 3, tables_arrangement_id: nil).tap do |distribution| Distribution.new(min_per_table: 3, max_per_table: 3).tap do |distribution|
distribution.tables << Set[:a, :b, :c].to_table distribution.tables << Set[:a, :b, :c].to_table
distribution.tables << Set[:d, :e, :f].to_table distribution.tables << Set[:d, :e, :f].to_table
end end
@ -58,7 +58,7 @@ module Tables
context 'when there are three tables with two people each' do context 'when there are three tables with two people each' do
let(:initial_solution) do let(:initial_solution) do
Distribution.new(min_per_table: 2, max_per_table: 2, tables_arrangement_id: nil).tap do |distribution| Distribution.new(min_per_table: 2, max_per_table: 2).tap do |distribution|
distribution.tables << Set[:a, :b].to_table distribution.tables << Set[:a, :b].to_table
distribution.tables << Set[:c, :d].to_table distribution.tables << Set[:c, :d].to_table
distribution.tables << Set[:e, :f].to_table distribution.tables << Set[:e, :f].to_table

View File

@ -1,28 +0,0 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# frozen_string_literal: true
require 'rails_helper'
module Tables
RSpec.describe WheelSwap do
context 'when the solution has three tables' do
let(:initial_solution) do
Distribution.new(min_per_table: 3, max_per_table: 3, tables_arrangement_id: nil).tap do |distribution|
distribution.tables << Set[:a, :b, :c].to_table
distribution.tables << Set[:d, :e, :f].to_table
distribution.tables << Set[:g, :h, :i].to_table
end
end
it 'swaps a random guest from each table with a guest from another table', :aggregate_failures do
result = described_class.new(initial_solution).call
expect(result.tables.size).to eq(3)
expect(result.tables.map(&:size)).to all(eq(3))
expect(result.tables.map(&:to_a).flatten).to match_array((:a..:i).to_a)
end
end
end
end