Compare commits
369 Commits
letter-ope
...
main
Author | SHA1 | Date | |
---|---|---|---|
db5b0809fa | |||
feb6dc6b6c | |||
3c4c347217 | |||
b1a2484a88 | |||
b0c9c6ad87 | |||
![]() |
7c593e2342 | ||
![]() |
838e52a0a0 | ||
![]() |
990d51f786 | ||
![]() |
de878594ea | ||
![]() |
06c103e617 | ||
e28751521d | |||
0502bc4552 | |||
1760149fbb | |||
7d8ecfd0e3 | |||
78ab27a697 | |||
4afd40b02a | |||
![]() |
43ba96868d | ||
![]() |
00b4e8edfc | ||
12174b6f20 | |||
0d1b64256d | |||
ac659bef86 | |||
5e06c2acca | |||
![]() |
9d656d3759 | ||
4475fc126d | |||
d7cf620d0a | |||
de198e2978 | |||
![]() |
5e69d170a2 | ||
![]() |
e1bf362467 | ||
![]() |
c6ce5d62c0 | ||
afc147643b | |||
![]() |
d5bd6d865e | ||
9c0b64f427 | |||
![]() |
edd56d46f7 | ||
dd14a96e98 | |||
75a0191d40 | |||
8662652e1a | |||
3260b0b422 | |||
81f1e79b6d | |||
d18bddb31a | |||
3f158d7906 | |||
db85580c1f | |||
![]() |
76ed4229ea | ||
4befb8505b | |||
b1df5d2f53 | |||
a1f06dae5b | |||
e8a88b50e2 | |||
185f359942 | |||
543b53d938 | |||
c8b88ab3b1 | |||
e1a5e4f73e | |||
036cc57aa2 | |||
4dfd428ce4 | |||
51922b4f15 | |||
1e3a49adb8 | |||
7596032284 | |||
5dce77c29d | |||
03e09c74a0 | |||
da51a073cc | |||
1953ba9d7f | |||
![]() |
bea90af59d | ||
c1c90f4db1 | |||
![]() |
bcddb802b7 | ||
edfb7227cf | |||
![]() |
b20078a115 | ||
71562e8143 | |||
![]() |
6478760b8d | ||
5cb4d14343 | |||
397a0f34ff | |||
4496deeef5 | |||
de87b6c46b | |||
eabba2109a | |||
1586f88986 | |||
c42eb4e576 | |||
29b3fca80c | |||
d236e459cd | |||
ff8918a1d4 | |||
45313daba2 | |||
ba97a3155a | |||
b3f339a02b | |||
d6607cd997 | |||
ba8eb8b85e | |||
717ee35015 | |||
![]() |
1626056456 | ||
f46f25b799 | |||
beda9ff870 | |||
f3c9d82074 | |||
de92ec0469 | |||
![]() |
6a36a2bda3 | ||
c30bdbd513 | |||
![]() |
a1ed94794a | ||
9505b6d801 | |||
f1ba084e04 | |||
![]() |
4c262b3ddd | ||
![]() |
9f05d74bb4 | ||
54d891070c | |||
b32af61358 | |||
7f4bd8a8df | |||
![]() |
46b59ac617 | ||
f02a6b6a3d | |||
efb5cf64f5 | |||
9a99981f67 | |||
9e9ee0c995 | |||
82a39bce82 | |||
d953e7c4a2 | |||
4e4845a8c3 | |||
60436caf5b | |||
7f4554d21a | |||
509aca79bf | |||
f3088470da | |||
b9770b7a9f | |||
![]() |
2fe72a34e4 | ||
d1791e12c7 | |||
3832f1d840 | |||
4e2d046b18 | |||
c0ba659f29 | |||
fc9911abf4 | |||
3e363ac7dd | |||
cc10fbfb83 | |||
4a107d6728 | |||
9461fa5255 | |||
![]() |
96b6f4ac9e | ||
![]() |
afb103bd50 | ||
8029de4bef | |||
e47fcd3f22 | |||
![]() |
1281060bf8 | ||
b1c95de07c | |||
ac6df3b75b | |||
8e866d8a35 | |||
bc07d63985 | |||
f957d0acb3 | |||
4efb912e38 | |||
6100ce0b99 | |||
11f98bd712 | |||
27ce9cac62 | |||
0a2cf6a5eb | |||
622128a29a | |||
5fb26f42d6 | |||
![]() |
bd1ff7f92d | ||
![]() |
46bd0d71bd | ||
![]() |
c5e1c2aef1 | ||
![]() |
bbe9983070 | ||
![]() |
36345ea74c | ||
![]() |
b5ed6a7280 | ||
![]() |
be04c5c441 | ||
![]() |
252321461c | ||
![]() |
52c7ed6eb1 | ||
bf35ca5f6e | |||
918944b345 | |||
![]() |
8cd4efc6f5 | ||
![]() |
fecbdb263e | ||
![]() |
c5139a8a47 | ||
cf4d27009c | |||
![]() |
9926d99a22 | ||
![]() |
88367081f1 | ||
![]() |
3aa629d676 | ||
8ae8c5b9a5 | |||
![]() |
b36099e6df | ||
![]() |
ee20b596d1 | ||
![]() |
56dd6d6d13 | ||
![]() |
b195ab7426 | ||
![]() |
c5f92ab511 | ||
![]() |
51695e2c83 | ||
![]() |
4dd7f2f45d | ||
![]() |
e9ed703c7e | ||
![]() |
431264e848 | ||
![]() |
63b92ce30f | ||
![]() |
10310eecc6 | ||
![]() |
af2f4015bc | ||
f63d460c90 | |||
![]() |
496a3ac28d | ||
![]() |
450d914d49 | ||
![]() |
b27aab84a4 | ||
4c965b6e8f | |||
d401859088 | |||
80892930ba | |||
![]() |
79c0619e2f | ||
![]() |
d4264e6118 | ||
b48e51e867 | |||
![]() |
5e22172620 | ||
![]() |
afdb0cabca | ||
![]() |
0b8a38cb82 | ||
![]() |
98c64018db | ||
3e6afcc048 | |||
522bcb0032 | |||
40f89ae179 | |||
ecbb6af4bd | |||
b112aefe21 | |||
a5d3062654 | |||
![]() |
567093c449 | ||
2147d7ad5e | |||
f414acb2d5 | |||
66aded5112 | |||
8c12884212 | |||
6cead51bb9 | |||
74ca20d3e8 | |||
cedb8dce92 | |||
006b1893c1 | |||
![]() |
fcb1e77a3a | ||
9f7f99f3f4 | |||
![]() |
f550b8d92c | ||
7c968f555f | |||
1f0c6c2aac | |||
390899524b | |||
![]() |
59e7653064 | ||
d6fd72a45c | |||
889485eaab | |||
105a0ad30c | |||
d03ef173e6 | |||
b86d537cdc | |||
c1774a1c6c | |||
23d09df543 | |||
44dbb7e005 | |||
8a9d0bfdb8 | |||
c6c5a87d8b | |||
acf3b7b82a | |||
0ade367fb5 | |||
91bbae1c63 | |||
e20a366410 | |||
a154e92b6c | |||
65a265b900 | |||
f997657cd3 | |||
37bbc1e4f1 | |||
![]() |
088c609a44 | ||
![]() |
fd40fb61b5 | ||
![]() |
5db5281d9f | ||
492c8e362a | |||
c5c7ea6d54 | |||
e175a3dd34 | |||
a8f0302bb9 | |||
1e533702fd | |||
9c3c766175 | |||
![]() |
d2841a449e | ||
802ec2761c | |||
eded3946de | |||
55e6cfcd36 | |||
5f2778c97a | |||
20cca87cdd | |||
cb10d50d9e | |||
b16ef1e237 | |||
0c7c69ee5e | |||
4fc95185fb | |||
02fcd03b0e | |||
fbc6926402 | |||
19dcb551fa | |||
2fc8a6340b | |||
c15d0806a8 | |||
27c7feebee | |||
b85e2ef932 | |||
c7b9c83f37 | |||
82f17056be | |||
4d69863974 | |||
5fcac34a52 | |||
ad88fb0909 | |||
9fe649f8b8 | |||
5784c89b79 | |||
0780b17f4b | |||
6c6ae62e5a | |||
3b2f52da9b | |||
![]() |
9ac8b9b783 | ||
![]() |
1318f34cec | ||
bd4e0a0135 | |||
![]() |
4f2c3ee1a6 | ||
![]() |
a7e40f3d63 | ||
43f4143df3 | |||
d05900f2a6 | |||
![]() |
663e26bda3 | ||
![]() |
511273280f | ||
![]() |
f1ff39ceb1 | ||
2c6a05ee06 | |||
3bfe889747 | |||
![]() |
e44043f572 | ||
28386df1d8 | |||
![]() |
998706da97 | ||
bab5cd3161 | |||
4e61ee2f22 | |||
f68caca5a4 | |||
da8f3c7618 | |||
![]() |
d18adb2806 | ||
70bbf79a5a | |||
98c1c0d18c | |||
71eecf94a6 | |||
be40c97f2f | |||
ac09d67f4f | |||
e8543d8fb4 | |||
ead48c2588 | |||
dfb50ed2dc | |||
ff0e0b6931 | |||
5cbc81c498 | |||
9d90ade40c | |||
b38e845b90 | |||
83e36df14e | |||
cbcb7b70e3 | |||
9f0773647f | |||
20127398c6 | |||
9e097361d0 | |||
dae2e3bace | |||
98877166dd | |||
438de103ec | |||
9fab79044d | |||
84684b90d7 | |||
64f34a71dc | |||
1fb6c483ed | |||
278faa7319 | |||
93d907cdc8 | |||
fdef94be9a | |||
c62bb137ce | |||
054561faa6 | |||
3996ffc85c | |||
82c543b167 | |||
dfe914a0b8 | |||
0e234b34a0 | |||
2ab966faf8 | |||
9b612ce01d | |||
a3f14f4fec | |||
022b58bb38 | |||
8527b20075 | |||
![]() |
2c4befbcf6 | ||
![]() |
70e9f74207 | ||
85eb85e841 | |||
![]() |
6aba5e234f | ||
e0a34df7b7 | |||
a96be2a79e | |||
3ea1d1e7ec | |||
3fca449461 | |||
ef573c5f73 | |||
822b2b0fad | |||
71046b9a1c | |||
5f01741943 | |||
be9ca9e6b0 | |||
b237239a1f | |||
241668b84d | |||
f708191ede | |||
5f66373d50 | |||
9d08ef6f18 | |||
f588b97e18 | |||
7a80f1f5ef | |||
279093ad98 | |||
e6cf0da814 | |||
4f1aa9dd2d | |||
4d9563cab7 | |||
70b44e1b96 | |||
8429b3952b | |||
e4dad698ea | |||
be3497ad64 | |||
6d61e8452a | |||
63bb32f2a7 | |||
682b5cb5fd | |||
cb90a93ef3 | |||
5b3c1fdfac | |||
8bff98b165 | |||
988e158d99 | |||
cf6ca5aa17 | |||
9e222f59be | |||
24c39f331a | |||
bc4e9cc63e | |||
5700532ac7 | |||
7df4ab1c56 | |||
06f8039f40 | |||
918bc0c1a8 | |||
271ad270a4 | |||
598cb553c9 | |||
306fa41187 | |||
![]() |
50c0a80dec | ||
993e4e5e57 | |||
1a760af3e8 | |||
![]() |
d8ee5972aa | ||
515d841c11 | |||
![]() |
d2eaa21df2 | ||
![]() |
3bde27ddce |
.gitea/workflows
.github/workflows
.gitignore.rubocop.yml.ruby-versionDockerfileDockerfile.devGemfileGemfile.lockREADME.mdRakefileapp
channels/application_cable
controllers
affinities_controller.rbapplication_controller.rbcaptcha_controller.rbexpenses_controller.rbgroups_controller.rbguests_controller.rbinvitations_controller.rbsummary_controller.rbtables_arrangements_controller.rbtokens_controller.rb
users
websites_controller.rbextensions
helpers
application_helper.rbexpenses_helper.rbgroups_helper.rbguests_helper.rbtables_arrangements_helper.rb
jobs
mailers
models
application_record.rbexpense.rbgroup.rbgroup_affinity.rbguest.rbinvitation.rbseat.rbtables_arrangement.rbuser.rbwebsite.rbwedding.rb
queries
serializers
services
views
admin_mailer
attendance_change_email.html.erbattendance_change_email.text.erbinvitations_pdf_email.html.erbinvitations_pdf_email.txt.erb
invitations
layouts
users/mailer
bin
config.ruconfig
159
.gitea/workflows/tests.yml
Normal file
159
.gitea/workflows/tests.yml
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
name: Run unit tests
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
jobs:
|
||||||
|
unit_tests:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
services: &services
|
||||||
|
postgres:
|
||||||
|
image: postgres
|
||||||
|
env:
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
ports:
|
||||||
|
- 5432
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
ref: ${{ github.head_ref }} # Checkout the actual branch, not the result if merged into the base
|
||||||
|
- uses: ruby/setup-ruby@v1
|
||||||
|
- run: bundle install
|
||||||
|
- &postgres_wait
|
||||||
|
name: Wait until Postgres is ready to accept connections
|
||||||
|
run: |
|
||||||
|
apt-get update && apt-get install -f -y postgresql-client
|
||||||
|
until pg_isready -h postgres -U postgres -d postgres
|
||||||
|
do
|
||||||
|
sleep 1
|
||||||
|
echo "Trying again"
|
||||||
|
done
|
||||||
|
- name: Load schema and run unit tests
|
||||||
|
run: |
|
||||||
|
bundle exec rake db:schema:load
|
||||||
|
bundle exec rspec
|
||||||
|
env:
|
||||||
|
RAILS_ENV: test
|
||||||
|
DATABASE_URL: postgres://postgres:postgres@postgres:5432/postgres
|
||||||
|
- name: Get all migrations added
|
||||||
|
id: changed-migration-files
|
||||||
|
uses: tj-actions/changed-files@v45
|
||||||
|
with:
|
||||||
|
files: |
|
||||||
|
db/migrate/**.rb
|
||||||
|
- name: Redo all migrations and check there are no schema changes
|
||||||
|
if: steps.changed-migration-files.outputs.any_changed == 'true'
|
||||||
|
env:
|
||||||
|
ALL_CHANGED_FILES: ${{ steps.changed-migration-files.outputs.all_changed_files }}
|
||||||
|
RAILS_ENV: test
|
||||||
|
DATABASE_URL: postgres://postgres:postgres@postgres:5432/postgres
|
||||||
|
run: |
|
||||||
|
echo ${#ALL_CHANGED_FILES[@]} migrations changed:
|
||||||
|
for file in ${ALL_CHANGED_FILES}; do
|
||||||
|
echo "$file"
|
||||||
|
done
|
||||||
|
|
||||||
|
bundle exec rake db:migrate:redo STEP=${#ALL_CHANGED_FILES[@]}
|
||||||
|
git diff --exit-code db/schema.rb
|
||||||
|
- name: Clean up containers generated by this flow
|
||||||
|
if: failure()
|
||||||
|
run: docker ps --filter network=$JOB_CONTAINER_NAME-$GITHUB_JOB-network --filter name=$JOB_CONTAINER_NAME-* --format "{{.ID}}" | xargs docker rm -f
|
||||||
|
rubocop:
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- uses: ruby/setup-ruby@v1
|
||||||
|
- run: bundle install
|
||||||
|
- run: bundle exec rubocop --force-exclusion --parallel
|
||||||
|
check-licenses:
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- uses: ruby/setup-ruby@v1
|
||||||
|
- name: Install project dependencies
|
||||||
|
run: bundle install --jobs `getconf _NPROCESSORS_ONLN`
|
||||||
|
- name: Run license finder
|
||||||
|
run: license_finder
|
||||||
|
copyright_notice:
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.ACTIONS_TOKEN }}
|
||||||
|
ref: ${{ github.head_ref }}
|
||||||
|
- uses: VinnyBabuManjaly/copyright-action@v1.0.0
|
||||||
|
with:
|
||||||
|
CopyrightString: '# Copyright (C) 2024-2025 LibreWeddingPlanner contributors\n\n'
|
||||||
|
FileType: '.rb'
|
||||||
|
Path: 'app/, config/, db/, spec/'
|
||||||
|
IgnorePath: 'db'
|
||||||
|
- uses: VinnyBabuManjaly/copyright-action@v1.0.0
|
||||||
|
with:
|
||||||
|
CopyrightString: '<%# Copyright (C) 2024-2025 LibreWeddingPlanner contributors %>\n\n'
|
||||||
|
FileType: '.erb'
|
||||||
|
Path: 'app/'
|
||||||
|
- name: Commit changes
|
||||||
|
run: |
|
||||||
|
git config --local user.email "bustikiller@bustikiller.com"
|
||||||
|
git config --local user.name "Manuel Bustillo"
|
||||||
|
git add .
|
||||||
|
|
||||||
|
if [ -n "$(git status --porcelain)" ]; then
|
||||||
|
echo "there are changes";
|
||||||
|
git commit -m "Add copyright notice"
|
||||||
|
git push
|
||||||
|
else
|
||||||
|
echo "no changes";
|
||||||
|
fi
|
||||||
|
|
||||||
|
build-static-assets:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 30
|
||||||
|
needs:
|
||||||
|
- unit_tests
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Login to the private Docker registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ secrets.PRIVATE_REGISTRY_HOST }}
|
||||||
|
username: ${{ secrets.PRIVATE_REGISTRY_USERNAME }}
|
||||||
|
password: ${{ secrets.PRIVATE_REGISTRY_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build and push intermediate stages (build)
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
target: build
|
||||||
|
push: ${{ github.ref == 'refs/heads/main' }}
|
||||||
|
tags: ${{ secrets.PRIVATE_REGISTRY_HOST }}/${{ env.GITHUB_REPOSITORY }}:build
|
||||||
|
cache-from: type=registry,ref=${{ secrets.PRIVATE_REGISTRY_HOST }}/${{ env.GITHUB_REPOSITORY }}:build
|
||||||
|
cache-to: type=inline
|
||||||
|
|
||||||
|
- name: Build and push (final)
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: ${{ github.ref == 'refs/heads/main' }}
|
||||||
|
tags: ${{ secrets.PRIVATE_REGISTRY_HOST }}/${{ env.GITHUB_REPOSITORY }}:latest
|
||||||
|
cache-from: type=registry,ref=${{ secrets.PRIVATE_REGISTRY_HOST }}/${{ env.GITHUB_REPOSITORY }}:latest
|
||||||
|
cache-to: type=inline
|
36
.github/workflows/build.yml
vendored
36
.github/workflows/build.yml
vendored
@ -1,36 +0,0 @@
|
|||||||
name: Build Nginx-based docker image
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
jobs:
|
|
||||||
build-static-assets:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 30
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Login to the private Docker registry
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: ${{ secrets.PRIVATE_REGISTRY_HOST }}
|
|
||||||
username: ${{ secrets.PRIVATE_REGISTRY_USERNAME }}
|
|
||||||
password: ${{ secrets.PRIVATE_REGISTRY_TOKEN }}
|
|
||||||
|
|
||||||
- name: Build and push
|
|
||||||
uses: docker/build-push-action@v6
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
|
||||||
tags: |
|
|
||||||
${{ secrets.PRIVATE_REGISTRY_HOST }}/${{ env.GITHUB_REPOSITORY }}:latest
|
|
||||||
cache-from: type=registry,ref=user/app:latest
|
|
||||||
cache-to: type=inline
|
|
39
.github/workflows/copyright_notice.yml
vendored
39
.github/workflows/copyright_notice.yml
vendored
@ -1,39 +0,0 @@
|
|||||||
name: Add copyright notice
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
jobs:
|
|
||||||
copyright_notice:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.ACTIONS_TOKEN }}
|
|
||||||
ref: ${{ github.head_ref }}
|
|
||||||
- uses: VinnyBabuManjaly/copyright-action@v1.0.0
|
|
||||||
with:
|
|
||||||
CopyrightString: '# Copyright (C) 2024 Manuel Bustillo\n\n'
|
|
||||||
FileType: '.rb'
|
|
||||||
Path: 'app/, config/, db/, spec/'
|
|
||||||
- uses: VinnyBabuManjaly/copyright-action@v1.0.0
|
|
||||||
with:
|
|
||||||
CopyrightString: '<%# Copyright (C) 2024 Manuel Bustillo %>\n\n'
|
|
||||||
FileType: '.erb'
|
|
||||||
Path: 'app/'
|
|
||||||
- name: Commit changes
|
|
||||||
run: |
|
|
||||||
git config --local user.email "bustikiller@bustikiller.com"
|
|
||||||
git config --local user.name "Manuel Bustillo"
|
|
||||||
git add .
|
|
||||||
|
|
||||||
if [ -n "$(git status --porcelain)" ]; then
|
|
||||||
echo "there are changes";
|
|
||||||
git commit -m "Add copyright notice"
|
|
||||||
git push
|
|
||||||
else
|
|
||||||
echo "no changes";
|
|
||||||
fi
|
|
23
.github/workflows/license_finder.yml
vendored
23
.github/workflows/license_finder.yml
vendored
@ -1,23 +0,0 @@
|
|||||||
name: Check usage of free licenses
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
pull_request:
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
jobs:
|
|
||||||
check-licenses:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- uses: ruby/setup-ruby@v1.202.0
|
|
||||||
with:
|
|
||||||
ruby-version: '3.3.6'
|
|
||||||
- name: Install project dependencies
|
|
||||||
run: bundle install --jobs `getconf _NPROCESSORS_ONLN`
|
|
||||||
- name: Run license finder
|
|
||||||
run: license_finder
|
|
43
.github/workflows/tests.yml
vendored
43
.github/workflows/tests.yml
vendored
@ -1,43 +0,0 @@
|
|||||||
name: Run unit tests
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
pull_request:
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
jobs:
|
|
||||||
unit_tests:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
services:
|
|
||||||
postgres:
|
|
||||||
image: postgres
|
|
||||||
env:
|
|
||||||
POSTGRES_USER: postgres
|
|
||||||
POSTGRES_PASSWORD: postgres
|
|
||||||
ports:
|
|
||||||
- 5432
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- uses: ruby/setup-ruby@v1.202.0
|
|
||||||
- run: bundle install
|
|
||||||
- name: Wait until Postgres is ready to accept connections
|
|
||||||
run: |
|
|
||||||
apt-get update && apt-get install -f -y postgresql-client
|
|
||||||
until pg_isready -h postgres -U postgres -d postgres
|
|
||||||
do
|
|
||||||
sleep 1
|
|
||||||
echo "Trying again"
|
|
||||||
done
|
|
||||||
- run: |
|
|
||||||
bundle exec rake db:schema:load
|
|
||||||
bundle exec rspec
|
|
||||||
env:
|
|
||||||
RAILS_ENV: test
|
|
||||||
DATABASE_URL: postgres://postgres:postgres@postgres:5432/postgres
|
|
||||||
- name: Clean up containers generated by this flow
|
|
||||||
if: failure()
|
|
||||||
run: docker ps --filter network=$JOB_CONTAINER_NAME-$GITHUB_JOB-network --filter name=$JOB_CONTAINER_NAME-* --format "{{.ID}}" | xargs docker rm -f
|
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -36,3 +36,4 @@
|
|||||||
|
|
||||||
# Ignore swagger generated documentation
|
# Ignore swagger generated documentation
|
||||||
swagger/v1/swagger.yaml
|
swagger/v1/swagger.yaml
|
||||||
|
wedding-planner.code-workspace
|
||||||
|
29
.rubocop.yml
Normal file
29
.rubocop.yml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
require:
|
||||||
|
- rubocop-rails
|
||||||
|
- rubocop-factory_bot
|
||||||
|
- rubocop-rspec
|
||||||
|
- rubocop-rspec_rails
|
||||||
|
AllCops:
|
||||||
|
NewCops: enable
|
||||||
|
Exclude:
|
||||||
|
- 'db/**/*'
|
||||||
|
- 'config/**/*'
|
||||||
|
- 'script/**/*'
|
||||||
|
- 'bin/*'
|
||||||
|
- '*.yml'
|
||||||
|
Layout/LineLength:
|
||||||
|
Max: 120
|
||||||
|
RSpec/ExampleLength:
|
||||||
|
Enabled: false
|
||||||
|
Metrics/ModuleLength:
|
||||||
|
Enabled: false
|
||||||
|
RSpec/MultipleMemoizedHelpers:
|
||||||
|
Enabled: false
|
||||||
|
Style/Documentation:
|
||||||
|
Enabled: false
|
||||||
|
Metrics/MethodLength:
|
||||||
|
Max: 20
|
||||||
|
Rails/SkipsModelValidations:
|
||||||
|
Enabled: false
|
||||||
|
Metrics/AbcSize:
|
||||||
|
Enabled: false
|
@ -1 +1 @@
|
|||||||
ruby-3.3.6
|
ruby-3.4.3
|
||||||
|
10
Dockerfile
10
Dockerfile
@ -1,8 +1,8 @@
|
|||||||
# 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.3.6
|
ARG RUBY_VERSION=3.4.3
|
||||||
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
|
||||||
@ -13,14 +13,14 @@ 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
|
RUN apt-get update && apt-get install -y nodejs wkhtmltopdf
|
||||||
|
|
||||||
# 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
|
||||||
|
|
||||||
# Install packages needed to build gems
|
# Install packages needed to build gems
|
||||||
RUN apt-get update -qq && \
|
RUN apt-get update -qq && \
|
||||||
apt-get install --no-install-recommends -y build-essential git libpq-dev libvips pkg-config
|
apt-get install --no-install-recommends -y build-essential git libpq-dev libvips pkg-config libyaml-dev
|
||||||
|
|
||||||
# Install application gems
|
# Install application gems
|
||||||
COPY Gemfile Gemfile.lock ./
|
COPY Gemfile Gemfile.lock ./
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
# 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.3.6
|
ARG RUBY_VERSION=3.4.3
|
||||||
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
|
RUN apt-get update && apt-get install -y nodejs wkhtmltopdf
|
||||||
|
|
||||||
FROM base as build
|
FROM base as build
|
||||||
|
|
||||||
# Install packages needed to build gems
|
# Install packages needed to build gems
|
||||||
RUN apt-get update -qq && \
|
RUN apt-get update -qq && \
|
||||||
apt-get install --no-install-recommends -y build-essential git libpq-dev libvips pkg-config
|
apt-get install --no-install-recommends -y build-essential git libpq-dev libvips pkg-config libyaml-dev
|
||||||
|
|
||||||
# Install application gems
|
# Install application gems
|
||||||
COPY Gemfile Gemfile.lock ./
|
COPY Gemfile Gemfile.lock ./
|
||||||
|
25
Gemfile
25
Gemfile
@ -1,6 +1,8 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
source 'https://rubygems.org'
|
source 'https://rubygems.org'
|
||||||
|
|
||||||
ruby '3.3.6'
|
ruby '3.4.3'
|
||||||
gem 'bootsnap', require: false
|
gem 'bootsnap', require: false
|
||||||
gem 'csv'
|
gem 'csv'
|
||||||
gem 'importmap-rails'
|
gem 'importmap-rails'
|
||||||
@ -15,10 +17,14 @@ gem 'stimulus-rails'
|
|||||||
gem 'turbo-rails'
|
gem 'turbo-rails'
|
||||||
gem 'tzinfo-data', platforms: %i[windows jruby]
|
gem 'tzinfo-data', platforms: %i[windows jruby]
|
||||||
|
|
||||||
|
gem 'acts_as_tenant'
|
||||||
gem 'faker'
|
gem 'faker'
|
||||||
|
gem 'httparty'
|
||||||
gem 'jsonapi-rails'
|
gem 'jsonapi-rails'
|
||||||
|
gem 'pluck_to_hash'
|
||||||
gem 'rack-cors'
|
gem 'rack-cors'
|
||||||
gem 'react-rails'
|
gem 'react-rails'
|
||||||
|
gem 'rswag'
|
||||||
gem 'rubytree'
|
gem 'rubytree'
|
||||||
|
|
||||||
group :development, :test do
|
group :development, :test do
|
||||||
@ -27,18 +33,25 @@ 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', '~> 7.1.0'
|
gem 'rspec-rails', '~> 8.0.0'
|
||||||
gem 'rswag'
|
|
||||||
gem 'shoulda-matchers', '~> 6.0'
|
gem 'shoulda-matchers', '~> 6.0'
|
||||||
end
|
end
|
||||||
|
|
||||||
group :development do
|
group :development do
|
||||||
gem 'rubocop'
|
|
||||||
gem 'web-console'
|
|
||||||
gem 'letter_opener_web'
|
gem 'letter_opener_web'
|
||||||
|
gem 'rubocop'
|
||||||
|
gem 'rubocop-factory_bot', require: false
|
||||||
|
gem 'rubocop-rails', require: false
|
||||||
|
gem 'rubocop-rspec', require: false
|
||||||
|
gem 'rubocop-rspec_rails', require: false
|
||||||
|
gem 'web-console'
|
||||||
end
|
end
|
||||||
|
|
||||||
gem 'chroma'
|
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'
|
||||||
|
529
Gemfile.lock
529
Gemfile.lock
@ -1,29 +1,29 @@
|
|||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
actioncable (8.0.0)
|
actioncable (8.0.2.1)
|
||||||
actionpack (= 8.0.0)
|
actionpack (= 8.0.2.1)
|
||||||
activesupport (= 8.0.0)
|
activesupport (= 8.0.2.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.0)
|
actionmailbox (8.0.2.1)
|
||||||
actionpack (= 8.0.0)
|
actionpack (= 8.0.2.1)
|
||||||
activejob (= 8.0.0)
|
activejob (= 8.0.2.1)
|
||||||
activerecord (= 8.0.0)
|
activerecord (= 8.0.2.1)
|
||||||
activestorage (= 8.0.0)
|
activestorage (= 8.0.2.1)
|
||||||
activesupport (= 8.0.0)
|
activesupport (= 8.0.2.1)
|
||||||
mail (>= 2.8.0)
|
mail (>= 2.8.0)
|
||||||
actionmailer (8.0.0)
|
actionmailer (8.0.2.1)
|
||||||
actionpack (= 8.0.0)
|
actionpack (= 8.0.2.1)
|
||||||
actionview (= 8.0.0)
|
actionview (= 8.0.2.1)
|
||||||
activejob (= 8.0.0)
|
activejob (= 8.0.2.1)
|
||||||
activesupport (= 8.0.0)
|
activesupport (= 8.0.2.1)
|
||||||
mail (>= 2.8.0)
|
mail (>= 2.8.0)
|
||||||
rails-dom-testing (~> 2.2)
|
rails-dom-testing (~> 2.2)
|
||||||
actionpack (8.0.0)
|
actionpack (8.0.2.1)
|
||||||
actionview (= 8.0.0)
|
actionview (= 8.0.2.1)
|
||||||
activesupport (= 8.0.0)
|
activesupport (= 8.0.2.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.0)
|
actiontext (8.0.2.1)
|
||||||
actionpack (= 8.0.0)
|
actionpack (= 8.0.2.1)
|
||||||
activerecord (= 8.0.0)
|
activerecord (= 8.0.2.1)
|
||||||
activestorage (= 8.0.0)
|
activestorage (= 8.0.2.1)
|
||||||
activesupport (= 8.0.0)
|
activesupport (= 8.0.2.1)
|
||||||
globalid (>= 0.6.0)
|
globalid (>= 0.6.0)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
actionview (8.0.0)
|
actionview (8.0.2.1)
|
||||||
activesupport (= 8.0.0)
|
activesupport (= 8.0.2.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.0)
|
activejob (8.0.2.1)
|
||||||
activesupport (= 8.0.0)
|
activesupport (= 8.0.2.1)
|
||||||
globalid (>= 0.3.6)
|
globalid (>= 0.3.6)
|
||||||
activemodel (8.0.0)
|
activemodel (8.0.2.1)
|
||||||
activesupport (= 8.0.0)
|
activesupport (= 8.0.2.1)
|
||||||
activerecord (8.0.0)
|
activerecord (8.0.2.1)
|
||||||
activemodel (= 8.0.0)
|
activemodel (= 8.0.2.1)
|
||||||
activesupport (= 8.0.0)
|
activesupport (= 8.0.2.1)
|
||||||
timeout (>= 0.4.0)
|
timeout (>= 0.4.0)
|
||||||
activestorage (8.0.0)
|
activestorage (8.0.2.1)
|
||||||
actionpack (= 8.0.0)
|
actionpack (= 8.0.2.1)
|
||||||
activejob (= 8.0.0)
|
activejob (= 8.0.2.1)
|
||||||
activerecord (= 8.0.0)
|
activerecord (= 8.0.2.1)
|
||||||
activesupport (= 8.0.0)
|
activesupport (= 8.0.2.1)
|
||||||
marcel (~> 1.0)
|
marcel (~> 1.0)
|
||||||
activesupport (8.0.0)
|
activesupport (8.0.2.1)
|
||||||
base64
|
base64
|
||||||
benchmark (>= 0.3)
|
benchmark (>= 0.3)
|
||||||
bigdecimal
|
bigdecimal
|
||||||
@ -72,32 +72,37 @@ GEM
|
|||||||
securerandom (>= 0.3)
|
securerandom (>= 0.3)
|
||||||
tzinfo (~> 2.0, >= 2.0.5)
|
tzinfo (~> 2.0, >= 2.0.5)
|
||||||
uri (>= 0.13.1)
|
uri (>= 0.13.1)
|
||||||
|
acts_as_tenant (1.0.1)
|
||||||
|
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.13.0)
|
annotaterb (4.19.0)
|
||||||
ast (2.4.2)
|
activerecord (>= 6.0.0)
|
||||||
|
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.2.0)
|
base64 (0.3.0)
|
||||||
bcrypt (3.1.20)
|
bcrypt (3.1.20)
|
||||||
benchmark (0.4.0)
|
benchmark (0.4.1)
|
||||||
bigdecimal (3.1.8)
|
bigdecimal (3.2.3)
|
||||||
bindex (0.8.1)
|
bindex (0.8.1)
|
||||||
bootsnap (1.18.4)
|
bootsnap (1.18.6)
|
||||||
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.4)
|
concurrent-ruby (1.3.5)
|
||||||
connection_pool (2.4.1)
|
connection_pool (2.5.4)
|
||||||
crass (1.0.6)
|
crass (1.0.6)
|
||||||
csv (3.3.0)
|
csv (3.3.5)
|
||||||
date (3.4.0)
|
date (3.4.1)
|
||||||
debug (1.9.2)
|
debug (1.11.0)
|
||||||
irb (~> 1.10)
|
irb (~> 1.10)
|
||||||
reline (>= 0.3.8)
|
reline (>= 0.3.8)
|
||||||
devise (4.9.4)
|
devise (4.9.4)
|
||||||
@ -106,38 +111,44 @@ GEM
|
|||||||
railties (>= 4.1.0)
|
railties (>= 4.1.0)
|
||||||
responders
|
responders
|
||||||
warden (~> 1.2.3)
|
warden (~> 1.2.3)
|
||||||
diff-lcs (1.5.1)
|
diff-lcs (1.6.2)
|
||||||
drb (2.2.1)
|
drb (2.2.3)
|
||||||
erubi (1.13.0)
|
erb (5.0.2)
|
||||||
|
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.4.6)
|
factory_bot (6.5.5)
|
||||||
activesupport (>= 5.0.0)
|
activesupport (>= 6.1.0)
|
||||||
factory_bot_rails (6.4.3)
|
factory_bot_rails (6.5.1)
|
||||||
factory_bot (~> 6.4)
|
factory_bot (~> 6.5)
|
||||||
railties (>= 5.0.0)
|
railties (>= 6.1.0)
|
||||||
faker (3.5.1)
|
faker (3.5.2)
|
||||||
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)
|
||||||
i18n (1.14.6)
|
httparty (0.23.1)
|
||||||
|
csv
|
||||||
|
mini_mime (>= 1.0.0)
|
||||||
|
multi_xml (>= 0.5.2)
|
||||||
|
i18n (1.14.7)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
importmap-rails (2.0.3)
|
importmap-rails (2.2.2)
|
||||||
actionpack (>= 6.0.0)
|
actionpack (>= 6.0.0)
|
||||||
activesupport (>= 6.0.0)
|
activesupport (>= 6.0.0)
|
||||||
railties (>= 6.0.0)
|
railties (>= 6.0.0)
|
||||||
io-console (0.7.2)
|
io-console (0.8.1)
|
||||||
irb (1.14.1)
|
irb (1.15.2)
|
||||||
|
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.7.5)
|
json (2.13.2)
|
||||||
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)
|
||||||
@ -151,7 +162,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.3)
|
language_server-protocol (3.17.0.5)
|
||||||
launchy (3.0.1)
|
launchy (3.0.1)
|
||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
childprocess (~> 5.0)
|
childprocess (~> 5.0)
|
||||||
@ -170,8 +181,9 @@ 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)
|
||||||
logger (1.6.1)
|
lint_roller (1.1.0)
|
||||||
loofah (2.23.1)
|
logger (1.7.0)
|
||||||
|
loofah (2.24.1)
|
||||||
crass (~> 1.0.2)
|
crass (~> 1.0.2)
|
||||||
nokogiri (>= 1.12.0)
|
nokogiri (>= 1.12.0)
|
||||||
mail (2.8.1)
|
mail (2.8.1)
|
||||||
@ -182,89 +194,109 @@ 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)
|
||||||
minitest (5.25.1)
|
mini_portile2 (2.8.9)
|
||||||
|
minitest (5.25.5)
|
||||||
money (6.19.0)
|
money (6.19.0)
|
||||||
i18n (>= 0.6.4, <= 2)
|
i18n (>= 0.6.4, <= 2)
|
||||||
msgpack (1.7.2)
|
msgpack (1.7.5)
|
||||||
net-imap (0.5.1)
|
multi_xml (0.7.1)
|
||||||
|
bigdecimal (~> 3.1)
|
||||||
|
net-imap (0.5.10)
|
||||||
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.0)
|
net-smtp (0.5.1)
|
||||||
net-protocol
|
net-protocol
|
||||||
nio4r (2.7.4)
|
nio4r (2.7.4)
|
||||||
nokogiri (1.16.7-aarch64-linux)
|
nokogiri (1.18.10)
|
||||||
|
mini_portile2 (~> 2.8.2)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.16.7-arm-linux)
|
nokogiri (1.18.10-aarch64-linux-gnu)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.16.7-arm64-darwin)
|
nokogiri (1.18.10-arm-linux-gnu)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.16.7-x86-linux)
|
nokogiri (1.18.10-arm64-darwin)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.16.7-x86_64-darwin)
|
nokogiri (1.18.10-x86_64-darwin)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.16.7-x86_64-linux)
|
nokogiri (1.18.10-x86_64-linux-gnu)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
orm_adapter (0.5.0)
|
orm_adapter (0.5.0)
|
||||||
parallel (1.26.3)
|
ostruct (0.6.2)
|
||||||
parser (3.3.5.1)
|
parallel (1.27.0)
|
||||||
|
parser (3.3.9.0)
|
||||||
ast (~> 2.4.1)
|
ast (~> 2.4.1)
|
||||||
racc
|
racc
|
||||||
pg (1.5.9)
|
pg (1.6.2)
|
||||||
pry (0.15.0)
|
pg (1.6.2-aarch64-linux)
|
||||||
|
pg (1.6.2-arm64-darwin)
|
||||||
|
pg (1.6.2-x86_64-darwin)
|
||||||
|
pg (1.6.2-x86_64-linux)
|
||||||
|
pluck_to_hash (1.0.2)
|
||||||
|
activerecord (>= 4.0.2)
|
||||||
|
activesupport (>= 4.0.2)
|
||||||
|
pp (0.6.2)
|
||||||
|
prettyprint
|
||||||
|
prettyprint (0.2.0)
|
||||||
|
prism (1.5.1)
|
||||||
|
pry (0.15.2)
|
||||||
coderay (~> 1.1)
|
coderay (~> 1.1)
|
||||||
method_source (~> 1.0)
|
method_source (~> 1.0)
|
||||||
psych (5.2.0)
|
psych (5.2.6)
|
||||||
|
date
|
||||||
stringio
|
stringio
|
||||||
public_suffix (6.0.1)
|
public_suffix (6.0.1)
|
||||||
puma (6.4.3)
|
puma (6.6.1)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
raabro (1.4.0)
|
raabro (1.4.0)
|
||||||
racc (1.8.1)
|
racc (1.8.1)
|
||||||
rack (3.1.8)
|
rack (3.2.1)
|
||||||
rack-cors (2.0.2)
|
rack-cors (3.0.0)
|
||||||
rack (>= 2.0.0)
|
logger
|
||||||
rack-session (2.0.0)
|
rack (>= 3.0.14)
|
||||||
|
rack-session (2.1.1)
|
||||||
|
base64 (>= 0.1.0)
|
||||||
rack (>= 3.0.0)
|
rack (>= 3.0.0)
|
||||||
rack-test (2.1.0)
|
rack-test (2.2.0)
|
||||||
rack (>= 1.3)
|
rack (>= 1.3)
|
||||||
rackup (2.2.1)
|
rackup (2.2.1)
|
||||||
rack (>= 3)
|
rack (>= 3)
|
||||||
rails (8.0.0)
|
rails (8.0.2.1)
|
||||||
actioncable (= 8.0.0)
|
actioncable (= 8.0.2.1)
|
||||||
actionmailbox (= 8.0.0)
|
actionmailbox (= 8.0.2.1)
|
||||||
actionmailer (= 8.0.0)
|
actionmailer (= 8.0.2.1)
|
||||||
actionpack (= 8.0.0)
|
actionpack (= 8.0.2.1)
|
||||||
actiontext (= 8.0.0)
|
actiontext (= 8.0.2.1)
|
||||||
actionview (= 8.0.0)
|
actionview (= 8.0.2.1)
|
||||||
activejob (= 8.0.0)
|
activejob (= 8.0.2.1)
|
||||||
activemodel (= 8.0.0)
|
activemodel (= 8.0.2.1)
|
||||||
activerecord (= 8.0.0)
|
activerecord (= 8.0.2.1)
|
||||||
activestorage (= 8.0.0)
|
activestorage (= 8.0.2.1)
|
||||||
activesupport (= 8.0.0)
|
activesupport (= 8.0.2.1)
|
||||||
bundler (>= 1.15.0)
|
bundler (>= 1.15.0)
|
||||||
railties (= 8.0.0)
|
railties (= 8.0.2.1)
|
||||||
rails-dom-testing (2.2.0)
|
rails-dom-testing (2.3.0)
|
||||||
activesupport (>= 5.0.0)
|
activesupport (>= 5.0.0)
|
||||||
minitest
|
minitest
|
||||||
nokogiri (>= 1.6)
|
nokogiri (>= 1.6)
|
||||||
rails-html-sanitizer (1.6.0)
|
rails-html-sanitizer (1.6.2)
|
||||||
loofah (~> 2.21)
|
loofah (~> 2.21)
|
||||||
nokogiri (~> 1.14)
|
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.0)
|
railties (8.0.2.1)
|
||||||
actionpack (= 8.0.0)
|
actionpack (= 8.0.2.1)
|
||||||
activesupport (= 8.0.0)
|
activesupport (= 8.0.2.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.2.1)
|
rake (13.3.0)
|
||||||
rdoc (6.7.0)
|
rdoc (6.14.2)
|
||||||
|
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)
|
||||||
@ -272,34 +304,38 @@ GEM
|
|||||||
execjs
|
execjs
|
||||||
railties (>= 3.2)
|
railties (>= 3.2)
|
||||||
tilt
|
tilt
|
||||||
redis (5.3.0)
|
redis (5.4.1)
|
||||||
redis-client (>= 0.22.0)
|
redis-client (>= 0.22.0)
|
||||||
redis-client (0.22.2)
|
redis-client (0.23.2)
|
||||||
connection_pool
|
connection_pool
|
||||||
regexp_parser (2.9.2)
|
regexp_parser (2.11.3)
|
||||||
reline (0.5.11)
|
reline (0.6.2)
|
||||||
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)
|
||||||
rspec-core (3.13.2)
|
rqrcode (3.1.0)
|
||||||
|
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.3)
|
rspec-expectations (3.13.5)
|
||||||
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.2)
|
rspec-mocks (3.13.5)
|
||||||
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 (7.1.0)
|
rspec-rails (8.0.2)
|
||||||
actionpack (>= 7.0)
|
actionpack (>= 7.2)
|
||||||
activesupport (>= 7.0)
|
activesupport (>= 7.2)
|
||||||
railties (>= 7.0)
|
railties (>= 7.2)
|
||||||
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.1)
|
rspec-support (3.13.5)
|
||||||
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)
|
||||||
@ -315,32 +351,50 @@ 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.68.0)
|
rubocop (1.80.2)
|
||||||
json (~> 2.3)
|
json (~> 2.3)
|
||||||
language_server-protocol (>= 3.17.0)
|
language_server-protocol (~> 3.17.0.2)
|
||||||
|
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.4, < 3.0)
|
regexp_parser (>= 2.9.3, < 3.0)
|
||||||
rubocop-ast (>= 1.32.2, < 2.0)
|
rubocop-ast (>= 1.46.0, < 2.0)
|
||||||
ruby-progressbar (~> 1.7)
|
ruby-progressbar (~> 1.7)
|
||||||
unicode-display_width (>= 2.4.0, < 3.0)
|
unicode-display_width (>= 2.4.0, < 4.0)
|
||||||
rubocop-ast (1.33.0)
|
rubocop-ast (1.46.0)
|
||||||
parser (>= 3.3.1.0)
|
parser (>= 3.3.7.2)
|
||||||
|
prism (~> 1.4)
|
||||||
|
rubocop-factory_bot (2.27.1)
|
||||||
|
lint_roller (~> 1.1)
|
||||||
|
rubocop (~> 1.72, >= 1.72.1)
|
||||||
|
rubocop-rails (2.33.3)
|
||||||
|
activesupport (>= 4.2.0)
|
||||||
|
lint_roller (~> 1.1)
|
||||||
|
rack (>= 1.1)
|
||||||
|
rubocop (>= 1.75.0, < 2.0)
|
||||||
|
rubocop-ast (>= 1.44.0, < 2.0)
|
||||||
|
rubocop-rspec (3.6.0)
|
||||||
|
lint_roller (~> 1.1)
|
||||||
|
rubocop (~> 1.72, >= 1.72.1)
|
||||||
|
rubocop-rspec_rails (2.31.0)
|
||||||
|
lint_roller (~> 1.1)
|
||||||
|
rubocop (~> 1.72, >= 1.72.1)
|
||||||
|
rubocop-rspec (~> 3.5)
|
||||||
ruby-progressbar (1.13.0)
|
ruby-progressbar (1.13.0)
|
||||||
rubytree (2.1.0)
|
rubytree (2.1.1)
|
||||||
json (~> 2.0, > 2.3.1)
|
json (~> 2.0, > 2.9)
|
||||||
rubyzip (2.3.2)
|
rubyzip (2.3.2)
|
||||||
securerandom (0.3.2)
|
securerandom (0.4.1)
|
||||||
shoulda-matchers (6.4.0)
|
shoulda-matchers (6.5.0)
|
||||||
activesupport (>= 5.2.0)
|
activesupport (>= 5.2.0)
|
||||||
solid_queue (1.0.2)
|
solid_queue (1.2.1)
|
||||||
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)
|
||||||
@ -350,19 +404,19 @@ 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.2)
|
stringio (3.1.7)
|
||||||
thor (1.3.2)
|
thor (1.4.0)
|
||||||
tilt (2.4.0)
|
tilt (2.4.0)
|
||||||
timeout (0.4.2)
|
timeout (0.4.3)
|
||||||
tomlrb (2.0.3)
|
tomlrb (2.0.3)
|
||||||
turbo-rails (2.0.11)
|
turbo-rails (2.0.16)
|
||||||
actionpack (>= 6.0.0)
|
actionpack (>= 7.1.0)
|
||||||
railties (>= 6.0.0)
|
railties (>= 7.1.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.2)
|
uri (1.0.3)
|
||||||
useragent (0.16.10)
|
useragent (0.16.11)
|
||||||
warden (1.2.9)
|
warden (1.2.9)
|
||||||
rack (>= 2.0.9)
|
rack (>= 2.0.9)
|
||||||
web-console (4.2.1)
|
web-console (4.2.1)
|
||||||
@ -370,13 +424,17 @@ 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.6)
|
websocket-driver (0.7.7)
|
||||||
|
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.1)
|
zeitwerk (2.7.3)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
aarch64-linux
|
aarch64-linux
|
||||||
@ -387,6 +445,7 @@ PLATFORMS
|
|||||||
x86_64-linux
|
x86_64-linux
|
||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
|
acts_as_tenant
|
||||||
annotaterb
|
annotaterb
|
||||||
bootsnap
|
bootsnap
|
||||||
chroma
|
chroma
|
||||||
@ -395,6 +454,7 @@ DEPENDENCIES
|
|||||||
devise (~> 4.9)
|
devise (~> 4.9)
|
||||||
factory_bot_rails
|
factory_bot_rails
|
||||||
faker
|
faker
|
||||||
|
httparty
|
||||||
importmap-rails
|
importmap-rails
|
||||||
jbuilder
|
jbuilder
|
||||||
jsonapi-rails
|
jsonapi-rails
|
||||||
@ -402,15 +462,21 @@ DEPENDENCIES
|
|||||||
license_finder
|
license_finder
|
||||||
money
|
money
|
||||||
pg (~> 1.1)
|
pg (~> 1.1)
|
||||||
|
pluck_to_hash
|
||||||
pry
|
pry
|
||||||
puma (>= 5.0)
|
puma (>= 5.0)
|
||||||
rack-cors
|
rack-cors
|
||||||
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)
|
||||||
rspec-rails (~> 7.1.0)
|
rqrcode (~> 3.1)
|
||||||
|
rspec-rails (~> 8.0.0)
|
||||||
rswag
|
rswag
|
||||||
rubocop
|
rubocop
|
||||||
|
rubocop-factory_bot
|
||||||
|
rubocop-rails
|
||||||
|
rubocop-rspec
|
||||||
|
rubocop-rspec_rails
|
||||||
rubytree
|
rubytree
|
||||||
shoulda-matchers (~> 6.0)
|
shoulda-matchers (~> 6.0)
|
||||||
solid_queue (~> 1.0)
|
solid_queue (~> 1.0)
|
||||||
@ -419,9 +485,182 @@ DEPENDENCIES
|
|||||||
turbo-rails
|
turbo-rails
|
||||||
tzinfo-data
|
tzinfo-data
|
||||||
web-console
|
web-console
|
||||||
|
wicked_pdf (~> 2.8)
|
||||||
|
|
||||||
|
CHECKSUMS
|
||||||
|
actioncable (8.0.2.1) sha256=6f1cb20db39fba28a93569e8d5dab42b2749d7ddd4baebb5bbecd4217e49d6a2
|
||||||
|
actionmailbox (8.0.2.1) sha256=8ea8c6e31e448961c06fc1d6282775b32aff1c009f232d4564e07e54850a6cad
|
||||||
|
actionmailer (8.0.2.1) sha256=0de14d8d04541eab130858cb2f0697266be42de1afe1104bc43d7998137ddb9c
|
||||||
|
actionpack (8.0.2.1) sha256=61e7e11a31dbe5152ca57221788bdca42ef302c4cc53b4c8993d68dce8982b0a
|
||||||
|
actiontext (8.0.2.1) sha256=0cc4b3b5cfb9d915c6697b05b013dad7f4eaf074d9989700b6a0a55cf620d6b8
|
||||||
|
actionview (8.0.2.1) sha256=2ea6d20ccb0b7b84a221a940ac06853ce99235e4ecb4947815839c7c5ecbf347
|
||||||
|
activejob (8.0.2.1) sha256=d6e5f2da07ec8efac13a38af1752416770dc74e95783f7b252506d707aa32b89
|
||||||
|
activemodel (8.0.2.1) sha256=17bab6cdb86531844113df22f864480a89a276bf0318246e628f99e0ac077ec4
|
||||||
|
activerecord (8.0.2.1) sha256=a6556e7bdd53f3889d18d2aa3a7ff115fd6c5e1463dd06f97fb88d06b58c6df1
|
||||||
|
activestorage (8.0.2.1) sha256=43bb3d9e115471e201e6a66813810c1d15b607a321f29d62efdf9d90ffaf76f8
|
||||||
|
activesupport (8.0.2.1) sha256=0405a76fd1ca989975d9ae00d46a4d3979bdf3817482d846b63affa84bd561c6
|
||||||
|
acts_as_tenant (1.0.1) sha256=6944e4d64533337938a8817a6b4ff9b11189c9dcc0b1333bb89f3821a4c14c53
|
||||||
|
addressable (2.8.7) sha256=462986537cf3735ab5f3c0f557f14155d778f4b43ea4f485a9deb9c8f7c58232
|
||||||
|
annotaterb (4.19.0) sha256=c951df62059b3ac1ae383f4140bf935a140a15b6461f8d9a97d34b38ce2c7208
|
||||||
|
ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
|
||||||
|
babel-source (5.8.35) sha256=79ef222a9dcb867ac2efa3b0da35b4bcb15a4bfa67b6b2dcbf1e9a29104498d9
|
||||||
|
babel-transpiler (0.7.0) sha256=4c06f4ad9e8e1cabe94f99e11df2f140bb72aca9ba067dbb49dc14d9b98d1570
|
||||||
|
base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
|
||||||
|
bcrypt (3.1.20) sha256=8410f8c7b3ed54a3c00cd2456bf13917d695117f033218e2483b2e40b0784099
|
||||||
|
benchmark (0.4.1) sha256=d4ef40037bba27f03b28013e219b950b82bace296549ec15a78016552f8d2cce
|
||||||
|
bigdecimal (3.2.3) sha256=ffd11d1ac67a0d3b2f44aec0a6487210b3f813f363dd11f1fcccf5ba00da4e1b
|
||||||
|
bindex (0.8.1) sha256=7b1ecc9dc539ed8bccfc8cb4d2732046227b09d6f37582ff12e50a5047ceb17e
|
||||||
|
bootsnap (1.18.6) sha256=0ae2393c1e911e38be0f24e9173e7be570c3650128251bf06240046f84a07d00
|
||||||
|
builder (3.3.0) sha256=497918d2f9dca528fdca4b88d84e4ef4387256d984b8154e9d5d3fe5a9c8835f
|
||||||
|
childprocess (5.1.0) sha256=9a8d484be2fd4096a0e90a0cd3e449a05bc3aa33f8ac9e4d6dcef6ac1455b6ec
|
||||||
|
chroma (0.2.0) sha256=64bdcd36a4765fbcd45adc64960cc153101300b4918f90ffdd89f4e2eb954b54
|
||||||
|
chunky_png (1.4.0) sha256=89d5b31b55c0cf4da3cf89a2b4ebc3178d8abe8cbaf116a1dba95668502fdcfe
|
||||||
|
coderay (1.1.3) sha256=dc530018a4684512f8f38143cd2a096c9f02a1fc2459edcfe534787a7fc77d4b
|
||||||
|
concurrent-ruby (1.3.5) sha256=813b3e37aca6df2a21a3b9f1d497f8cbab24a2b94cab325bffe65ee0f6cbebc6
|
||||||
|
connection_pool (2.5.4) sha256=e9e1922327416091f3f6542f5f4446c2a20745276b9aa796dd0bb2fd0ea1e70a
|
||||||
|
crass (1.0.6) sha256=dc516022a56e7b3b156099abc81b6d2b08ea1ed12676ac7a5657617f012bd45d
|
||||||
|
csv (3.3.5) sha256=6e5134ac3383ef728b7f02725d9872934f523cb40b961479f69cf3afa6c8e73f
|
||||||
|
date (3.4.1) sha256=bf268e14ef7158009bfeaec40b5fa3c7271906e88b196d958a89d4b408abe64f
|
||||||
|
debug (1.11.0) sha256=1425db64cfa0130c952684e3dc974985be201dd62899bf4bbe3f8b5d6cf1aef2
|
||||||
|
devise (4.9.4) sha256=920042fe5e704c548aa4eb65ebdd65980b83ffae67feb32c697206bfd975a7f8
|
||||||
|
diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962
|
||||||
|
drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373
|
||||||
|
erb (5.0.2) sha256=d30f258143d4300fb4ecf430042ac12970c9bb4b33c974a545b8f58c1ec26c0f
|
||||||
|
erubi (1.13.1) sha256=a082103b0885dbc5ecf1172fede897f9ebdb745a4b97a5e8dc63953db1ee4ad9
|
||||||
|
et-orbi (1.2.11) sha256=d26e868cc21db88280a9ec1a50aa3da5d267eb9b2037ba7b831d6c2731f5df64
|
||||||
|
execjs (2.9.1) sha256=e8fd066f6df60c8e8fbebc32c6fb356b5212c77374e8416a9019ca4bb154dcfb
|
||||||
|
factory_bot (6.5.5) sha256=ce59295daee1b4704dab8a2fee6816f513d467c6aa3bc587860767d74a66efbe
|
||||||
|
factory_bot_rails (6.5.1) sha256=d3cc4851eae4dea8a665ec4a4516895045e710554d2b5ac9e68b94d351bc6d68
|
||||||
|
faker (3.5.2) sha256=f9a80291b2e3f259801d1dd552f0732fe04dce5d1f74e798365bc0413789c473
|
||||||
|
fugit (1.11.1) sha256=e89485e7be22226d8e9c6da411664d0660284b4b1c08cacb540f505907869868
|
||||||
|
globalid (1.2.1) sha256=70bf76711871f843dbba72beb8613229a49429d1866828476f9c9d6ccc327ce9
|
||||||
|
httparty (0.23.1) sha256=3ac1dd62f2010f6ece551716f5ceec2b2012011d89f1751917ab7f724e966b55
|
||||||
|
i18n (1.14.7) sha256=ceba573f8138ff2c0915427f1fc5bdf4aa3ab8ae88c8ce255eb3ecf0a11a5d0f
|
||||||
|
importmap-rails (2.2.2) sha256=729f5b1092f832780829ade1d0b46c7e53d91c556f06da7254da2977e93fe614
|
||||||
|
io-console (0.8.1) sha256=1e15440a6b2f67b6ea496df7c474ed62c860ad11237f29b3bd187f054b925fcb
|
||||||
|
irb (1.15.2) sha256=222f32952e278da34b58ffe45e8634bf4afc2dc7aa9da23fed67e581aa50fdba
|
||||||
|
jbuilder (2.13.0) sha256=7200a38a1c0081aa81b7a9757e7a299db75bc58cf1fd45ca7919a91627d227d6
|
||||||
|
json (2.13.2) sha256=02e1f118d434c6b230a64ffa5c8dee07e3ec96244335c392eaed39e1199dbb68
|
||||||
|
json-schema (5.0.1) sha256=bef71a82c600a42594911553522e143f7634affc198ed507ef3ded2f920a74a9
|
||||||
|
jsonapi-deserializable (0.2.0) sha256=5f0ca2d3f8404cce1584a314e8a3753be32a56054c942adfe997b87e92bce147
|
||||||
|
jsonapi-parser (0.1.1) sha256=9ee0dc031e88fc7548d56fab66f9716d1e1c06f972b529b8c4617bc42a097020
|
||||||
|
jsonapi-rails (0.4.1) sha256=fa68b927b58f194e8b81f578c0bf18e61575638f45a390f66c832de2e6d179ba
|
||||||
|
jsonapi-rb (0.5.0) sha256=7922a164278f506c43d56277f6bd0800a0b603cc985f7f63fe7241b2628bd105
|
||||||
|
jsonapi-renderer (0.2.2) sha256=b5c44b033d61b4abdb6500fa4ab84807ca0b36ea0e59e47a2c3ca7095a6e447b
|
||||||
|
jsonapi-serializable (0.3.1) sha256=221e657677659d798e268a33ec97a83ec5ea0e4233f931358db84e88056552e9
|
||||||
|
language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc
|
||||||
|
launchy (3.0.1) sha256=b7fa60bda0197cf57614e271a250a8ca1f6a34ab889a3c73f67ec5d57c8a7f2c
|
||||||
|
letter_opener (1.10.0) sha256=2ff33f2e3b5c3c26d1959be54b395c086ca6d44826e8bf41a14ff96fdf1bdbb2
|
||||||
|
letter_opener_web (3.0.0) sha256=3f391efe0e8b9b24becfab5537dfb17a5cf5eb532038f947daab58cb4b749860
|
||||||
|
license_finder (7.2.1) sha256=179ead19b64b170638b72fd16024233813673ac9d20d5ba75ae0b4444887ef14
|
||||||
|
lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
|
||||||
|
logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
|
||||||
|
loofah (2.24.1) sha256=655a30842b70ec476410b347ab1cd2a5b92da46a19044357bbd9f401b009a337
|
||||||
|
mail (2.8.1) sha256=ec3b9fadcf2b3755c78785cb17bc9a0ca9ee9857108a64b6f5cfc9c0b5bfc9ad
|
||||||
|
marcel (1.0.4) sha256=0d5649feb64b8f19f3d3468b96c680bae9746335d02194270287868a661516a4
|
||||||
|
method_source (1.1.0) sha256=181301c9c45b731b4769bc81e8860e72f9161ad7d66dd99103c9ab84f560f5c5
|
||||||
|
mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef
|
||||||
|
mini_portile2 (2.8.9) sha256=0cd7c7f824e010c072e33f68bc02d85a00aeb6fce05bb4819c03dfd3c140c289
|
||||||
|
minitest (5.25.5) sha256=391b6c6cb43a4802bfb7c93af1ebe2ac66a210293f4a3fb7db36f2fc7dc2c756
|
||||||
|
money (6.19.0) sha256=ec936fa1e42f2783719241ed9fd52725d0efa628f928feea1eb5c37d5de7daf3
|
||||||
|
msgpack (1.7.5) sha256=ffb04979f51e6406823c03abe50e1da2c825c55a37dee138518cdd09d9d3aea8
|
||||||
|
multi_xml (0.7.1) sha256=4fce100c68af588ff91b8ba90a0bb3f0466f06c909f21a32f4962059140ba61b
|
||||||
|
net-imap (0.5.10) sha256=f84d206a296bff48a3a10507567fc38b050d2a40c92ea0d448164f64e60d6205
|
||||||
|
net-pop (0.1.2) sha256=848b4e982013c15b2f0382792268763b748cce91c9e91e36b0f27ed26420dff3
|
||||||
|
net-protocol (0.2.2) sha256=aa73e0cba6a125369de9837b8d8ef82a61849360eba0521900e2c3713aa162a8
|
||||||
|
net-smtp (0.5.1) sha256=ed96a0af63c524fceb4b29b0d352195c30d82dd916a42f03c62a3a70e5b70736
|
||||||
|
nio4r (2.7.4) sha256=d95dee68e0bb251b8ff90ac3423a511e3b784124e5db7ff5f4813a220ae73ca9
|
||||||
|
nokogiri (1.18.10) sha256=d5cc0731008aa3b3a87b361203ea3d19b2069628cb55e46ac7d84a0445e69cc1
|
||||||
|
nokogiri (1.18.10-aarch64-linux-gnu) sha256=7fb87235d729c74a2be635376d82b1d459230cc17c50300f8e4fcaabc6195344
|
||||||
|
nokogiri (1.18.10-arm-linux-gnu) sha256=51f4f25ab5d5ba1012d6b16aad96b840a10b067b93f35af6a55a2c104a7ee322
|
||||||
|
nokogiri (1.18.10-arm64-darwin) sha256=c2b0de30770f50b92c9323fa34a4e1cf5a0af322afcacd239cd66ee1c1b22c85
|
||||||
|
nokogiri (1.18.10-x86_64-darwin) sha256=536e74bed6db2b5076769cab5e5f5af0cd1dccbbd75f1b3e1fa69d1f5c2d79e2
|
||||||
|
nokogiri (1.18.10-x86_64-linux-gnu) sha256=ff5ba26ba2dbce5c04b9ea200777fd225061d7a3930548806f31db907e500f72
|
||||||
|
orm_adapter (0.5.0) sha256=aa5d0be5d540cbb46d3a93e88061f4ece6a25f6e97d6a47122beb84fe595e9b9
|
||||||
|
ostruct (0.6.2) sha256=6d7302a299e400a2c248d6ce0dad18fc3a5714e8096facc25ffd0c54ee57cfc0
|
||||||
|
parallel (1.27.0) sha256=4ac151e1806b755fb4e2dc2332cbf0e54f2e24ba821ff2d3dcf86bf6dc4ae130
|
||||||
|
parser (3.3.9.0) sha256=94d6929354b1a6e3e1f89d79d4d302cc8f5aa814431a6c9c7e0623335d7687f2
|
||||||
|
pg (1.6.2) sha256=58614afd405cc9c2c9e15bffe8432e0d6cfc58b722344ad4a47c73a85189c875
|
||||||
|
pg (1.6.2-aarch64-linux) sha256=0503c6be5b0ca5ca3aaf91f2ed638f90843313cb81e8e7d7b60ad4bb62c3d131
|
||||||
|
pg (1.6.2-arm64-darwin) sha256=4d44500b28d5193b26674583d199a6484f80f1f2ea9cf54f7d7d06a1b7e316b6
|
||||||
|
pg (1.6.2-x86_64-darwin) sha256=c441a55723584e2ae41749bf26024d7ffdfe1841b442308ed50cd6b7fda04115
|
||||||
|
pg (1.6.2-x86_64-linux) sha256=525f438137f2d1411a1ebcc4208ec35cb526b5a3b285a629355c73208506a8ea
|
||||||
|
pluck_to_hash (1.0.2) sha256=1599906239716f98262a41493dd7d4cb72e8d83ad3d76d666deacfc5de50a47e
|
||||||
|
pp (0.6.2) sha256=947ec3120c6f92195f8ee8aa25a7b2c5297bb106d83b41baa02983686577b6ff
|
||||||
|
prettyprint (0.2.0) sha256=2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193
|
||||||
|
prism (1.5.1) sha256=b40c1b76ccb9fcccc3d1553967cda6e79fa7274d8bfea0d98b15d27a6d187134
|
||||||
|
pry (0.15.2) sha256=12d54b8640d3fa29c9211dd4ffb08f3fd8bf7a4fd9b5a73ce5b59c8709385b6b
|
||||||
|
psych (5.2.6) sha256=814328aa5dcb6d604d32126a20bc1cbcf05521a5b49dbb1a8b30a07e580f316e
|
||||||
|
public_suffix (6.0.1) sha256=61d44e1cab5cbbbe5b31068481cf16976dd0dc1b6b07bd95617ef8c5e3e00c6f
|
||||||
|
puma (6.6.1) sha256=b9b56e4a4ea75d1bfa6d9e1972ee2c9f43d0883f011826d914e8e37b3694ea1e
|
||||||
|
raabro (1.4.0) sha256=d4fa9ff5172391edb92b242eed8be802d1934b1464061ae5e70d80962c5da882
|
||||||
|
racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
|
||||||
|
rack (3.2.1) sha256=30af3f7e5ec21b0d14d822cf24446048dba5f651b617c7e97405b604f20a9e33
|
||||||
|
rack-cors (3.0.0) sha256=7b95be61db39606906b61b83bd7203fa802b0ceaaad8fcb2fef39e097bf53f68
|
||||||
|
rack-session (2.1.1) sha256=0b6dc07dea7e4b583f58a48e8b806d4c9f1c6c9214ebc202ec94562cbea2e4e9
|
||||||
|
rack-test (2.2.0) sha256=005a36692c306ac0b4a9350355ee080fd09ddef1148a5f8b2ac636c720f5c463
|
||||||
|
rackup (2.2.1) sha256=f737191fd5c5b348b7f0a4412a3b86383f88c43e13b8217b63d4c8d90b9e798d
|
||||||
|
rails (8.0.2.1) sha256=13ab95615569e74e364384b346b1d83e4795dbde83d9edf584e8768e8049b3ac
|
||||||
|
rails-dom-testing (2.3.0) sha256=8acc7953a7b911ca44588bf08737bc16719f431a1cc3091a292bca7317925c1d
|
||||||
|
rails-html-sanitizer (1.6.2) sha256=35fce2ca8242da8775c83b6ba9c1bcaad6751d9eb73c1abaa8403475ab89a560
|
||||||
|
railties (8.0.2.1) sha256=54e40e1771fc2878f572d5a4e076cddb057ba8d4d471f8b7d9bfc61bc1301d4c
|
||||||
|
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
|
||||||
|
rake (13.3.0) sha256=96f5092d786ff412c62fde76f793cc0541bd84d2eb579caa529aa8a059934493
|
||||||
|
rdoc (6.14.2) sha256=9fdd44df130f856ae70cc9a264dfd659b9b40de369b16581f4ab746e42439226
|
||||||
|
react-rails (3.2.1) sha256=2235db0b240517596b1cb3e26177ab5bc64d3a56579b0415ee242b1691f81f64
|
||||||
|
redis (5.4.1) sha256=b5e675b57ad22b15c9bcc765d5ac26f60b675408af916d31527af9bd5a81faae
|
||||||
|
redis-client (0.23.2) sha256=e33bab6682c8155cfef95e6dd296936bb9c2981a89fb578ace27a076fa2836fa
|
||||||
|
regexp_parser (2.11.3) sha256=ca13f381a173b7a93450e53459075c9b76a10433caadcb2f1180f2c741fc55a4
|
||||||
|
reline (0.6.2) sha256=1dad26a6008872d59c8e05244b119347c9f2ddaf4a53dce97856cd5f30a02846
|
||||||
|
responders (3.1.1) sha256=92f2a87e09028347368639cfb468f5fefa745cb0dc2377ef060db1cdd79a341a
|
||||||
|
rexml (3.3.9) sha256=d71875b85299f341edf47d44df0212e7658cbdf35aeb69cefdb63f57af3137c9
|
||||||
|
rqrcode (3.1.0) sha256=e2d5996375f6e9a013823c289ed575dbea678b8e0388574302c1fac563f098af
|
||||||
|
rqrcode_core (2.0.0) sha256=1e40b823ab57a96482a417fff5dd5c33645a00cea6ef5d9e342fecc5ef91d9ab
|
||||||
|
rspec-core (3.13.5) sha256=ab3f682897c6131c67f9a17cfee5022a597f283aebe654d329a565f9937a4fa3
|
||||||
|
rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836
|
||||||
|
rspec-mocks (3.13.5) sha256=e4338a6f285ada9fe56f5893f5457783af8194f5d08884d17a87321d5195ea81
|
||||||
|
rspec-rails (8.0.2) sha256=113139a53f5d068d4f48d1c29ad5f982013ed9b0daa69d7f7b266eda5d433ace
|
||||||
|
rspec-support (3.13.5) sha256=add745af535dd14b18f1209ab41ef987fdfad12786176b6a3b3619b9a7279fbf
|
||||||
|
rswag (2.16.0) sha256=f07ce41548b9bb51464c38bc7b95af22fee84b90f2d1197a515a623906353086
|
||||||
|
rswag-api (2.16.0) sha256=b653f7bd92e98be18b01ab4525d88950d7b0960e293a99f856b9efcee3ae6074
|
||||||
|
rswag-specs (2.16.0) sha256=8ba26085c408b0bd2ed21dc8015c80f417c7d34c63720ab7133c2549b5bd2a91
|
||||||
|
rswag-ui (2.16.0) sha256=a1f49e927dceda92e6e6e7c1000f1e217ee66c565f69e28131dc98b33cd3a04f
|
||||||
|
rubocop (1.80.2) sha256=6485f30fefcf5c199db3b91e5e253b1ef43f7e564784e2315255809a3dd9abf4
|
||||||
|
rubocop-ast (1.46.0) sha256=0da7f6ad5b98614f89b74f11873c191059c823eae07d6ffd40a42a3338f2232b
|
||||||
|
rubocop-factory_bot (2.27.1) sha256=9d744b5916778c1848e5fe6777cc69855bd96548853554ec239ba9961b8573fe
|
||||||
|
rubocop-rails (2.33.3) sha256=848c011b58c1292f3066246c9eb18abf6ffcfbce28bc57c4ab888bbec79af74b
|
||||||
|
rubocop-rspec (3.6.0) sha256=c0e4205871776727e54dee9cc91af5fd74578001551ba40e1fe1a1ab4b404479
|
||||||
|
rubocop-rspec_rails (2.31.0) sha256=775375e18a26a1184a812ef3054b79d218e85601b9ae897f38f8be24dddf1f45
|
||||||
|
ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33
|
||||||
|
rubytree (2.1.1) sha256=4925016356a81730e982f1f8c3b5f8da461f18906c77d238bad4c4ba896abd41
|
||||||
|
rubyzip (2.3.2) sha256=3f57e3935dc2255c414484fbf8d673b4909d8a6a57007ed754dde39342d2373f
|
||||||
|
securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1
|
||||||
|
shoulda-matchers (6.5.0) sha256=ef6b572b2bed1ac4aba6ab2c5ff345a24b6d055a93a3d1c3bfc86d9d499e3f44
|
||||||
|
solid_queue (1.2.1) sha256=7976b3690a08080ef63d1b11281f0b77398f7697dbeda0e2c5532682639d4b15
|
||||||
|
sprockets (4.2.1) sha256=951b13dd2f2fcae840a7184722689a803e0ff9d2702d902bd844b196da773f97
|
||||||
|
sprockets-rails (3.5.2) sha256=a9e88e6ce9f8c912d349aa5401509165ec42326baf9e942a85de4b76dbc4119e
|
||||||
|
stimulus-rails (1.3.4) sha256=765676ffa1f33af64ce026d26b48e8ffb2e0b94e0f50e9119e11d6107d67cb06
|
||||||
|
stringio (3.1.7) sha256=5b78b7cb242a315fb4fca61a8255d62ec438f58da2b90be66048546ade4507fa
|
||||||
|
thor (1.4.0) sha256=8763e822ccb0f1d7bee88cde131b19a65606657b847cc7b7b4b82e772bcd8a3d
|
||||||
|
tilt (2.4.0) sha256=df74f29a451daed26591a85e8e0cebb198892cb75b6573394303acda273fba4d
|
||||||
|
timeout (0.4.3) sha256=9509f079b2b55fe4236d79633bd75e34c1c1e7e3fb4b56cb5fda61f80a0fe30e
|
||||||
|
tomlrb (2.0.3) sha256=c2736acf24919f793334023a4ff396c0647d93fce702a73c9d348deaa815d4f7
|
||||||
|
turbo-rails (2.0.16) sha256=d24e1b60f0c575b3549ecda967e5391027143f8220d837ed792c8d48ea0ea38d
|
||||||
|
tzinfo (2.0.6) sha256=8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b
|
||||||
|
unicode-display_width (2.6.0) sha256=12279874bba6d5e4d2728cef814b19197dbb10d7a7837a869bab65da943b7f5a
|
||||||
|
uri (1.0.3) sha256=e9f2244608eea2f7bc357d954c65c910ce0399ca5e18a7a29207ac22d8767011
|
||||||
|
useragent (0.16.11) sha256=700e6413ad4bb954bb63547fa098dddf7b0ebe75b40cc6f93b8d54255b173844
|
||||||
|
warden (1.2.9) sha256=46684f885d35a69dbb883deabf85a222c8e427a957804719e143005df7a1efd0
|
||||||
|
web-console (4.2.1) sha256=e7bcf37a10ea2b4ec4281649d1cee461b32232d0a447e82c786e6841fd22fe20
|
||||||
|
websocket-driver (0.7.7) sha256=056d99f2cd545712cfb1291650fde7478e4f2661dc1db6a0fa3b966231a146b4
|
||||||
|
websocket-extensions (0.1.5) sha256=1c6ba63092cda343eb53fc657110c71c754c56484aad42578495227d717a8241
|
||||||
|
wicked_pdf (2.8.2) sha256=648d9b0cec5a34adbc9bbf809731052a78119e2d6d323b9e4aa1383e1d683824
|
||||||
|
with_env (1.1.0) sha256=50b3e4f0a6cda8f90d8a6bd87a6261f6c381429abafb161c4c69ad4a0cd0b6e4
|
||||||
|
xml-simple (1.1.9) sha256=d21131e519c86f1a5bc2b6d2d57d46e6998e47f18ed249b25cad86433dbd695d
|
||||||
|
zeitwerk (2.7.3) sha256=b2e86b4a9b57d26ba68a15230dcc7fe6f040f06831ce64417b0621ad96ba3e85
|
||||||
|
|
||||||
RUBY VERSION
|
RUBY VERSION
|
||||||
ruby 3.3.6p108
|
ruby 3.4.3p32
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
2.5.17
|
2.6.1
|
||||||
|
13
README.md
13
README.md
@ -57,8 +57,19 @@ 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.
|
||||||
|
|
||||||
Once all containers have started, visit http://libre-wedding-planner.app.localhost/dashboard to load the application.
|
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.
|
||||||
|
|
||||||
|
## Multitenancy
|
||||||
|
|
||||||
|
LibreWeddingPlanner is designed to manage multiple weddings in a single host. All URLs (in the API and the frontend) are scoped under a slug that is unique per wedding. The slug is made of lowercase letters, numbers, and dashes (-).
|
||||||
|
|
||||||
|
The development environment is seeded with a wedding whose slug is `default`.
|
||||||
## Email delivery
|
## Email delivery
|
||||||
|
|
||||||
In the development environment, real emails will not be sent. You can visit http://libre-wedding-planner.app.localhost/letter_opener/ to get a list of emails generated by the application.
|
In the development environment, real emails will not be sent. You can visit http://libre-wedding-planner.app.localhost/letter_opener/ to get a list of emails generated by the application.
|
||||||
|
4
Rakefile
4
Rakefile
@ -1,6 +1,8 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
# Add your own tasks in files placed in lib/tasks ending in .rake,
|
# Add your own tasks in files placed in lib/tasks ending in .rake,
|
||||||
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
|
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
|
||||||
|
|
||||||
require_relative "config/application"
|
require_relative 'config/application'
|
||||||
|
|
||||||
Rails.application.load_tasks
|
Rails.application.load_tasks
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module ApplicationCable
|
module ApplicationCable
|
||||||
class Channel < ActionCable::Channel::Base
|
class Channel < ActionCable::Channel::Base
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module ApplicationCable
|
module ApplicationCable
|
||||||
class Connection < ActionCable::Connection::Base
|
class Connection < ActionCable::Connection::Base
|
||||||
|
72
app/controllers/affinities_controller.rb
Normal file
72
app/controllers/affinities_controller.rb
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AffinitiesController < ApplicationController
|
||||||
|
before_action :set_group, except: :reset
|
||||||
|
|
||||||
|
def index
|
||||||
|
overridden = @group.affinities.each_with_object({}) do |affinity, acc|
|
||||||
|
acc[affinity.another_group(@group).id] = affinity.discomfort
|
||||||
|
end
|
||||||
|
|
||||||
|
for_each_group do |group_id|
|
||||||
|
overridden[group_id] || GroupAffinity::NEUTRAL
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def bulk_update
|
||||||
|
affinities = params.expect(affinities: [%i[group_id affinity]]).map(&:to_h).map do |affinity|
|
||||||
|
{
|
||||||
|
group_a_id: @group.id,
|
||||||
|
group_b_id: affinity[:group_id],
|
||||||
|
discomfort: GroupAffinity::MAX_DISCOMFORT - affinity[:affinity]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
GroupAffinity.upsert_all(affinities, unique_by: :uindex_group_pair)
|
||||||
|
|
||||||
|
render json: {}, status: :ok
|
||||||
|
rescue ActiveRecord::InvalidForeignKey
|
||||||
|
render json: { error: 'At least one of the group IDs provided does not exist.' }, status: :bad_request
|
||||||
|
rescue ActiveRecord::StatementInvalid
|
||||||
|
render json: { error: 'Invalid group ID or discomfort provided.' }, status: :bad_request
|
||||||
|
end
|
||||||
|
|
||||||
|
def default
|
||||||
|
hierarchy = AffinityGroupsHierarchy.new
|
||||||
|
|
||||||
|
for_each_group do |group_id|
|
||||||
|
hierarchy.default_discomfort(@group.id, group_id).to_f
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def reset
|
||||||
|
hierarchy = AffinityGroupsHierarchy.new
|
||||||
|
|
||||||
|
affinities = Group.pluck(:id).combination(2).map do |(group_a_id, group_b_id)|
|
||||||
|
{
|
||||||
|
group_a_id:,
|
||||||
|
group_b_id:,
|
||||||
|
discomfort: hierarchy.default_discomfort(group_a_id, group_b_id).to_f
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
GroupAffinity.upsert_all(affinities, unique_by: :uindex_group_pair)
|
||||||
|
|
||||||
|
render json: {}, status: :ok
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def for_each_group
|
||||||
|
Group.where.not(id: @group.id)
|
||||||
|
.pluck(:id)
|
||||||
|
.index_with { |group_id| GroupAffinity::MAX_DISCOMFORT - yield(group_id) }
|
||||||
|
.then { |affinities| render json: affinities }
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_group
|
||||||
|
@group = Group.find(params[:group_id])
|
||||||
|
end
|
||||||
|
end
|
@ -1,6 +1,10 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class ApplicationController < ActionController::Base
|
class ApplicationController < ActionController::Base
|
||||||
|
set_current_tenant_through_filter
|
||||||
|
before_action :set_tenant
|
||||||
before_action :authenticate_user!
|
before_action :authenticate_user!
|
||||||
after_action :set_csrf_cookie
|
after_action :set_csrf_cookie
|
||||||
|
|
||||||
@ -29,15 +33,35 @@ class ApplicationController < ActionController::Base
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def validate_captcha!
|
||||||
|
Rails.logger.info("Captcha params: #{captcha_params}")
|
||||||
|
|
||||||
|
return if LibreCaptcha.new.valid?(id: captcha_params[:id], answer: captcha_params[:answer])
|
||||||
|
|
||||||
|
render json: { error: 'Incorrect CAPTCHA solution' }, status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
|
||||||
|
def captcha_params
|
||||||
|
params.expect(captcha: %i[id answer])
|
||||||
|
end
|
||||||
|
|
||||||
|
def default_url_options(options = {})
|
||||||
|
options.merge(path_params: { slug: ActsAsTenant.current_tenant&.slug })
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_tenant
|
||||||
|
set_current_tenant(Wedding.find_by!(slug: params[:slug]))
|
||||||
|
end
|
||||||
|
|
||||||
def development_swagger?
|
def development_swagger?
|
||||||
Rails.env.test? ||
|
Rails.env.test? ||
|
||||||
Rails.env.development? && request.headers['referer']&.include?('/api-docs/index.html')
|
(Rails.env.development? && request.headers['referer']&.include?('/api-docs/index.html'))
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_csrf_cookie
|
def set_csrf_cookie
|
||||||
cookies['csrf-token'] = {
|
cookies['csrf-token'] = {
|
||||||
value: form_authenticity_token,
|
value: form_authenticity_token,
|
||||||
secure: Rails.env.production?,
|
secure: false,
|
||||||
same_site: :strict
|
same_site: :strict
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
15
app/controllers/captcha_controller.rb
Normal file
15
app/controllers/captcha_controller.rb
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class CaptchaController < ApplicationController
|
||||||
|
skip_before_action :authenticate_user!
|
||||||
|
skip_before_action :set_tenant
|
||||||
|
def create
|
||||||
|
id = LibreCaptcha.new.id
|
||||||
|
render json: {
|
||||||
|
id:,
|
||||||
|
media_url: media_captcha_index_url(id:)
|
||||||
|
}, status: :created
|
||||||
|
end
|
||||||
|
end
|
@ -1,4 +1,6 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class ExpensesController < ApplicationController
|
class ExpensesController < ApplicationController
|
||||||
def summary
|
def summary
|
||||||
@ -6,7 +8,12 @@ class ExpensesController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def index
|
def index
|
||||||
render json: Expense.all.order(pricing_type: :asc, amount: :desc).as_json(only: %i[id name amount pricing_type])
|
render json: Expense.order(pricing_type: :asc, amount: :desc).as_json(only: %i[id name amount pricing_type])
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
Expense.create!(expense_params)
|
||||||
|
render json: {}, status: :created
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
@ -14,9 +21,14 @@ class ExpensesController < ApplicationController
|
|||||||
render json: {}, status: :ok
|
render json: {}, status: :ok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
Expense.find(params[:id]).destroy!
|
||||||
|
render json: {}, status: :ok
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def expense_params
|
def expense_params
|
||||||
params.require(:expense).permit(:name, :amount, :pricing_type)
|
params.expect(expense: %i[name amount pricing_type])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,7 +1,46 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class GroupsController < ApplicationController
|
class GroupsController < ApplicationController
|
||||||
def index
|
def index
|
||||||
render json: Groups::SummaryQuery.new.call.as_json
|
query_result = Groups::SummaryQuery.new.call.as_json.map(&:deep_symbolize_keys).map do |group|
|
||||||
|
{
|
||||||
|
id: group[:id],
|
||||||
|
name: group[:name],
|
||||||
|
icon: group[:icon],
|
||||||
|
color: group[:color],
|
||||||
|
parent_id: group[:parent_id],
|
||||||
|
attendance: group.slice(:total, :considered, :invited, :confirmed, :declined, :tentative)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
render json: query_result
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
group = Group.create!(**group_params, parent:)
|
||||||
|
render json: group.as_json(only: %i[id name icon color parent_id]), status: :created
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
group = Group.find(params[:id])
|
||||||
|
group.update!(**group_params, parent:)
|
||||||
|
render json: group.as_json(only: %i[id name icon color parent_id]), status: :ok
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
Group.find(params[:id]).destroy!
|
||||||
|
render json: {}, status: :ok
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def parent
|
||||||
|
params[:group][:parent_id].present? ? Group.find(params[:group][:parent_id]) : nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def group_params
|
||||||
|
params.expect(group: %i[name icon color])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,23 +1,35 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
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.all.includes(:group)
|
render json: Guest.includes(:group)
|
||||||
.joins(:group)
|
.left_joins(:group)
|
||||||
.order('groups.name' => :asc, name: :asc)
|
.order('groups.name' => :asc, invitation_id: :asc, name: :asc)
|
||||||
.as_json(only: %i[id name status], include: { group: { only: %i[id name] } })
|
.as_json(GUEST_PARAMS)
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
Guest.create!(guest_params)
|
guest = Guest.create!(guest_params)
|
||||||
render json: {}, status: :created
|
render json: guest.as_json(GUEST_PARAMS), status: :created
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
Guest.find(params[:id]).update!(guest_params)
|
guest = Guest.find(params[:id])
|
||||||
render json: {}, status: :ok
|
guest.update!(guest_params)
|
||||||
|
|
||||||
|
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
|
||||||
@ -28,6 +40,6 @@ class GuestsController < ApplicationController
|
|||||||
private
|
private
|
||||||
|
|
||||||
def guest_params
|
def guest_params
|
||||||
params.require(:guest).permit(:name, :group_id, :status)
|
user_signed_in? ? params.expect(guest: %i[name group_id status]) : params.expect(guest: %i[status])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
76
app/controllers/invitations_controller.rb
Normal file
76
app/controllers/invitations_controller.rb
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
# 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
|
44
app/controllers/summary_controller.rb
Normal file
44
app/controllers/summary_controller.rb
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class SummaryController < ApplicationController
|
||||||
|
def index
|
||||||
|
render json: {
|
||||||
|
expenses:,
|
||||||
|
guests:
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def guests
|
||||||
|
guest_summary = Guest.group(:status).count
|
||||||
|
|
||||||
|
{
|
||||||
|
total: guest_summary.except('considered').values.sum,
|
||||||
|
confirmed: guest_summary['confirmed'].to_i,
|
||||||
|
declined: guest_summary['declined'].to_i,
|
||||||
|
tentative: guest_summary['tentative'].to_i,
|
||||||
|
invited: guest_summary['invited'].to_i
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def expenses
|
||||||
|
expense_summary = Expenses::TotalQuery.new(wedding: ActsAsTenant.current_tenant).call
|
||||||
|
|
||||||
|
{
|
||||||
|
projected: {
|
||||||
|
total: expense_summary['total_projected'],
|
||||||
|
guests: expense_summary['projected_guests']
|
||||||
|
},
|
||||||
|
confirmed: {
|
||||||
|
total: expense_summary['total_confirmed'],
|
||||||
|
guests: expense_summary['confirmed_guests']
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
paid: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
@ -1,23 +1,57 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class TablesArrangementsController < ApplicationController
|
class TablesArrangementsController < ApplicationController
|
||||||
def index
|
def index
|
||||||
render json: TablesArrangement.all.order(discomfort: :asc).limit(3).as_json(only: %i[id name discomfort])
|
current_digest = Tables::Distribution.digest(current_tenant)
|
||||||
|
|
||||||
|
render json: TablesArrangement
|
||||||
|
.order(valid: :desc)
|
||||||
|
.order(discomfort: :asc)
|
||||||
|
.select(:id, :name, :discomfort, :status, :progress)
|
||||||
|
.select("digest = '#{current_digest}'::uuid OR discomfort IS NULL as valid")
|
||||||
|
.limit(20)
|
||||||
|
.as_json(only: %i[id name discomfort valid status progress])
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
Seat.joins(guest: :group)
|
Guest.joins(:seats, :group)
|
||||||
.where(tables_arrangement_id: params[:id])
|
.where(seats: { tables_arrangement_id: params[:id] })
|
||||||
.order('guests.group_id')
|
.select('guests.*', 'groups.color', 'seats.table_number')
|
||||||
.pluck(
|
.group_by(&:table_number)
|
||||||
:table_number,
|
.map { |number, guests| format(number:, guests:) }
|
||||||
'guests.name',
|
.then { |result| render json: { id: params[:id], tables: result } }
|
||||||
'guests.id',
|
end
|
||||||
'groups.color'
|
|
||||||
)
|
def create
|
||||||
.group_by(&:first)
|
ActiveRecord::Base.transaction do
|
||||||
.transform_values { |table| table.map { |(_, name, id, color)| { id:, name:, color: } } }
|
tables_arrangement = TablesArrangement.create!(status: :not_started)
|
||||||
.map { |number, guests| { number:, guests: } }
|
TableSimulatorJob.perform_later(current_tenant.id, tables_arrangement.id)
|
||||||
.then { |result| render json: result }
|
end
|
||||||
|
|
||||||
|
render json: {}, status: :created
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def format(number:, guests:)
|
||||||
|
{
|
||||||
|
number: number,
|
||||||
|
discomfort: discomfort(guests: guests),
|
||||||
|
guests: guests.as_json(only: %i[id name color])
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def discomfort(guests:)
|
||||||
|
table = Tables::Table.new(guests)
|
||||||
|
|
||||||
|
table.min_per_table = TableSimulatorJob::MIN_PER_TABLE
|
||||||
|
table.max_per_table = TableSimulatorJob::MAX_PER_TABLE
|
||||||
|
calculator = Tables::DiscomfortCalculator.new(table:)
|
||||||
|
{
|
||||||
|
discomfort: calculator.calculate,
|
||||||
|
breakdown: calculator.breakdown
|
||||||
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
12
app/controllers/tokens_controller.rb
Normal file
12
app/controllers/tokens_controller.rb
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class TokensController < ApplicationController
|
||||||
|
skip_before_action :authenticate_user!
|
||||||
|
skip_before_action :set_tenant
|
||||||
|
|
||||||
|
def show
|
||||||
|
head :ok
|
||||||
|
end
|
||||||
|
end
|
@ -1,6 +1,9 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
class Users::ConfirmationsController < Devise::ConfirmationsController
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Users
|
||||||
|
class ConfirmationsController < Devise::ConfirmationsController
|
||||||
clear_respond_to
|
clear_respond_to
|
||||||
respond_to :json
|
respond_to :json
|
||||||
|
|
||||||
@ -20,4 +23,5 @@ class Users::ConfirmationsController < Devise::ConfirmationsController
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end
|
end
|
@ -1,6 +1,32 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
class Users::RegistrationsController < Devise::RegistrationsController
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Users
|
||||||
|
class RegistrationsController < Devise::RegistrationsController
|
||||||
clear_respond_to
|
clear_respond_to
|
||||||
respond_to :json
|
respond_to :json
|
||||||
|
|
||||||
|
before_action :validate_captcha!, only: :create
|
||||||
|
|
||||||
|
def create
|
||||||
|
wedding = Wedding.create(slug: params[:slug])
|
||||||
|
unless wedding.persisted?
|
||||||
|
render json: { errors: wedding.errors.full_messages }, status: :unprocessable_entity
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
ActsAsTenant.with_tenant(wedding) do
|
||||||
|
super do |user|
|
||||||
|
wedding.destroy unless user.persisted?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_tenant
|
||||||
|
set_current_tenant(nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
@ -1,6 +1,10 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
class Users::SessionsController < Devise::SessionsController
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Users
|
||||||
|
class SessionsController < Devise::SessionsController
|
||||||
clear_respond_to
|
clear_respond_to
|
||||||
respond_to :json
|
respond_to :json
|
||||||
|
end
|
||||||
end
|
end
|
25
app/controllers/websites_controller.rb
Normal file
25
app/controllers/websites_controller.rb
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# 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
|
@ -1,4 +1,6 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module TreeNodeExtension
|
module TreeNodeExtension
|
||||||
def distance_to_common_ancestor(another_node)
|
def distance_to_common_ancestor(another_node)
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module ApplicationHelper
|
module ApplicationHelper
|
||||||
end
|
end
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module ExpensesHelper
|
module ExpensesHelper
|
||||||
end
|
end
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module GroupsHelper
|
module GroupsHelper
|
||||||
end
|
end
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module GuestsHelper
|
module GuestsHelper
|
||||||
end
|
end
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module TablesArrangementsHelper
|
module TablesArrangementsHelper
|
||||||
end
|
end
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class ApplicationJob < ActiveJob::Base
|
class ApplicationJob < ActiveJob::Base
|
||||||
# Automatically retry jobs that encountered a deadlock
|
# Automatically retry jobs that encountered a deadlock
|
||||||
|
@ -1,17 +1,42 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class TableSimulatorJob < ApplicationJob
|
class TableSimulatorJob < ApplicationJob
|
||||||
queue_as :default
|
queue_as :default
|
||||||
|
|
||||||
def perform(*_args)
|
MIN_PER_TABLE = 8
|
||||||
|
MAX_PER_TABLE = 10
|
||||||
|
|
||||||
|
def perform(wedding_id, tables_arrangement_id) # rubocop:disable Metrics/MethodLength
|
||||||
|
Rails.logger.info "Starting table simulation #{tables_arrangement_id} for wedding #{wedding_id}"
|
||||||
|
ActsAsTenant.with_tenant(Wedding.find(wedding_id)) do
|
||||||
engine = VNS::Engine.new
|
engine = VNS::Engine.new
|
||||||
|
|
||||||
engine.add_perturbation(Tables::Swap)
|
engine.add_optimization(Tables::Swap)
|
||||||
engine.add_perturbation(Tables::Shift)
|
engine.add_optimization(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: 8, max_per_table: 10)
|
|
||||||
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)
|
||||||
@ -19,5 +44,8 @@ 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
|
||||||
|
45
app/mailers/admin_mailer.rb
Normal file
45
app/mailers/admin_mailer.rb
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# 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
|
@ -1,6 +1,18 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class ApplicationMailer < ActionMailer::Base
|
class ApplicationMailer < ActionMailer::Base
|
||||||
default from: "from@example.com"
|
class << self
|
||||||
layout "mailer"
|
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'
|
||||||
end
|
end
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class ApplicationRecord < ActiveRecord::Base
|
class ApplicationRecord < ActiveRecord::Base
|
||||||
primary_abstract_class
|
primary_abstract_class
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
# == Schema Information
|
# == Schema Information
|
||||||
#
|
#
|
||||||
@ -10,8 +12,18 @@
|
|||||||
# pricing_type :enum default("fixed"), not null
|
# pricing_type :enum default("fixed"), 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
|
||||||
|
#
|
||||||
|
# Indexes
|
||||||
|
#
|
||||||
|
# index_expenses_on_wedding_id (wedding_id)
|
||||||
|
#
|
||||||
|
# Foreign Keys
|
||||||
|
#
|
||||||
|
# fk_rails_... (wedding_id => weddings.id) ON DELETE => cascade
|
||||||
#
|
#
|
||||||
class Expense < ApplicationRecord
|
class Expense < ApplicationRecord
|
||||||
|
acts_as_tenant :wedding
|
||||||
enum :pricing_type,
|
enum :pricing_type,
|
||||||
fixed: 'fixed',
|
fixed: 'fixed',
|
||||||
per_person: 'per_person'
|
per_person: 'per_person'
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
# == Schema Information
|
# == Schema Information
|
||||||
#
|
#
|
||||||
@ -12,34 +14,36 @@
|
|||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
# parent_id :uuid
|
# parent_id :uuid
|
||||||
|
# wedding_id :uuid not null
|
||||||
#
|
#
|
||||||
# Indexes
|
# Indexes
|
||||||
#
|
#
|
||||||
# index_groups_on_name (name) UNIQUE
|
# index_groups_on_name (name) UNIQUE
|
||||||
# index_groups_on_parent_id (parent_id)
|
# index_groups_on_parent_id (parent_id)
|
||||||
|
# index_groups_on_wedding_id (wedding_id)
|
||||||
#
|
#
|
||||||
# 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
|
||||||
#
|
#
|
||||||
class Group < ApplicationRecord
|
class Group < ApplicationRecord
|
||||||
|
acts_as_tenant :wedding
|
||||||
|
|
||||||
validates :name, uniqueness: true
|
validates :name, uniqueness: true
|
||||||
validates :name, :order, presence: true
|
validates :name, :order, presence: true
|
||||||
|
|
||||||
has_many :children, class_name: 'Group', foreign_key: 'parent_id'
|
has_many :children, class_name: 'Group', foreign_key: 'parent_id', dependent: :nullify, inverse_of: :parent
|
||||||
belongs_to :parent, class_name: 'Group', optional: true
|
belongs_to :parent, class_name: 'Group', optional: true
|
||||||
|
|
||||||
before_create :set_color
|
before_create :set_color
|
||||||
|
|
||||||
scope :roots, -> { where(parent_id: nil) }
|
scope :roots, -> { where(parent_id: nil) }
|
||||||
|
|
||||||
has_many :guests
|
has_many :guests, dependent: :nullify
|
||||||
|
|
||||||
def colorize_children(generation = 1)
|
def colorize_children(generation = 1)
|
||||||
derived_colors = generation == 1 ? color.paint.palette.analogous(size: children.count) : color.paint.palette.decreasing_saturation
|
children.zip(palette(generation)) do |child, raw_color|
|
||||||
|
|
||||||
children.zip(derived_colors) do |child, raw_color|
|
|
||||||
|
|
||||||
final_color = raw_color.paint
|
final_color = raw_color.paint
|
||||||
final_color.brighten(60) if final_color.dark?
|
final_color.brighten(60) if final_color.dark?
|
||||||
|
|
||||||
@ -49,8 +53,20 @@ class Group < ApplicationRecord
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def affinities
|
||||||
|
GroupAffinity.where(group_a_id: id).or(GroupAffinity.where(group_b_id: id))
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def palette(generation)
|
||||||
|
if generation == 1
|
||||||
|
color.paint.palette.analogous(size: children.count)
|
||||||
|
else
|
||||||
|
color.paint.palette.decreasing_saturation
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def set_color
|
def set_color
|
||||||
return if color.present?
|
return if color.present?
|
||||||
|
|
||||||
|
43
app/models/group_affinity.rb
Normal file
43
app/models/group_affinity.rb
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: group_affinities
|
||||||
|
#
|
||||||
|
# id :bigint not null, primary key
|
||||||
|
# discomfort :float not null
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
# group_a_id :uuid not null
|
||||||
|
# group_b_id :uuid not null
|
||||||
|
#
|
||||||
|
# Indexes
|
||||||
|
#
|
||||||
|
# index_group_affinities_on_group_a_id (group_a_id)
|
||||||
|
# index_group_affinities_on_group_b_id (group_b_id)
|
||||||
|
# uindex_group_pair (LEAST(group_a_id, group_b_id), GREATEST(group_a_id, group_b_id)) UNIQUE
|
||||||
|
#
|
||||||
|
# Foreign Keys
|
||||||
|
#
|
||||||
|
# fk_rails_... (group_a_id => groups.id)
|
||||||
|
# fk_rails_... (group_b_id => groups.id)
|
||||||
|
#
|
||||||
|
class GroupAffinity < ApplicationRecord
|
||||||
|
NEUTRAL = 1
|
||||||
|
MIN_DISCOMFORT = 0
|
||||||
|
MAX_DISCOMFORT = 2
|
||||||
|
|
||||||
|
belongs_to :group_a, class_name: 'Group'
|
||||||
|
belongs_to :group_b, class_name: 'Group'
|
||||||
|
|
||||||
|
validates :discomfort,
|
||||||
|
numericality: { greater_than_or_equal_to: MIN_DISCOMFORT, less_than_or_equal_to: MAX_DISCOMFORT }
|
||||||
|
|
||||||
|
def another_group(group)
|
||||||
|
return nil if group != group_a && group != group_b
|
||||||
|
|
||||||
|
group == group_a ? group_b : group_a
|
||||||
|
end
|
||||||
|
end
|
@ -1,4 +1,6 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
# == Schema Information
|
# == Schema Information
|
||||||
#
|
#
|
||||||
@ -10,18 +12,26 @@
|
|||||||
# 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 not null
|
# group_id :uuid
|
||||||
|
# invitation_id :uuid
|
||||||
|
# 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)
|
||||||
#
|
#
|
||||||
# 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) ON DELETE => cascade
|
||||||
#
|
#
|
||||||
class Guest < ApplicationRecord
|
class Guest < ApplicationRecord
|
||||||
belongs_to :group
|
acts_as_tenant :wedding
|
||||||
|
belongs_to :group, optional: true
|
||||||
|
belongs_to :invitation, optional: true
|
||||||
|
|
||||||
enum :status, {
|
enum :status, {
|
||||||
considered: 0,
|
considered: 0,
|
||||||
@ -35,16 +45,5 @@ class Guest < ApplicationRecord
|
|||||||
|
|
||||||
scope :potential, -> { where.not(status: %i[declined considered]) }
|
scope :potential, -> { where.not(status: %i[declined considered]) }
|
||||||
|
|
||||||
after_save :recalculate_simulations, if: :saved_change_to_status?
|
|
||||||
after_destroy :recalculate_simulations
|
|
||||||
|
|
||||||
has_many :seats, dependent: :delete_all
|
has_many :seats, dependent: :delete_all
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def recalculate_simulations
|
|
||||||
TablesArrangement.delete_all
|
|
||||||
|
|
||||||
ActiveJob.perform_all_later(50.times.map { TableSimulatorJob.new })
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
29
app/models/invitation.rb
Normal file
29
app/models/invitation.rb
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# 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
|
@ -1,4 +1,6 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
# == Schema Information
|
# == Schema Information
|
||||||
#
|
#
|
||||||
@ -10,18 +12,22 @@
|
|||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
# guest_id :uuid not null
|
# guest_id :uuid not null
|
||||||
# tables_arrangement_id :uuid not null
|
# tables_arrangement_id :uuid not null
|
||||||
|
# wedding_id :uuid not null
|
||||||
#
|
#
|
||||||
# Indexes
|
# Indexes
|
||||||
#
|
#
|
||||||
# index_seats_on_guest_id (guest_id)
|
# index_seats_on_guest_id (guest_id)
|
||||||
# index_seats_on_tables_arrangement_id (tables_arrangement_id)
|
# index_seats_on_tables_arrangement_id (tables_arrangement_id)
|
||||||
|
# index_seats_on_wedding_id (wedding_id)
|
||||||
#
|
#
|
||||||
# Foreign Keys
|
# Foreign Keys
|
||||||
#
|
#
|
||||||
# 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
|
||||||
#
|
#
|
||||||
class Seat < ApplicationRecord
|
class Seat < ApplicationRecord
|
||||||
|
acts_as_tenant :wedding
|
||||||
belongs_to :guest
|
belongs_to :guest
|
||||||
belongs_to :table_arrangement
|
belongs_to :tables_arrangement
|
||||||
end
|
end
|
||||||
|
@ -1,17 +1,32 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
# == Schema Information
|
# == Schema Information
|
||||||
#
|
#
|
||||||
# Table name: tables_arrangements
|
# Table name: tables_arrangements
|
||||||
#
|
#
|
||||||
# id :uuid not null, primary key
|
# id :uuid not null, primary key
|
||||||
|
# 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
|
||||||
|
#
|
||||||
|
# Indexes
|
||||||
|
#
|
||||||
|
# index_tables_arrangements_on_wedding_id (wedding_id)
|
||||||
|
#
|
||||||
|
# Foreign Keys
|
||||||
|
#
|
||||||
|
# fk_rails_... (wedding_id => weddings.id) ON DELETE => cascade
|
||||||
#
|
#
|
||||||
class TablesArrangement < ApplicationRecord
|
class TablesArrangement < ApplicationRecord
|
||||||
has_many :seats
|
acts_as_tenant :wedding
|
||||||
|
has_many :seats, dependent: :delete_all
|
||||||
has_many :guests, through: :seats
|
has_many :guests, through: :seats
|
||||||
|
|
||||||
before_create :assign_name
|
before_create :assign_name
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
# == Schema Information
|
# == Schema Information
|
||||||
#
|
#
|
||||||
@ -18,6 +20,7 @@
|
|||||||
# unlock_token :string
|
# unlock_token :string
|
||||||
# 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
|
||||||
#
|
#
|
||||||
# Indexes
|
# Indexes
|
||||||
#
|
#
|
||||||
@ -25,8 +28,15 @@
|
|||||||
# index_users_on_email (email) UNIQUE
|
# index_users_on_email (email) UNIQUE
|
||||||
# index_users_on_reset_password_token (reset_password_token) UNIQUE
|
# index_users_on_reset_password_token (reset_password_token) UNIQUE
|
||||||
# index_users_on_unlock_token (unlock_token) UNIQUE
|
# index_users_on_unlock_token (unlock_token) UNIQUE
|
||||||
|
# index_users_on_wedding_id (wedding_id)
|
||||||
|
#
|
||||||
|
# Foreign Keys
|
||||||
|
#
|
||||||
|
# fk_rails_... (wedding_id => weddings.id) ON DELETE => cascade
|
||||||
#
|
#
|
||||||
class User < ApplicationRecord
|
class User < ApplicationRecord
|
||||||
|
acts_as_tenant :wedding
|
||||||
|
|
||||||
devise :database_authenticatable, :registerable,
|
devise :database_authenticatable, :registerable,
|
||||||
:recoverable, :validatable, :confirmable, :lockable
|
:recoverable, :validatable, :confirmable, :lockable
|
||||||
end
|
end
|
||||||
|
25
app/models/website.rb
Normal file
25
app/models/website.rb
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# 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
|
28
app/models/wedding.rb
Normal file
28
app/models/wedding.rb
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: weddings
|
||||||
|
#
|
||||||
|
# id :uuid not null, primary key
|
||||||
|
# slug :string not null
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
#
|
||||||
|
# Indexes
|
||||||
|
#
|
||||||
|
# index_weddings_on_slug (slug) UNIQUE
|
||||||
|
#
|
||||||
|
class Wedding < ApplicationRecord
|
||||||
|
SLUG_REGEX = /[a-z\d-]+/
|
||||||
|
|
||||||
|
validates :slug, presence: true, uniqueness: true, format: { with: /\A#{SLUG_REGEX}\z/ }
|
||||||
|
|
||||||
|
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
|
@ -1,47 +1,49 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module Expenses
|
module Expenses
|
||||||
class TotalQuery
|
class TotalQuery
|
||||||
|
private attr_reader :wedding
|
||||||
|
def initialize(wedding:)
|
||||||
|
@wedding = wedding
|
||||||
|
end
|
||||||
|
|
||||||
def call
|
def call
|
||||||
ActiveRecord::Base.connection.execute(query).first
|
ActiveRecord::Base.connection.execute(
|
||||||
|
ActiveRecord::Base.sanitize_sql_array([query, { wedding_id: wedding.id }])
|
||||||
|
).first
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def query
|
def query
|
||||||
<<~SQL
|
<<~SQL.squish
|
||||||
WITH guest_count AS (#{guest_count_per_status}),
|
WITH guest_count AS (#{guest_count_per_status}),
|
||||||
expense_summary AS (#{expense_summary})
|
expense_summary AS (#{expense_summary})
|
||||||
SELECT expense_summary.fixed,
|
SELECT guest_count.confirmed as confirmed_guests,
|
||||||
expense_summary.fixed_count,
|
|
||||||
expense_summary.variable,
|
|
||||||
expense_summary.variable_count,
|
|
||||||
expense_summary.total_count,
|
|
||||||
guest_count.confirmed as confirmed_guests,
|
|
||||||
guest_count.projected as projected_guests,
|
guest_count.projected as projected_guests,
|
||||||
expense_summary.fixed + expense_summary.variable * guest_count.confirmed as total,
|
expense_summary.fixed + expense_summary.variable * guest_count.confirmed as total_confirmed,
|
||||||
expense_summary.fixed + expense_summary.variable * guest_count.projected as max_projected,
|
expense_summary.fixed + expense_summary.variable * guest_count.projected as total_projected
|
||||||
(expense_summary.fixed + expense_summary.variable * guest_count.confirmed) / guest_count.confirmed as per_person
|
|
||||||
FROM guest_count, expense_summary;
|
FROM guest_count, expense_summary;
|
||||||
SQL
|
SQL
|
||||||
end
|
end
|
||||||
|
|
||||||
def expense_summary
|
def expense_summary
|
||||||
<<~SQL
|
<<~SQL.squish
|
||||||
SELECT coalesce(sum(amount) filter (where pricing_type = 'fixed'), 0) as fixed,
|
SELECT coalesce(sum(amount) filter (where pricing_type = 'fixed'), 0) as fixed,
|
||||||
coalesce(count(amount) filter (where pricing_type = 'fixed'), 0) as fixed_count,
|
coalesce(sum(amount) filter (where pricing_type = 'per_person'), 0) as variable
|
||||||
coalesce(sum(amount) filter (where pricing_type = 'per_person'), 0) as variable,
|
|
||||||
coalesce(count(amount) filter (where pricing_type = 'per_person'), 0) as variable_count,
|
|
||||||
count(*) as total_count
|
|
||||||
FROM expenses
|
FROM expenses
|
||||||
|
WHERE wedding_id = :wedding_id
|
||||||
SQL
|
SQL
|
||||||
end
|
end
|
||||||
|
|
||||||
def guest_count_per_status
|
def guest_count_per_status
|
||||||
<<~SQL
|
<<~SQL.squish
|
||||||
SELECT COALESCE(count(*) filter(where status = #{Guest.statuses["confirmed"]}), 0) as confirmed,
|
SELECT COALESCE(count(*) filter(where status = #{Guest.statuses['confirmed']}), 0) as confirmed,
|
||||||
COALESCE(count(*) filter(where status IN (#{Guest.statuses.values_at("confirmed", "invited", "tentative").join(",")})), 0) as projected
|
COALESCE(count(*) filter(where status IN (#{Guest.statuses.values_at('confirmed', 'invited', 'tentative').join(',')})), 0) as projected
|
||||||
FROM guests
|
FROM guests
|
||||||
|
WHERE wedding_id = :wedding_id
|
||||||
SQL
|
SQL
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,31 +1,31 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module Groups
|
module Groups
|
||||||
class SummaryQuery
|
class SummaryQuery
|
||||||
def call
|
def call
|
||||||
ActiveRecord::Base.connection.execute(query).to_a
|
Group.left_joins(:guests).group(:id).pluck_to_hash(
|
||||||
|
:id,
|
||||||
|
:name,
|
||||||
|
:icon,
|
||||||
|
:parent_id,
|
||||||
|
:color,
|
||||||
|
*count_expressions
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def query
|
def count_expressions
|
||||||
<<~SQL.squish
|
[
|
||||||
SELECT
|
Arel.sql('count(*) filter (where status IS NOT NULL) as total'),
|
||||||
groups.id,
|
Arel.sql('count(*) filter (where status = 0) as considered'),
|
||||||
groups.name,
|
Arel.sql('count(*) filter (where status = 10) as invited'),
|
||||||
groups.icon,
|
Arel.sql('count(*) filter (where status = 20) as confirmed'),
|
||||||
groups.parent_id,
|
Arel.sql('count(*) filter (where status = 30) as declined'),
|
||||||
groups.color,
|
Arel.sql('count(*) filter (where status = 40) as tentative')
|
||||||
count(*) filter (where status IS NOT NULL) as total,
|
]
|
||||||
count(*) filter (where status = 0) as considered,
|
|
||||||
count(*) filter (where status = 10) as invited,
|
|
||||||
count(*) filter (where status = 20) as confirmed,
|
|
||||||
count(*) filter (where status = 30) as declined,
|
|
||||||
count(*) filter (where status = 40) as tentative
|
|
||||||
FROM groups
|
|
||||||
LEFT JOIN guests on groups.id = guests.group_id
|
|
||||||
GROUP BY groups.id
|
|
||||||
SQL
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class SerializableGroup < JSONAPI::Serializable::Resource
|
class SerializableGroup < JSONAPI::Serializable::Resource
|
||||||
type 'group'
|
type 'group'
|
||||||
|
@ -1,18 +1,16 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class SerializableGuest < JSONAPI::Serializable::Resource
|
class SerializableGuest < JSONAPI::Serializable::Resource
|
||||||
type 'guest'
|
type 'guest'
|
||||||
|
|
||||||
attributes :id, :group_id, :status
|
attributes :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
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class AffinityGroupsHierarchy < Array
|
class AffinityGroupsHierarchy < Array
|
||||||
include Singleton
|
DEFAULT_DISCOMFORT = 1
|
||||||
|
|
||||||
def initialize
|
def initialize
|
||||||
super
|
super
|
||||||
@ -12,6 +14,10 @@ class AffinityGroupsHierarchy < Array
|
|||||||
|
|
||||||
hydrate(group)
|
hydrate(group)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
discomforts
|
||||||
|
invitation_counts
|
||||||
|
freeze
|
||||||
end
|
end
|
||||||
|
|
||||||
def find(id)
|
def find(id)
|
||||||
@ -33,8 +39,43 @@ class AffinityGroupsHierarchy < Array
|
|||||||
@references[id_a].distance_to_common_ancestor(@references[id_b])
|
@references[id_a].distance_to_common_ancestor(@references[id_b])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def discomfort(id_a, id_b)
|
||||||
|
return 0 if id_a == id_b
|
||||||
|
|
||||||
|
@discomforts[uuid_to_int(id_a) + uuid_to_int(id_b)] || DEFAULT_DISCOMFORT
|
||||||
|
end
|
||||||
|
|
||||||
|
def default_discomfort(id_a, id_b)
|
||||||
|
return 0 if id_a == id_b
|
||||||
|
|
||||||
|
dist = distance(id_a, id_b)
|
||||||
|
|
||||||
|
return DEFAULT_DISCOMFORT if dist.nil?
|
||||||
|
|
||||||
|
Rational(dist, dist + 1)
|
||||||
|
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
|
||||||
|
@discomforts ||= GroupAffinity.pluck(:group_a_id, :group_b_id,
|
||||||
|
:discomfort).each_with_object({}) do |(id_a, id_b, discomfort), acc|
|
||||||
|
acc[uuid_to_int(id_a) + uuid_to_int(id_b)] = discomfort
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def uuid_to_int(uuid)
|
||||||
|
uuid.gsub('-', '').hex
|
||||||
|
end
|
||||||
|
|
||||||
def hydrate(group)
|
def hydrate(group)
|
||||||
group.children.each do |child|
|
group.children.each do |child|
|
||||||
register_child(group.id, child.id)
|
register_child(group.id, child.id)
|
||||||
|
20
app/services/libre_captcha.rb
Normal file
20
app/services/libre_captcha.rb
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class LibreCaptcha
|
||||||
|
def id
|
||||||
|
HTTParty.post('http://libre-captcha:8888/v2/captcha',
|
||||||
|
body: {
|
||||||
|
input_type: 'text',
|
||||||
|
level: :hard,
|
||||||
|
media: 'image/png',
|
||||||
|
size: '350x100'
|
||||||
|
}.to_json).then { |raw| JSON.parse(raw)['id'] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def valid?(id:, answer:)
|
||||||
|
HTTParty.post('http://libre-captcha:8888/v2/answer',
|
||||||
|
body: { id:, answer: }.to_json).then { |raw| JSON.parse(raw)['result'] == 'True' }
|
||||||
|
end
|
||||||
|
end
|
@ -1,14 +1,21 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module Tables
|
module Tables
|
||||||
class DiscomfortCalculator
|
class DiscomfortCalculator
|
||||||
private attr_reader :table
|
private attr_reader :table, :hierarchy
|
||||||
def initialize(table:)
|
def initialize(table:, hierarchy: AffinityGroupsHierarchy.new)
|
||||||
@table = table
|
@table = table
|
||||||
|
@hierarchy = hierarchy
|
||||||
end
|
end
|
||||||
|
|
||||||
def calculate
|
def calculate
|
||||||
table_size_penalty + 10 * (cohesion_penalty * 1.0 / table.size)
|
breakdown.values.sum
|
||||||
|
end
|
||||||
|
|
||||||
|
def breakdown
|
||||||
|
@breakdown ||= { table_size_penalty:, cohesion_penalty:, invitations_penalty: }
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@ -28,6 +35,16 @@ module Tables
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def cohesion_penalty
|
||||||
|
10 * (cohesion_discomfort * 1.0 / table.size)
|
||||||
|
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
|
||||||
@ -35,14 +52,9 @@ module Tables
|
|||||||
#
|
#
|
||||||
# @return [Number] Total discomfort of the table.
|
# @return [Number] Total discomfort of the table.
|
||||||
#
|
#
|
||||||
def cohesion_penalty
|
def cohesion_discomfort
|
||||||
table.map(&:group_id).tally.to_a.combination(2).sum do |(a, count_a), (b, count_b)|
|
table.map(&:group_id).tally.to_a.combination(2).sum do |(a, count_a), (b, count_b)|
|
||||||
distance = AffinityGroupsHierarchy.instance.distance(a, b)
|
count_a * count_b * hierarchy.discomfort(a, b)
|
||||||
|
|
||||||
next count_a * count_b if distance.nil?
|
|
||||||
next 0 if distance.zero?
|
|
||||||
|
|
||||||
count_a * count_b * Rational(distance, distance + 1)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,21 +1,32 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative '../../extensions/tree_node_extension'
|
require_relative '../../extensions/tree_node_extension'
|
||||||
|
|
||||||
module Tables
|
module Tables
|
||||||
class Distribution
|
class Distribution
|
||||||
attr_accessor :tables, :min_per_table, :max_per_table
|
class << self
|
||||||
|
def digest(wedding)
|
||||||
def initialize(min_per_table:, max_per_table:)
|
Digest::UUID.uuid_v5(wedding.id, wedding.guests.potential.order(:id).pluck(:id).join)
|
||||||
@min_per_table = min_per_table
|
end
|
||||||
@max_per_table = max_per_table
|
|
||||||
@tables = []
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def random_distribution(people)
|
attr_accessor :tables, :min_per_table, :max_per_table, :hierarchy, :tables_arrangement_id
|
||||||
|
|
||||||
|
def initialize(min_per_table:, max_per_table:, tables_arrangement_id:, hierarchy: AffinityGroupsHierarchy.new)
|
||||||
|
@min_per_table = min_per_table
|
||||||
|
@max_per_table = max_per_table
|
||||||
|
@hierarchy = hierarchy
|
||||||
|
@tables = []
|
||||||
|
@tables_arrangement_id = tables_arrangement_id
|
||||||
|
end
|
||||||
|
|
||||||
|
def random_distribution(people, random: Random.new)
|
||||||
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
|
||||||
@tables = people.in_groups(rand(min_tables..max_tables), false)
|
table_size = random.rand(min_tables..max_tables)
|
||||||
|
@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 }
|
||||||
@ -31,21 +42,24 @@ module Tables
|
|||||||
"#{@tables.count} tables, discomfort: #{discomfort}"
|
"#{@tables.count} tables, discomfort: #{discomfort}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def pretty_print
|
|
||||||
@tables.map.with_index do |table, i|
|
|
||||||
"Table #{i + 1} (#{table.count} ppl): (#{local_discomfort(table)}) #{table.map(&:name).join(', ')}"
|
|
||||||
end.join("\n")
|
|
||||||
end
|
|
||||||
|
|
||||||
def deep_dup
|
def deep_dup
|
||||||
self.class.new(min_per_table: @min_per_table, max_per_table: @max_per_table).tap do |new_distribution|
|
self.class.new(
|
||||||
|
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.create!
|
arrangement = TablesArrangement.find(tables_arrangement_id)
|
||||||
|
|
||||||
|
self.tables_arrangement_id = arrangement.id
|
||||||
|
|
||||||
|
arrangement.seats.delete_all
|
||||||
|
|
||||||
records_to_store = []
|
records_to_store = []
|
||||||
|
|
||||||
@ -57,14 +71,17 @@ module Tables
|
|||||||
|
|
||||||
Seat.insert_all!(records_to_store)
|
Seat.insert_all!(records_to_store)
|
||||||
|
|
||||||
arrangement.update!(discomfort:)
|
arrangement.update!(
|
||||||
|
discomfort:,
|
||||||
|
digest: self.class.digest(tables.first.first.wedding)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def local_discomfort(table)
|
def local_discomfort(table)
|
||||||
table.discomfort ||= DiscomfortCalculator.new(table:).calculate
|
table.discomfort ||= DiscomfortCalculator.new(table:, hierarchy:).calculate
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module Tables
|
module Tables
|
||||||
class Shift
|
class Shift
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module Tables
|
module Tables
|
||||||
class Swap
|
class Swap
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module Tables
|
module Tables
|
||||||
class Table < Set
|
class Table < Set
|
||||||
|
33
app/services/tables/wheel_swap.rb
Normal file
33
app/services/tables/wheel_swap.rb
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# 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
|
@ -1,7 +1,11 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
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
|
||||||
@ -9,48 +13,100 @@ 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
|
||||||
raise 'No target function defined' unless @target_function
|
check_preconditions!
|
||||||
raise 'No perturbations defined' unless @perturbations
|
|
||||||
raise 'No initial solution defined' unless @initial_solution
|
|
||||||
|
|
||||||
@best_solution = @initial_solution
|
@current_solution = @initial_solution
|
||||||
@best_score = @target_function.call(@best_solution)
|
@best_score = @target_function.call(@current_solution)
|
||||||
|
|
||||||
puts "Initial score: #{@best_score.to_f}"
|
run_all_optimizations
|
||||||
|
|
||||||
self.class.sequence(@perturbations).each do |perturbation|
|
@progress_notifier&.call(Rational(1, ITERATIONS + 1))
|
||||||
puts "Running perturbation: #{perturbation.name}"
|
|
||||||
optimize(perturbation.new(@best_solution))
|
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 optimize(perturbation)
|
def check_preconditions!
|
||||||
perturbation.each do |alternative_solution|
|
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
|
||||||
|
optimized = false
|
||||||
|
|
||||||
|
optimization_klass.new(@current_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
|
||||||
|
|
||||||
@best_solution = alternative_solution.deep_dup
|
@current_solution = alternative_solution.deep_dup
|
||||||
@best_score = score
|
@best_score = score
|
||||||
|
optimized = true
|
||||||
|
Rails.logger.debug { "[#{optimization_klass}] Found better solution with score: #{score}" }
|
||||||
|
|
||||||
puts "New lowest score: #{@best_score.to_f}"
|
break
|
||||||
|
end
|
||||||
|
|
||||||
return optimize(perturbation.class.new(@best_solution))
|
return unless optimized
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
17
app/views/admin_mailer/attendance_change_email.html.erb
Normal file
17
app/views/admin_mailer/attendance_change_email.html.erb
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<%# 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>
|
9
app/views/admin_mailer/attendance_change_email.text.erb
Normal file
9
app/views/admin_mailer/attendance_change_email.text.erb
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<%# 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") %>
|
7
app/views/admin_mailer/invitations_pdf_email.html.erb
Normal file
7
app/views/admin_mailer/invitations_pdf_email.html.erb
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<%# Copyright (C) 2024-2025 LibreWeddingPlanner contributors %>
|
||||||
|
|
||||||
|
<p><%= I18n.t('admin_mailer.greeting') %>,</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<%= I18n.t('admin_mailer.invitations_pdf_email.paragraph_1') %>
|
||||||
|
</p>
|
5
app/views/admin_mailer/invitations_pdf_email.txt.erb
Normal file
5
app/views/admin_mailer/invitations_pdf_email.txt.erb
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<%# Copyright (C) 2024-2025 LibreWeddingPlanner contributors %>
|
||||||
|
|
||||||
|
<%= I18n.t('admin_mailer.greeting') %>,
|
||||||
|
|
||||||
|
<%= I18n.t('admin_mailer.invitations_pdf_email.paragraph_1') %>
|
33
app/views/invitations/sheet.html.erb
Normal file
33
app/views/invitations/sheet.html.erb
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<%# 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 %>
|
@ -1,4 +1,4 @@
|
|||||||
<%# Copyright (C) 2024 Manuel Bustillo %>
|
<%# Copyright (C) 2024-2025 LibreWeddingPlanner contributors %>
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
<%# Copyright (C) 2024 Manuel Bustillo %>
|
<%# Copyright (C) 2024-2025 LibreWeddingPlanner contributors %>
|
||||||
|
|
||||||
<%= yield %>
|
<%= yield %>
|
||||||
|
12
app/views/layouts/pdf.html.erb
Normal file
12
app/views/layouts/pdf.html.erb
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<%# Copyright (C) 2024-2025 LibreWeddingPlanner contributors %>
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset='utf-8' />
|
||||||
|
</head>
|
||||||
|
<div id="content">
|
||||||
|
<%= yield %>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,7 @@
|
|||||||
|
<%# Copyright (C) 2024-2025 LibreWeddingPlanner contributors %>
|
||||||
|
|
||||||
|
<p>Welcome <%= @email %>!</p>
|
||||||
|
|
||||||
|
<p>You can confirm your account email through the link below:</p>
|
||||||
|
|
||||||
|
<p><%= link_to 'Confirm my account', confirmation_url(slug: ActsAsTenant.current_tenant&.slug, confirmation_token: @token) %></p>
|
9
app/views/users/mailer/email_changed.html.erb
Normal file
9
app/views/users/mailer/email_changed.html.erb
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<%# Copyright (C) 2024-2025 LibreWeddingPlanner contributors %>
|
||||||
|
|
||||||
|
<p>Hello <%= @email %>!</p>
|
||||||
|
|
||||||
|
<% if @resource.try(:unconfirmed_email?) %>
|
||||||
|
<p>We're contacting you to notify you that your email is being changed to <%= @resource.unconfirmed_email %>.</p>
|
||||||
|
<% else %>
|
||||||
|
<p>We're contacting you to notify you that your email has been changed to <%= @resource.email %>.</p>
|
||||||
|
<% end %>
|
5
app/views/users/mailer/password_change.html.erb
Normal file
5
app/views/users/mailer/password_change.html.erb
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<%# Copyright (C) 2024-2025 LibreWeddingPlanner contributors %>
|
||||||
|
|
||||||
|
<p>Hello <%= @resource.email %>!</p>
|
||||||
|
|
||||||
|
<p>We're contacting you to notify you that your password has been changed.</p>
|
10
app/views/users/mailer/reset_password_instructions.html.erb
Normal file
10
app/views/users/mailer/reset_password_instructions.html.erb
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<%# Copyright (C) 2024-2025 LibreWeddingPlanner contributors %>
|
||||||
|
|
||||||
|
<p>Hello <%= @resource.email %>!</p>
|
||||||
|
|
||||||
|
<p>Someone has requested a link to change your password. You can do this through the link below.</p>
|
||||||
|
|
||||||
|
<p><%= link_to 'Change my password', edit_password_url(slug: ActsAsTenant.current_tenant&.slug, reset_password_token: @token) %></p>
|
||||||
|
|
||||||
|
<p>If you didn't request this, please ignore this email.</p>
|
||||||
|
<p>Your password won't change until you access the link above and create a new one.</p>
|
9
app/views/users/mailer/unlock_instructions.html.erb
Normal file
9
app/views/users/mailer/unlock_instructions.html.erb
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<%# Copyright (C) 2024-2025 LibreWeddingPlanner contributors %>
|
||||||
|
|
||||||
|
<p>Hello <%= @resource.email %>!</p>
|
||||||
|
|
||||||
|
<p>Your account has been locked due to an excessive number of unsuccessful sign in attempts.</p>
|
||||||
|
|
||||||
|
<p>Click the link below to unlock your account:</p>
|
||||||
|
|
||||||
|
<p><%= link_to 'Unlock my account', unlock_url(slug: ActsAsTenant.current_tenant&.slug, unlock_token: @token) %></p>
|
1
bin/jobs
1
bin/jobs
@ -3,4 +3,5 @@
|
|||||||
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)
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
# This file is used by Rack-based servers to start the application.
|
# This file is used by Rack-based servers to start the application.
|
||||||
|
|
||||||
require_relative "config/environment"
|
require_relative 'config/environment'
|
||||||
|
|
||||||
run Rails.application
|
run Rails.application
|
||||||
Rails.application.load_server
|
Rails.application.load_server
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
require_relative 'boot'
|
require_relative 'boot'
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
|
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
|
||||||
|
|
||||||
|
@ -83,6 +83,7 @@ test:
|
|||||||
#
|
#
|
||||||
production:
|
production:
|
||||||
<<: *default
|
<<: *default
|
||||||
|
host: db
|
||||||
database: wedding_planner_production
|
database: wedding_planner_production
|
||||||
username: wedding_planner
|
username: wedding_planner
|
||||||
password: <%= ENV["WEDDING_PLANNER_DATABASE_PASSWORD"] %>
|
password: <%= ENV["WEDDING_PLANNER_DATABASE_PASSWORD"] %>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
# Load the Rails application.
|
# Load the Rails application.
|
||||||
require_relative "application"
|
require_relative "application"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
require "active_support/core_ext/integer/time"
|
require "active_support/core_ext/integer/time"
|
||||||
|
|
||||||
@ -79,4 +79,5 @@ 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
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
require "active_support/core_ext/integer/time"
|
require "active_support/core_ext/integer/time"
|
||||||
|
|
||||||
@ -72,6 +72,20 @@ 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.
|
||||||
@ -92,6 +106,10 @@ Rails.application.configure do
|
|||||||
# "example.com", # Allow requests from example.com
|
# "example.com", # Allow requests from example.com
|
||||||
# /.*\.example\.com/ # Allow requests from subdomains like `www.example.com`
|
# /.*\.example\.com/ # Allow requests from subdomains like `www.example.com`
|
||||||
# ]
|
# ]
|
||||||
|
|
||||||
|
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" } }
|
||||||
end
|
end
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
require "active_support/core_ext/integer/time"
|
require "active_support/core_ext/integer/time"
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
# Pin npm packages by running ./bin/importmap
|
# Pin npm packages by running ./bin/importmap
|
||||||
|
|
||||||
|
9
config/initializers/acts_as_tenant.rb
Normal file
9
config/initializers/acts_as_tenant.rb
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
|
ActsAsTenant.configure do |config|
|
||||||
|
config.require_tenant = !Rails.env.test?
|
||||||
|
end
|
||||||
|
|
||||||
|
Rails.application.console do
|
||||||
|
ActsAsTenant.current_tenant = Wedding.first
|
||||||
|
end
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
# Be sure to restart your server when you modify this file.
|
# Be sure to restart your server when you modify this file.
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
Chroma.define_palette :decreasing_saturation do
|
Chroma.define_palette :decreasing_saturation do
|
||||||
spin(20).desaturate(40)
|
spin(20).desaturate(40)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
# Be sure to restart your server when you modify this file.
|
# Be sure to restart your server when you modify this file.
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
# config/initializers/cors.rb
|
# config/initializers/cors.rb
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
@ -246,7 +246,7 @@ Devise.setup do |config|
|
|||||||
# Turn scoped views on. Before rendering "sessions/new", it will first check for
|
# Turn scoped views on. Before rendering "sessions/new", it will first check for
|
||||||
# "users/sessions/new". It's turned off by default because it's slower if you
|
# "users/sessions/new". It's turned off by default because it's slower if you
|
||||||
# are using only default views.
|
# are using only default views.
|
||||||
# config.scoped_views = false
|
config.scoped_views = true
|
||||||
|
|
||||||
# Configure the default scope given to Warden. By default it's the first
|
# Configure the default scope given to Warden. By default it's the first
|
||||||
# devise role declared in your routes (usually :user).
|
# devise role declared in your routes (usually :user).
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
# Be sure to restart your server when you modify this file.
|
# Be sure to restart your server when you modify this file.
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
# Be sure to restart your server when you modify this file.
|
# Be sure to restart your server when you modify this file.
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
# Be sure to restart your server when you modify this file.
|
# Be sure to restart your server when you modify this file.
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
Rswag::Api.configure do |c|
|
Rswag::Api.configure do |c|
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
Rswag::Ui.configure do |c|
|
Rswag::Ui.configure do |c|
|
||||||
|
|
||||||
@ -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.swagger_endpoint '/api/api-docs/v1/swagger.yaml', 'API V1 Docs'
|
c.openapi_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
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2024 Manuel Bustillo
|
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
|
||||||
|
|
||||||
class Numeric
|
class Numeric
|
||||||
def to_currency
|
def to_currency
|
||||||
@ -10,4 +10,10 @@ 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
|
32
config/initializers/wicked_pdf.rb
Normal file
32
config/initializers/wicked_pdf.rb
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# 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
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user