Compare commits

..

571 Commits
nginx ... main

Author SHA1 Message Date
db5b0809fa Merge pull request 'Update dependency annotaterb to v4.19.0' () from renovate/annotaterb-4.x-lockfile into main
Reviewed-on: 
2025-09-17 19:21:32 +00:00
feb6dc6b6c Merge pull request 'Update dependency pg to v1.6.2' () from renovate/pg-1.x-lockfile into main
Reviewed-on: 
2025-09-17 18:53:12 +00:00
3c4c347217 Merge pull request 'Update actions/checkout action to v5' () from renovate/actions-checkout-5.x into main
Reviewed-on: 
2025-09-17 18:09:59 +00:00
b1a2484a88 Merge pull request 'Update dependency rails to v8.0.2.1' () from renovate/rails-8.x-lockfile into main
Reviewed-on: 
2025-09-17 18:09:04 +00:00
b0c9c6ad87 Merge pull request 'Update dependency rubocop-rails to v2.33.3' () from renovate/rubocop-rails-2.x-lockfile into main
Reviewed-on: 
2025-09-17 07:11:43 +00:00
Renovate Bot
7c593e2342 Update actions/checkout action to v5 2025-09-16 22:18:25 +00:00
Renovate Bot
838e52a0a0 Update dependency rubocop-rails to v2.33.3 2025-09-16 22:17:39 +00:00
Renovate Bot
990d51f786 Update dependency annotaterb to v4.19.0 2025-09-16 22:16:33 +00:00
Renovate Bot
de878594ea Update dependency rails to v8.0.2.1 2025-09-16 22:16:11 +00:00
Renovate Bot
06c103e617 Update dependency pg to v1.6.2 2025-09-16 22:15:44 +00:00
e28751521d Merge pull request 'Persist and expose via API the progress of the tables arrangement simulations' () from arrangements-status into main
Reviewed-on: 
2025-09-16 00:39:40 +00:00
0502bc4552
Disable a rubocop alert 2025-09-15 23:17:00 +02:00
1760149fbb Merge pull request 'Update dependency factory_bot_rails to v6.5.1' () from renovate/factory_bot_rails-6.x-lockfile into main
Reviewed-on: 
2025-09-15 21:10:10 +00:00
7d8ecfd0e3
Refactor class to reduce complexity of #run method 2025-09-15 23:04:02 +02:00
78ab27a697
Fix specs 2025-09-15 22:52:41 +02:00
4afd40b02a Merge pull request 'Update dependency rspec-rails to v8.0.2' () from renovate/rspec-rails-8.x-lockfile into main
Reviewed-on: 
2025-09-11 17:19:06 +00:00
Renovate Bot
43ba96868d Update dependency rspec-rails to v8.0.2 2025-09-11 14:14:21 +00:00
Renovate Bot
00b4e8edfc Update dependency factory_bot_rails to v6.5.1 2025-09-11 14:14:10 +00:00
12174b6f20 Persist VNS calculation progress whenever an improvement has been made 2025-09-08 22:44:54 +02:00
0d1b64256d Provide notification callbacks for progress and new solutions 2025-09-08 16:32:13 +02:00
ac659bef86 Update Tables::Distribution#save! to consider that the distribution may already be persisted 2025-09-08 15:51:43 +02:00
5e06c2acca Merge pull request 'Update dependency puma to v6.6.1' () from renovate/puma-6.x-lockfile into main
Reviewed-on: 
2025-08-07 13:50:21 +00:00
Renovate Bot
9d656d3759 Update dependency puma to v6.6.1 2025-08-07 02:04:50 +00:00
4475fc126d Merge pull request 'Update dependency importmap-rails to v2.2.2' () from renovate/importmap-rails-2.x-lockfile into main
Reviewed-on: 
2025-08-06 15:11:22 +00:00
d7cf620d0a Merge pull request 'Update dependency annotaterb to v4.18.0' () from renovate/annotaterb-4.x-lockfile into main
Reviewed-on: 
2025-08-06 14:14:16 +00:00
de198e2978 Merge pull request 'Update dependency rubocop to v1.79.2' () from renovate/rubocop-1.x-lockfile into main
Reviewed-on: 
2025-08-06 14:14:10 +00:00
Renovate Bot
5e69d170a2 Update dependency importmap-rails to v2.2.2 2025-08-06 02:05:25 +00:00
Renovate Bot
e1bf362467 Update dependency annotaterb to v4.18.0 2025-08-06 02:05:16 +00:00
Renovate Bot
c6ce5d62c0 Update dependency rubocop to v1.79.2 2025-08-06 02:05:04 +00:00
afc147643b Merge pull request 'Update dependency rubocop to v1.79.1' () from renovate/rubocop-1.x-lockfile into main
Reviewed-on: 
2025-08-05 14:48:16 +00:00
Renovate Bot
d5bd6d865e Update dependency rubocop to v1.79.1 2025-08-05 02:06:41 +00:00
9c0b64f427 Merge pull request 'Update dependency pg to v1.6.1' () from renovate/pg-1.x-lockfile into main 2025-08-05 02:06:13 +00:00
Renovate Bot
edd56d46f7 Update dependency pg to v1.6.1 2025-08-04 02:08:37 +00:00
dd14a96e98 Expose and document the new status attribute in the tables arrangements controller 2025-08-01 12:29:13 +02:00
75a0191d40 Add a new status column to tables arrangements table 2025-08-01 12:25:43 +02:00
8662652e1a Merge pull request 'Add a perturbation step to the VNS engine' () from vns-perturbations into main
Reviewed-on: 
2025-07-24 12:21:01 +00:00
3260b0b422 Add copyright notice 2025-07-24 11:46:28 +00:00
81f1e79b6d Merge branch 'main' into vns-perturbations 2025-07-24 11:45:19 +00:00
d18bddb31a Remove commented-out code 2025-07-24 13:42:47 +02:00
3f158d7906 Merge pull request 'Update dependency solid_queue to v1.2.1' () from renovate/solid_queue-1.x-lockfile into main
Reviewed-on: 
2025-07-24 11:41:15 +00:00
db85580c1f Introduce a wheel swap perturbation as part of the VNS engine process 2025-07-24 13:41:01 +02:00
Renovate Bot
76ed4229ea Update dependency solid_queue to v1.2.1 2025-07-24 02:04:54 +00:00
4befb8505b Run perturbation on top of the best solution so far 2025-07-23 11:02:32 +02:00
b1df5d2f53 Shuffle guests in WheelSwap before assigning them to new tables 2025-07-23 11:01:38 +02:00
a1f06dae5b Define a WheelSwap class that randomly swaps one guest from each table 2025-07-23 10:22:41 +02:00
e8a88b50e2 Initialize Tables::WheelSwap class 2025-07-22 16:26:28 +02:00
185f359942 Include additional debugging messages 2025-07-22 15:56:25 +02:00
543b53d938 Initialize empty set of perturbations and add debug messages 2025-07-22 15:53:06 +02:00
c8b88ab3b1 Merge pull request 'Introduce an invitations penalty for solutions that split guests in the same invitation' () from invitations-penalty into main
Reviewed-on: 
2025-07-22 13:49:28 +00:00
e1a5e4f73e Rename perturbation -> optimization to reflect the nature of swap and shift operations 2025-07-22 15:39:30 +02:00
036cc57aa2 Introduce an invitations penalty for solutions that split guests in the same invitation 2025-07-22 15:33:13 +02:00
4dfd428ce4 Merge pull request 'Remove deprecation warning by renaming swagger_endpoint -> openapi_endpoint' () from deprecation-warning-swagger into main
Reviewed-on: 
2025-07-22 08:09:50 +00:00
51922b4f15 Merge pull request 'Introduce specs for the method Tables::DiscomfortCalculator#cohesion_penalty' () from specs-cohesion_penalty into main
Reviewed-on: 
2025-07-22 07:53:06 +00:00
1e3a49adb8 Remove deprecation warning by renaming swagger_endpoint -> openapi_endpoint 2025-07-22 09:49:33 +02:00
7596032284 Introduce specs for the method Tables::DiscomfortCalculator#cohesion_penalty 2025-07-22 09:40:05 +02:00
5dce77c29d Merge pull request 'Define a benchmark to measure the VNS performance and prevent redundant hierarchy calculations' () from benchmark into main
Reviewed-on: 
2025-07-18 14:26:10 +00:00
03e09c74a0 Rubocop fixes 2025-07-18 16:09:28 +02:00
da51a073cc Define a benchmark to measure the VNS performance and prevent redundant hierarchy calculations 2025-07-18 16:05:02 +02:00
1953ba9d7f Merge pull request 'Update dependency redis to v5.4.1' () from renovate/redis-5.x-lockfile into main
Reviewed-on: 
2025-07-18 12:15:14 +00:00
Renovate Bot
bea90af59d Update dependency redis to v5.4.1 2025-07-18 02:06:51 +00:00
c1c90f4db1 Merge pull request 'Update dependency annotaterb to v4.17.0' () from renovate/annotaterb-4.x-lockfile into main 2025-07-17 02:06:40 +00:00
Renovate Bot
bcddb802b7 Update dependency annotaterb to v4.17.0 2025-07-15 02:04:13 +00:00
edfb7227cf Merge pull request 'Update dependency solid_queue to v1.2.0' () from renovate/solid_queue-1.x-lockfile into main 2025-07-15 02:04:00 +00:00
Renovate Bot
b20078a115 Update dependency solid_queue to v1.2.0 2025-07-12 02:06:34 +00:00
71562e8143 Merge pull request 'Update dependency rubocop to v1.78.0' () from renovate/rubocop-1.x-lockfile into main
Reviewed-on: 
2025-07-09 13:19:30 +00:00
Renovate Bot
6478760b8d Update dependency rubocop to v1.78.0 2025-07-09 02:04:57 +00:00
5cb4d14343 Merge pull request 'Define a worker process and endpoint to send invitations PDF via email' () from qr-doc-background-job into main
Reviewed-on: 
2025-07-08 17:37:09 +00:00
397a0f34ff Define a worker process and endpoint to send invitations PDF via email 2025-07-08 19:23:16 +02:00
4496deeef5 Merge pull request 'Update FROM address for email delivery' () from update-from into main
Reviewed-on: 
2025-07-06 17:57:48 +00:00
de87b6c46b Merge pull request 'Enable TLS in email delivery' () from tls-email into main
Reviewed-on: 
2025-07-06 17:46:08 +00:00
eabba2109a Update FROM address for email delivery 2025-07-06 19:40:47 +02:00
1586f88986 Enable TLS in email delivery 2025-07-06 19:32:31 +02:00
c42eb4e576 Merge pull request 'Send email to organizers whenever a guest changes their attendance status' () from status-change-email into main
Reviewed-on: 
2025-07-06 16:53:49 +00:00
29b3fca80c Add copyright notice 2025-07-06 16:32:02 +00:00
d236e459cd Send an email whenever an anonymous session updates the attendance status of a guest 2025-07-06 18:30:35 +02:00
ff8918a1d4 Define a new email that will be sent to admins when a guest changes their attendance status 2025-07-06 18:11:08 +02:00
45313daba2 Merge pull request 'Configure SMTP settings' () from smtp-settings into main
Reviewed-on: 
2025-07-05 11:56:23 +00:00
ba97a3155a Fix syntax error 2025-07-05 13:44:54 +02:00
b3f339a02b Avoid error in case SMTP configuration is not present 2025-07-05 13:33:56 +02:00
d6607cd997 Fix location of SMTP secrets 2025-07-05 13:27:04 +02:00
ba8eb8b85e Configure SMTP settings 2025-07-05 13:16:27 +02:00
717ee35015 Merge pull request 'Update dependency faker to v3.5.2' () from renovate/faker-3.x-lockfile into main
Reviewed-on: 
2025-07-02 05:48:15 +00:00
Renovate Bot
1626056456 Update dependency faker to v3.5.2 2025-07-02 02:06:43 +00:00
f46f25b799 Merge pull request 'Extend the invitations index endpoint to return a PDF file with QRs for invitations' () from invitations-pdf into main
Reviewed-on: 
2025-06-30 22:58:06 +00:00
beda9ff870 Add copyright notice 2025-06-30 22:14:30 +00:00
f3c9d82074 Extend the invitations index endpoint to return a PDF file with QRs for invitations 2025-07-01 00:12:56 +02:00
de92ec0469 Merge pull request 'Update dependency rubocop to v1.77.0' () from renovate/rubocop-1.x-lockfile into main
Reviewed-on: 
2025-06-22 19:07:22 +00:00
Renovate Bot
6a36a2bda3 Update dependency rubocop to v1.77.0 2025-06-21 02:06:01 +00:00
c30bdbd513 Merge pull request 'Update dependency rspec-rails to v8.0.1' () from renovate/rspec-rails-8.x-lockfile into main
Reviewed-on: 
2025-06-20 09:13:11 +00:00
Renovate Bot
a1ed94794a Update dependency rspec-rails to v8.0.1 2025-06-20 02:05:35 +00:00
9505b6d801 Merge pull request 'Update dependency debug to v1.11.0' () from renovate/debug-1.x-lockfile into main
Reviewed-on: 
2025-06-19 06:59:19 +00:00
f1ba084e04 Merge pull request 'Update dependency annotaterb to v4.16.0' () from renovate/annotaterb-4.x-lockfile into main
Reviewed-on: 
2025-06-19 06:33:47 +00:00
Renovate Bot
4c262b3ddd Update dependency annotaterb to v4.16.0 2025-06-19 02:06:48 +00:00
Renovate Bot
9f05d74bb4 Update dependency debug to v1.11.0 2025-06-18 02:06:39 +00:00
54d891070c Merge pull request 'Update dependency factory_bot_rails to v6.5.0' () from renovate/factory_bot_rails-6.x-lockfile into main
Reviewed-on: 
2025-06-15 10:59:23 +00:00
b32af61358 Merge pull request 'Return guests ordered by invitation within same group' () from guests-order into main
Reviewed-on: 
2025-06-15 09:35:43 +00:00
7f4bd8a8df Return guests ordered by invitation within same group 2025-06-15 11:24:57 +02:00
Renovate Bot
46b59ac617 Update dependency factory_bot_rails to v6.5.0 2025-06-14 02:04:16 +00:00
f02a6b6a3d Merge pull request 'Allow updating the status of guests from unauthenticated sessions' () from guest-update-from-invitation into main
Reviewed-on: 
2025-06-12 21:26:00 +00:00
efb5cf64f5 Minor changes 2025-06-12 23:01:03 +02:00
9a99981f67 Allow updating the status of guests from unauthenticated sessions 2025-06-12 22:53:50 +02:00
9e9ee0c995 Merge pull request 'Define a new public endpoint to get information about an invitation' () from invitations-show-endpoint into main
Reviewed-on: 
2025-06-12 17:32:27 +00:00
82a39bce82 Define a new public endpoint to get information about an invitation 2025-06-12 19:21:53 +02:00
d953e7c4a2 Merge pull request 'Update dependency ruby to v3.4.3' () from renovate/ruby-3.x into main
Reviewed-on: 
2025-06-11 19:27:11 +00:00
4e4845a8c3 Merge pull request 'Build docker image in the main branch' () from run-build into main
Reviewed-on: 
2025-06-11 19:03:20 +00:00
60436caf5b Merge branch 'main' into renovate/ruby-3.x 2025-06-11 18:58:58 +00:00
7f4554d21a Build docker image in the main branch 2025-06-11 20:49:50 +02:00
509aca79bf Merge pull request 'Prevent PR jobs from running in the main branch' () from avoid-checks-main into main
Reviewed-on: 
2025-06-11 17:20:39 +00:00
f3088470da Use latest version of v1 ruby-setup 2025-06-11 19:09:48 +02:00
b9770b7a9f Prevent PR jobs from running in the main branch 2025-06-11 19:06:58 +02:00
Renovate Bot
2fe72a34e4 Update dependency rubocop to v1.76.1 2025-06-10 02:05:32 +00:00
d1791e12c7 Merge pull request 'Do not require authentication to access the public website content' () from public-access-website into main
Reviewed-on: 
2025-06-09 06:35:59 +00:00
3832f1d840 Do not require authentication to access the public website content 2025-06-09 08:26:26 +02:00
4e2d046b18 Merge pull request 'Store website content as HTML' () from store-website into main
Reviewed-on: 
2025-06-08 21:35:43 +00:00
c0ba659f29 Merge pull request 'Return guest that has just been created' () from api/return-guest into main
Reviewed-on: 
2025-06-08 21:31:40 +00:00
fc9911abf4 Fix rubocop ofenses 2025-06-08 23:13:44 +02:00
3e363ac7dd Add copyright notice 2025-06-08 19:03:39 +00:00
cc10fbfb83 Store website content as HTML 2025-06-08 20:59:09 +02:00
4a107d6728 Return guest that has just been created 2025-06-08 17:38:34 +02:00
9461fa5255 Merge pull request 'Document required changes to /etc/hosts file' () from bustikiller-patch-2 into main
Reviewed-on: 
2025-06-08 07:27:42 +00:00
Renovate Bot
96b6f4ac9e Update dependency rubocop to v1.76.0 2025-06-05 02:04:50 +00:00
Renovate Bot
afb103bd50 Update dependency turbo-rails to v2.0.16 2025-06-03 02:05:18 +00:00
8029de4bef Merge branch 'main' into bustikiller-patch-2 2025-06-02 06:30:54 +00:00
e47fcd3f22 Merge pull request 'Update dependency rack-cors to v3' () from renovate/rack-cors-3.x-lockfile into main
Reviewed-on: 
2025-06-02 06:30:41 +00:00
Renovate Bot
1281060bf8 Update dependency rack-cors to v3 2025-06-02 02:03:42 +00:00
b1c95de07c Merge pull request 'Update dependency rspec-rails to v8' () from renovate/rspec-rails-8.x into main
Reviewed-on: 
2025-06-01 20:52:05 +00:00
ac6df3b75b Merge branch 'main' into renovate/rspec-rails-8.x 2025-06-01 20:07:49 +00:00
8e866d8a35 Merge branch 'main' into bustikiller-patch-2 2025-06-01 20:07:09 +00:00
bc07d63985 Merge pull request 'Update dependency csv to v3.3.5' () from renovate/csv-3.x-lockfile into main
Reviewed-on: 
2025-06-01 20:06:42 +00:00
f957d0acb3 Merge pull request 'Define and seed an invitation model and controller' () from invitations into main
Reviewed-on: 
2025-06-01 18:36:49 +00:00
4efb912e38 Merge branch 'main' into renovate/ruby-3.x 2025-06-01 18:03:21 +00:00
6100ce0b99 Make migration reversible 2025-06-01 19:57:14 +02:00
11f98bd712 Add copyright notice 2025-06-01 17:51:49 +00:00
27ce9cac62 Fix rubocop ofenses 2025-06-01 19:51:08 +02:00
0a2cf6a5eb Remove unnecessary param 2025-06-01 19:50:22 +02:00
622128a29a Add copyright notice 2025-06-01 15:59:18 +00:00
5fb26f42d6 Define a controller for invitations 2025-06-01 17:58:27 +02:00
Renovate Bot
bd1ff7f92d Update dependency rspec-rails to v8 2025-06-01 02:06:39 +00:00
Renovate Bot
46bd0d71bd Update dependency csv to v3.3.5 2025-06-01 02:06:04 +00:00
Renovate Bot
c5e1c2aef1 Update dependency annotaterb to v4.15.0 2025-05-31 02:05:00 +00:00
Renovate Bot
bbe9983070 Update dependency rubocop to v1.75.8 2025-05-29 02:06:28 +00:00
Renovate Bot
36345ea74c Update dependency bootsnap to v1.18.6 2025-05-19 02:04:57 +00:00
Renovate Bot
b5ed6a7280 Update dependency rubocop-rails to v2.32.0 2025-05-18 02:07:50 +00:00
Renovate Bot
be04c5c441 Update dependency rubocop to v1.75.5 2025-05-06 02:05:08 +00:00
Renovate Bot
252321461c Update dependency shoulda-matchers to v6.5.0 2025-04-30 02:05:41 +00:00
Renovate Bot
52c7ed6eb1 Update dependency rubocop to v1.75.4 2025-04-29 02:04:53 +00:00
bf35ca5f6e Upgrade to ruby 3.4.3 2025-04-25 07:39:13 +02:00
918944b345 Update README.md 2025-04-25 05:32:34 +00:00
Renovate Bot
8cd4efc6f5 Update dependency ruby to v3.4.3 2025-04-24 02:06:21 +00:00
Renovate Bot
fecbdb263e Update dependency rubocop to v1.75.3 2025-04-23 02:05:13 +00:00
Renovate Bot
c5139a8a47 Update dependency csv to v3.3.4 2025-04-22 02:04:41 +00:00
cf4d27009c Merge pull request 'Update dependency solid_queue to v1.1.5' () from renovate/solid_queue-1.x-lockfile into main
Reviewed-on: 
2025-04-21 18:19:45 +00:00
Renovate Bot
9926d99a22 Update dependency solid_queue to v1.1.5 2025-04-21 02:04:55 +00:00
Renovate Bot
88367081f1 Update dependency rubocop-rspec to v3.6.0 2025-04-19 02:04:33 +00:00
Renovate Bot
3aa629d676 Update dependency rubocop-rails to v2.31.0 2025-04-02 02:05:52 +00:00
8ae8c5b9a5 Merge pull request 'Update dependency httparty to v0.23.1' () from renovate/httparty-0.x-lockfile into main
Reviewed-on: 
2025-03-30 11:16:52 +00:00
Renovate Bot
b36099e6df Update dependency httparty to v0.23.1 2025-03-29 03:06:56 +00:00
Renovate Bot
ee20b596d1 Update dependency rubocop to v1.75.1 2025-03-27 03:05:34 +00:00
Renovate Bot
56dd6d6d13 Update dependency solid_queue to v1.1.4 2025-03-18 03:04:28 +00:00
Renovate Bot
b195ab7426 Update dependency rubocop-rspec_rails to v2.31.0 2025-03-17 03:03:41 +00:00
Renovate Bot
c5f92ab511 Update dependency rubocop-rspec to v3.5.0 2025-03-16 03:08:33 +00:00
Renovate Bot
51695e2c83 Update dependency turbo-rails to v2.0.13 2025-03-15 03:05:49 +00:00
Renovate Bot
4dd7f2f45d Update dependency rubocop-factory_bot to v2.27.1 2025-03-14 03:05:39 +00:00
Renovate Bot
e9ed703c7e Update dependency rails to v8.0.2 2025-03-13 03:06:23 +00:00
Renovate Bot
431264e848 Update dependency rubocop-factory_bot to v2.27.0 2025-03-06 03:09:55 +00:00
Renovate Bot
63b92ce30f Update dependency rubocop-rails to v2.30.3 2025-03-04 03:05:23 +00:00
Renovate Bot
10310eecc6 Update dependency redis to v5.4.0 2025-03-02 03:05:58 +00:00
Renovate Bot
af2f4015bc Update dependency rubocop-rails to v2.30.2 2025-03-01 03:06:15 +00:00
f63d460c90 Merge pull request 'Update dependency rubocop to v1.73.1' () from renovate/rubocop-1.x-lockfile into main
Reviewed-on: 
2025-02-28 06:05:06 +00:00
Renovate Bot
496a3ac28d Update dependency rubocop to v1.73.1 2025-02-28 03:05:40 +00:00
Renovate Bot
450d914d49 Update dependency rubocop-rails to v2.30.1 2025-02-20 03:10:48 +00:00
Renovate Bot
b27aab84a4 Update dependency annotaterb to v4.14.0 2025-02-19 03:10:13 +00:00
4c965b6e8f Merge pull request 'Update dependency ruby to v3.4.2' () from renovate/ruby-3.x into main
Reviewed-on: 
2025-02-16 21:54:16 +00:00
d401859088 Upgrade to ruby 3.4.2 2025-02-16 22:20:24 +01:00
80892930ba Merge pull request 'Update dependency rubocop-rails to v2.30.0' () from renovate/rubocop-rails-2.x-lockfile into main
Reviewed-on: 
2025-02-16 21:19:54 +00:00
Renovate Bot
79c0619e2f Update dependency rubocop-rails to v2.30.0 2025-02-16 03:12:42 +00:00
Renovate Bot
d4264e6118 Update dependency ruby to v3.4.2 2025-02-16 03:12:05 +00:00
b48e51e867 Merge pull request 'Update dependency rspec-rails to v7.1.1' () from renovate/rspec-rails-7.x-lockfile into main
Reviewed-on: 
2025-02-13 10:58:25 +00:00
Renovate Bot
5e22172620 Update dependency rspec-rails to v7.1.1 2025-02-09 03:10:19 +00:00
Renovate Bot
afdb0cabca Update dependency puma to v6.6.0 2025-02-05 03:06:22 +00:00
Renovate Bot
0b8a38cb82 Update dependency solid_queue to v1.1.3 2025-02-04 03:06:07 +00:00
Renovate Bot
98c64018db Update dependency rubocop to v1.71.1 2025-02-01 03:05:02 +00:00
3e6afcc048 Merge branch 'invitations' of https://gitea.bustikiller.com/bustikiller/wedding-planner into invitations 2025-01-28 08:59:44 +01:00
522bcb0032 Generate invitations within the same group 2025-01-28 08:59:15 +01:00
40f89ae179 Add copyright notice 2025-01-27 19:44:30 +00:00
ecbb6af4bd Increase seeds size 2025-01-27 20:42:31 +01:00
b112aefe21 Optimize seeds file 2025-01-27 20:22:21 +01:00
a5d3062654 Define and seed an invitation model 2025-01-27 20:08:28 +01:00
Renovate Bot
567093c449 Update dependency rubocop-rails to v2.29.1 2025-01-27 03:05:19 +00:00
2147d7ad5e Redo simulations lifecycle ()
## Why

The current way of creating and deleting simulations doesn't scale for big instances. We cannot generate 50 simulations every time a guest confirms attendance, and we should not delete existing valuable simulations. For example, if a guest confirms attendance and declines right after, previously generated simulations should still be valid.

## What

In this PR we are introducing a series of changes that make simulations management easier:

1. We're removing the automatic creation of simulations.
2. Simulations are not removed anymore, neither manually nor automatically.
3. A new endpoint has been defined to create simulations on demand.
4. A digest property has been defined to determine whether a simulation is still valid (meaning there have not been any change in the list of guests involved).

Reviewed-on: 
Co-authored-by: Manuel Bustillo <bustikiller@bustikiller.com>
Co-committed-by: Manuel Bustillo <bustikiller@bustikiller.com>
2025-01-26 12:53:21 +00:00
f414acb2d5 Test reversibility of migrations ()
We want to make sure that:

1. Migrations are reversible
2. Reapplying migrations added to a PR leads to the same schema.rb

Reviewed-on: 
Co-authored-by: Manuel Bustillo <bustikiller@bustikiller.com>
Co-committed-by: Manuel Bustillo <bustikiller@bustikiller.com>
2025-01-25 09:43:43 +00:00
66aded5112 Merge pull request 'Update copyright notice action' () from remove-old-copyright-notice into main
Reviewed-on: 
2025-01-23 22:16:01 +00:00
8c12884212 Add copyright notice 2025-01-23 21:34:23 +00:00
6cead51bb9 Remove all copyright notices to force recreation 2025-01-23 22:29:26 +01:00
74ca20d3e8 Add copyright notice 2025-01-23 21:18:12 +00:00
cedb8dce92 Update copyright notice action 2025-01-23 22:15:34 +01:00
006b1893c1 Merge pull request 'Fix error loading arrangements' () from fix/error-loading-arrangements into main
Reviewed-on: 
2025-01-23 20:49:01 +00:00
Renovate Bot
fcb1e77a3a Update dependency rubocop to v1.71.0 2025-01-23 03:11:38 +00:00
9f7f99f3f4 Fix error loading arrangements 2025-01-22 20:39:09 +01:00
Renovate Bot
f550b8d92c Update dependency rubocop-rspec to v3.4.0 2025-01-21 03:03:34 +00:00
7c968f555f Merge pull request 'Use group affinities in discomfort calculator' () from discomfort-calculation-revamp into main
Reviewed-on: 
2025-01-19 20:19:10 +00:00
1f0c6c2aac Use group affinities in discomfort calculator 2025-01-19 20:58:42 +01:00
390899524b Fix rubocop ofenses 2025-01-19 19:44:22 +01:00
Renovate Bot
59e7653064 Update dependency rubocop-rails to v2.29.0 2025-01-19 03:18:52 +00:00
d6fd72a45c Merge pull request 'Fix duplicate row index on upsert statement' () from fix-upsert-index into main
Reviewed-on: 
2025-01-14 22:11:43 +00:00
889485eaab Merge branch 'main' into fix-upsert-index 2025-01-14 21:08:36 +00:00
105a0ad30c Merge pull request 'Upgrade to ruby 3.4' () from ruby-3-4 into main
Reviewed-on: 
2025-01-14 20:39:46 +00:00
d03ef173e6 Merge branch 'main' into ruby-3-4 2025-01-14 20:10:18 +00:00
b86d537cdc Install missing libyaml dependencies 2025-01-14 20:59:30 +01:00
c1774a1c6c Upgrade Nokogiri to the latest version 2025-01-14 20:16:02 +01:00
23d09df543 Fix version of GH action 2025-01-14 20:11:59 +01:00
44dbb7e005 Run bundle lock --normalize-platforms 2025-01-14 20:11:34 +01:00
8a9d0bfdb8 Use the lateste version of ruby/setup-ruby 2025-01-14 19:51:29 +01:00
c6c5a87d8b Fix duplicate row index on upsert statement 2025-01-14 19:13:40 +01:00
acf3b7b82a Merge pull request 'Update copyright assignment to cover 2025 and include all contributors' () from copyright-2025 into main
Reviewed-on: 
2025-01-13 21:04:10 +00:00
0ade367fb5 Merge pull request 'Define an endpoint to reset the discomfort between all groups' () from full-affinity-reset into main
Reviewed-on: 
2025-01-13 20:57:43 +00:00
91bbae1c63 Add copyright notice 2025-01-13 20:38:47 +00:00
e20a366410 Update copyright assignment to cover 2025 and include all contributors 2025-01-13 21:37:02 +01:00
a154e92b6c Define an endpoint to reset the discomfort between all groups 2025-01-13 21:16:22 +01:00
65a265b900 Merge pull request 'Define an endpoint to return the default affinities of a group' () from affinity-reset into main
Reviewed-on: 
2025-01-12 21:34:04 +00:00
f997657cd3 Define an endpoint to return the default affinities of a group 2025-01-12 20:41:01 +01:00
37bbc1e4f1 Merge pull request 'Update dependency factory_bot_rails to v6.4.4' () from renovate/factory_bot_rails-6.x-lockfile into main
Reviewed-on: 
2025-01-12 18:31:51 +00:00
Renovate Bot
088c609a44 Update dependency rubytree to v2.1.1 2025-01-12 03:06:30 +00:00
Renovate Bot
fd40fb61b5 Update dependency factory_bot_rails to v6.4.4 2025-01-12 03:06:19 +00:00
Renovate Bot
5db5281d9f Update dependency rubocop to v1.70.0 2025-01-11 03:10:32 +00:00
492c8e362a Upgrade to ruby 3.4.1 2024-12-30 08:15:56 +00:00
c5c7ea6d54 WIP: Upgrade to ruby 3.4 2024-12-30 08:15:56 +00:00
e175a3dd34 Merge pull request 'Build image on PR instead of on push' () from build-image-on-pr into main
Reviewed-on: 
2024-12-30 07:37:28 +00:00
a8f0302bb9 Build image on PR instead of on push 2024-12-29 16:23:52 +01:00
1e533702fd Merge pull request 'Configure build and cache for intermediate layers' () from optimize-docker-build into main
Reviewed-on: 
2024-12-29 12:42:40 +00:00
9c3c766175 Configure build and cache for intermediate layers 2024-12-29 13:25:02 +01:00
Renovate Bot
d2841a449e Update dependency solid_queue to v1.1.2 2024-12-29 01:08:54 +00:00
802ec2761c Merge pull request 'rubocop-autocorrect' () from rubocop-autocorrect into main
Reviewed-on: 
2024-12-28 18:26:28 +00:00
eded3946de Restore source in gemfile 2024-12-28 18:39:25 +01:00
55e6cfcd36 Fix order of Ruby's magic string comment and Copyright assignment 2024-12-28 18:37:47 +01:00
5f2778c97a Restore original name 2024-12-28 18:35:42 +01:00
20cca87cdd Run rubocop checks as part of CI 2024-12-28 18:35:04 +01:00
cb10d50d9e Rename .github -> .gitea 2024-12-28 18:32:46 +01:00
b16ef1e237 Run Rubocop autocorrect on the rest of the project 2024-12-28 18:30:54 +01:00
0c7c69ee5e Run Rubocop autocorrect on app/controllers 2024-12-28 18:28:38 +01:00
4fc95185fb Run Rubocop autocorrect on app/helpers 2024-12-28 18:21:16 +01:00
02fcd03b0e Run Rubocop autocorrect on app/services 2024-12-28 18:20:09 +01:00
fbc6926402 Run Rubocop autocorrect on app/queries 2024-12-28 18:16:36 +01:00
19dcb551fa Run Rubocop autocorrect on app/models 2024-12-28 18:13:57 +01:00
2fc8a6340b Run Rubocop autocorrect on spec/ 2024-12-28 18:07:40 +01:00
c15d0806a8 Add exception to some Rubocop offenses 2024-12-28 17:49:00 +01:00
27c7feebee Run Rubocop autocorrect on spec/queries 2024-12-28 17:37:49 +01:00
b85e2ef932 Run Rubocop autocorrect on spec/extensions 2024-12-28 17:37:49 +01:00
c7b9c83f37 Add copyright notice 2024-12-28 16:31:27 +00:00
82f17056be Run rubocop autocorrect on factory files 2024-12-28 17:28:16 +01:00
4d69863974 Run Rubocop autocorrect on spec/models 2024-12-28 17:27:45 +01:00
5fcac34a52 Install Rubocop extensions 2024-12-28 17:08:57 +01:00
ad88fb0909 Merge pull request 'Define model and endpoints to store affinity between group pairs' () from affinities-controller into main
Reviewed-on: 
2024-12-28 15:47:57 +00:00
9fe649f8b8 Update swagger documentation 2024-12-28 16:43:58 +01:00
5784c89b79 Refine endpoint to receive an affinity value and transform it into a discomfort equivalent 2024-12-28 14:19:06 +01:00
0780b17f4b Add copyright notice 2024-12-26 19:30:32 +00:00
6c6ae62e5a Define model and endpoints to store affinity between group pairs 2024-12-26 20:29:06 +01:00
3b2f52da9b Update dependency pry to v0.15.2 () 2024-12-25 07:18:03 +00:00
Renovate Bot
9ac8b9b783 Update dependency pry to v0.15.2 2024-12-25 01:05:11 +00:00
Renovate Bot
1318f34cec Update dependency csv to v3.3.2 2024-12-24 01:04:34 +00:00
bd4e0a0135 Merge pull request 'Update dependency factory_bot_rails to v6.4.4' () from renovate/factory_bot_rails-6.x-lockfile into main
Reviewed-on: 
2024-12-23 18:21:20 +00:00
Renovate Bot
4f2c3ee1a6 Update dependency importmap-rails to v2.1.0 2024-12-23 01:05:11 +00:00
Renovate Bot
a7e40f3d63 Update dependency factory_bot_rails to v6.4.4 2024-12-23 01:05:04 +00:00
43f4143df3 Merge pull request 'Upgrade bundler version and include gem checksums' () from bundler-update into main
Reviewed-on: 
2024-12-21 08:34:09 +00:00
d05900f2a6 Upgrade bundler version and include gem checksums 2024-12-21 09:31:06 +01:00
Renovate Bot
663e26bda3 Update dependency debug to v1.10.0 2024-12-19 01:06:12 +00:00
Renovate Bot
511273280f Update dependency rails to v8.0.1 2024-12-17 01:17:06 +00:00
Renovate Bot
f1ff39ceb1 Update dependency csv to v3.3.1 2024-12-17 01:05:02 +00:00
2c6a05ee06 Merge pull request 'Redo TablesArrangements#show to display arrangement ID and discomfort breakdown' () from table-discomfort-breakdown into main
Reviewed-on: 
2024-12-16 22:18:23 +00:00
3bfe889747 Redo TablesArrangements#show to display arrangement ID and discomfort breakdown 2024-12-16 18:52:34 +01:00
Renovate Bot
e44043f572 Update dependency rubocop to v1.69.2 2024-12-16 08:03:14 +00:00
28386df1d8 Merge pull request 'Update dependency factory_bot_rails to v6.4.4' () from renovate/factory_bot_rails-6.x-lockfile into main
Reviewed-on: 
2024-12-16 08:01:57 +00:00
Renovate Bot
998706da97 Update dependency factory_bot_rails to v6.4.4 2024-12-15 01:17:10 +00:00
bab5cd3161 Merge pull request 'Define an endpoint with a global summary of expenses and attendance' () from dashboard into main
Reviewed-on: 
2024-12-11 22:44:35 +00:00
4e61ee2f22 Include real data in the guests summary of the dashboard 2024-12-11 23:42:25 +01:00
f68caca5a4 Add copyright notice 2024-12-11 08:03:02 +00:00
da8f3c7618 Define an endpoint with a global summary of expenses and attendance 2024-12-11 09:01:35 +01:00
Renovate Bot
d18adb2806 Update dependency rails to v8.0.0.1 2024-12-11 01:08:44 +00:00
70bbf79a5a Merge pull request 'Group attendance properties into a json key' () from groups-attendance-format into main
Reviewed-on: 
2024-12-10 07:46:20 +00:00
98c1c0d18c Group attendance properties into a json key 2024-12-10 08:41:10 +01:00
71eecf94a6 Merge pull request 'Define and document CRUD endpoints for expenses' () from expenses-crud into main
Reviewed-on: 
2024-12-09 18:31:59 +00:00
be40c97f2f Define and document CRUD endpoints for expenses 2024-12-09 19:28:32 +01:00
ac09d67f4f Merge pull request 'Configure librecaptcha configuration for the development environment' () from disable-librecaptcha-demo into main
Reviewed-on: 
2024-12-09 17:29:04 +00:00
e8543d8fb4 Configure librecaptcha configuration for the development environment 2024-12-09 18:25:47 +01:00
ead48c2588 Merge pull request 'Avoid stack too deep erros due to excessive recursion' () from stack-error-vns into main
Reviewed-on: 
2024-12-09 17:16:40 +00:00
dfb50ed2dc Avoid stack too deep erros due to excessive recursion 2024-12-09 18:14:21 +01:00
ff0e0b6931 Merge pull request 'Document tables arrangements controller' () from tables-arrangements-api into main
Reviewed-on: 
2024-12-08 13:06:00 +00:00
5cbc81c498 Add copyright notice 2024-12-08 13:02:08 +00:00
9d90ade40c Document tables arrangements controller 2024-12-08 14:00:53 +01:00
b38e845b90 Merge pull request 'Allow the creation of guests associated to no group' () from allow-creation-groupless-guests into main
Reviewed-on: 
2024-12-08 12:13:03 +00:00
83e36df14e Allow the creation of guests associated to no group 2024-12-08 13:10:49 +01:00
cbcb7b70e3 Merge pull request 'Define CUD endpoints for the Groups model' () from groups-endpoints into main
Reviewed-on: 
2024-12-08 10:52:59 +00:00
9f0773647f Add copyright notice 2024-12-08 10:41:24 +00:00
20127398c6 Fix summary query to leverage ActsAsTenant scopes 2024-12-08 11:39:50 +01:00
9e097361d0 Define endpoints to create, update, and delete groups 2024-12-08 11:30:38 +01:00
dae2e3bace Merge pull request 'Define a dummy endpoint to return a valid CSRF token' () from token-endpoint into main
Reviewed-on: 
2024-12-08 08:39:39 +00:00
98877166dd Add copyright notice 2024-12-08 08:34:55 +00:00
438de103ec Define a dummy endpoint to return a valid CSRF token 2024-12-08 09:32:34 +01:00
9fab79044d Merge pull request 'Configure allowed hosts' () from configure-host into main
Reviewed-on: 
2024-12-08 07:56:02 +00:00
84684b90d7 Configure allowed hosts 2024-12-08 08:53:51 +01:00
64f34a71dc Merge pull request 'Temporarily allow insecure cookies' () from temp-allow-insecure-cookie into main
Reviewed-on: 
2024-12-07 23:49:29 +00:00
1fb6c483ed Temporarily allow insecure cookies 2024-12-08 00:48:42 +01:00
278faa7319 Merge pull request 'Refine registration endpoint' () from remove-wedding-date into main
Reviewed-on: 
2024-12-07 22:45:15 +00:00
93d907cdc8 Remove leftovers of the date attribute 2024-12-07 23:43:21 +01:00
fdef94be9a Revert "Fix tenant-related error retrieving captcha"
This reverts commit 3996ffc85c1fe3a912db14cbb317158fe9bcd8e2.
2024-12-07 23:18:23 +01:00
c62bb137ce Merge branch 'main' into remove-wedding-date 2024-12-07 22:17:30 +00:00
054561faa6 Merge pull request 'Fix tenant-related error retrieving captcha' () from fix-tenant-captcha into main
Reviewed-on: 
2024-12-07 22:03:18 +00:00
3996ffc85c Fix tenant-related error retrieving captcha 2024-12-07 22:53:30 +01:00
82c543b167 Merge pull request 'Fix build of Docker image' () from fix-build into main
Reviewed-on: 
2024-12-07 19:29:38 +00:00
dfe914a0b8 Fix build of Docker image 2024-12-07 20:27:28 +01:00
0e234b34a0 Merge pull request 'Fix production DB host' () from fix-prod-db-host into main
Reviewed-on: 
2024-12-07 19:02:30 +00:00
2ab966faf8 Fix production DB host 2024-12-07 19:59:59 +01:00
9b612ce01d Add copyright notice 2024-12-07 18:09:21 +00:00
a3f14f4fec Include slug in root_url 2024-12-07 19:07:06 +01:00
022b58bb38 Fix issues with tenant during registration 2024-12-07 12:43:08 +01:00
8527b20075 Remove wedding date attribute 2024-12-07 12:39:43 +01:00
Renovate Bot
2c4befbcf6 Update dependency solid_queue to v1.1.0 2024-12-07 01:06:02 +00:00
Renovate Bot
70e9f74207 Update dependency rubocop to v1.69.1 2024-12-04 01:06:43 +00:00
85eb85e841 Merge pull request 'Update dependency factory_bot_rails to v6.4.4' () from renovate/factory_bot_rails-6.x-lockfile into main
Reviewed-on: 
2024-12-03 07:32:33 +00:00
Renovate Bot
6aba5e234f Update dependency factory_bot_rails to v6.4.4 2024-12-03 01:06:02 +00:00
e0a34df7b7 Merge pull request 'fix-tenant-scope' () from fix-tenant-scope into main
Reviewed-on: 
2024-12-02 19:35:36 +00:00
a96be2a79e Do not require a tenant scope for running tests 2024-12-02 20:33:05 +01:00
3ea1d1e7ec Add copyright notice 2024-12-02 08:05:46 +00:00
3fca449461 Limit visibility per tenant 2024-12-02 09:04:48 +01:00
ef573c5f73 Require a tenant to be configured for all queries 2024-12-02 08:57:10 +01:00
822b2b0fad Merge pull request 'Require a LibreCaptcha challenge for the signup action' () from libre-captcha into main
Reviewed-on: 
2024-12-01 19:03:56 +00:00
71046b9a1c Avoid exposing internal port and unnecessary endpoints 2024-12-01 20:01:00 +01:00
5f01741943 Validate the Captcha challenge for account signup 2024-12-01 19:57:01 +01:00
be9ca9e6b0 Add copyright notice 2024-12-01 18:43:28 +00:00
b237239a1f Define an endpoint to retrieve a LibreCaptcha captcha 2024-12-01 19:42:25 +01:00
241668b84d Merge pull request 'Indicate in the README that the application is multi-tenant' () from wedding-creation into main
Reviewed-on: 
2024-12-01 17:26:24 +00:00
f708191ede Indicate in the README that the application is multi-tenant 2024-12-01 18:21:33 +01:00
5f66373d50 Merge pull request 'Configure Devise to send emails using the tenant's slug for the URL' () from wedding-creation into main
Reviewed-on: 
2024-12-01 17:19:14 +00:00
9d08ef6f18 Update wedding slug rules to accept numbers and other chars 2024-12-01 18:17:37 +01:00
f588b97e18 Add copyright notice 2024-12-01 13:05:22 +00:00
7a80f1f5ef Make wedding object required for the swagger specs 2024-12-01 14:04:03 +01:00
279093ad98 Configure registration endpoint to create a wedding as well 2024-12-01 14:03:23 +01:00
e6cf0da814 Merge pull request 'Make the application multi-tenant based on a wedding model' () from wedding-model into main
Reviewed-on: 
2024-12-01 10:11:24 +00:00
4f1aa9dd2d Merge branch 'wedding-model' of https://gitea.bustikiller.com/bustikiller/wedding-planner into wedding-model 2024-12-01 10:41:45 +01:00
4d9563cab7 Adapt background job to use acts as tenant 2024-12-01 10:41:05 +01:00
70b44e1b96 Add copyright notice 2024-12-01 09:00:01 +00:00
8429b3952b Adapt factories to use a wedding object 2024-12-01 09:58:39 +01:00
e4dad698ea Merge branch 'wedding-model' of https://gitea.bustikiller.com/bustikiller/wedding-planner into wedding-model 2024-11-30 21:11:43 +01:00
be3497ad64 Configure current tenant in a before_action of the ApplicationController 2024-11-30 21:11:25 +01:00
6d61e8452a Add copyright notice 2024-11-30 20:07:45 +00:00
63bb32f2a7 Include users in the list of models affected by tenant 2024-11-30 21:06:21 +01:00
682b5cb5fd Merge remote-tracking branch 'origin/main' into wedding-model 2024-11-30 20:56:33 +01:00
cb90a93ef3 Add copyright notice 2024-11-30 19:07:36 +00:00
5b3c1fdfac Adapt seeds file to use ActsAsTenant 2024-11-30 20:06:43 +01:00
8bff98b165 Create DB associations 2024-11-30 20:06:43 +01:00
988e158d99 Install acts_as_tenant gem and update documentation 2024-11-30 20:06:43 +01:00
cf6ca5aa17 Create a seed user for the develoment environment 2024-11-30 20:06:43 +01:00
9e222f59be Add copyright notice 2024-11-30 18:31:48 +00:00
24c39f331a Define a simple wedding model 2024-11-30 19:30:04 +01:00
bc4e9cc63e Merge pull request 'Create a seed user for the develoment environment' () from seed-user into main
Reviewed-on: 
2024-11-30 18:28:25 +00:00
5700532ac7 Create a seed user for the develoment environment 2024-11-30 19:19:22 +01:00
7df4ab1c56 Merge pull request 'Remove noisy log messages' () from fix/noisy-output into main
Reviewed-on: 
2024-11-30 18:18:27 +00:00
06f8039f40 Merge pull request 'Configure letter opener to read emails via web UI' () from letter-opener into main
Reviewed-on: 
2024-11-30 18:17:46 +00:00
7803df7494 Add copyright notice 2024-11-30 18:14:58 +00:00
2889dc5c98 Update README 2024-11-30 19:12:13 +01:00
918bc0c1a8 Remove noisy log messages 2024-11-30 19:10:16 +01:00
b892d4006f Configure letter opener to read emails via web UI 2024-11-30 19:09:36 +01:00
271ad270a4 Merge pull request 'Install and configure Devise for user authentication' () from devise into main
Reviewed-on: 
2024-11-30 17:50:42 +00:00
598cb553c9 Add copyright notice 2024-11-30 17:47:30 +00:00
b0124fbd26 Configure email confirmation flow 2024-11-30 18:46:31 +01:00
5458c6dd8c Add copyright notice 2024-11-30 13:27:21 +00:00
f5038f5b96 Code cleanup 2024-11-30 14:25:01 +01:00
b8e6df732c Generate user model, document some endpoints (missing email verification) 2024-11-30 14:24:02 +01:00
ed7207d707 Require user authentication by default 2024-11-30 11:04:45 +01:00
13bdaf0bd2 Add copyright notice 2024-11-30 10:03:46 +00:00
63c7bc8772 Refine columns in the users table 2024-11-30 11:01:44 +01:00
306fa41187 Merge pull request 'Revert user authentication' () from revert-authentication into main
Reviewed-on: 
2024-11-30 09:59:42 +00:00
d9ab2f5091 Install devise gem 2024-11-30 10:59:28 +01:00
3d424c304e Revert user authentication
This reverts commit b215e8a3b40be089e7359cfd424f807c2d54b1c2.
2024-11-30 10:45:17 +01:00
Renovate Bot
50c0a80dec Update dependency rubocop to v1.69.0 2024-11-30 01:07:31 +00:00
993e4e5e57 Merge pull request 'Install Rails' authentication generator' () from authentication into main
Reviewed-on: 
2024-11-29 19:44:50 +00:00
1a760af3e8 Merge pull request 'Update dependency factory_bot_rails to v6.4.4' () from renovate/factory_bot_rails-6.x-lockfile into main
Reviewed-on: 
2024-11-29 19:38:56 +00:00
Renovate Bot
d8ee5972aa Update dependency factory_bot_rails to v6.4.4 2024-11-29 01:05:48 +00:00
515d841c11 Merge pull request 'Update dependency factory_bot_rails to v6.4.4' () from renovate/factory_bot_rails-6.x-lockfile into main
Reviewed-on: 
2024-11-26 06:26:17 +00:00
Renovate Bot
d2eaa21df2 Update dependency factory_bot_rails to v6.4.4 2024-11-25 01:08:54 +00:00
Renovate Bot
3bde27ddce Update dependency puma to v6.5.0 2024-11-24 01:05:23 +00:00
b215e8a3b4 Add copyright notice 2024-11-19 07:57:42 +00:00
0e0da9c765 Refine and document controllers 2024-11-19 08:56:51 +01:00
8b33616436 Remove unnecessary views 2024-11-19 00:34:11 +01:00
3e38630eb4 Refine controllers 2024-11-19 00:33:53 +01:00
134bf27955 Add copyright notice 2024-11-18 23:27:50 +00:00
aa0986986f Install Rails' authentication generator 2024-11-19 00:26:44 +01:00
300524956b Merge pull request 'Refine guest controller' () from refine-guest-controller into main
Reviewed-on: 
2024-11-17 19:05:59 +00:00
80c1c9b99d Refine guest controller 2024-11-17 20:02:08 +01:00
1f81dabff4 Merge pull request 'Define an endpoint to remove guests' () from delete-guest-endpoint into main
Reviewed-on: 
2024-11-17 17:28:13 +00:00
f1d1ea825c Recalculate simulations after removing a guest 2024-11-17 18:25:38 +01:00
7542c6361c Define an endpoint to destroy guests 2024-11-17 18:24:43 +01:00
7bdfb4f789 Merge pull request 'Remove unused bulk update endpoint' () from remove-guest-bulk-update into main
Reviewed-on: 
2024-11-17 16:11:53 +00:00
31d41ea2ea Remove unused bulk update endpoint 2024-11-17 17:07:29 +01:00
29c9764450 Merge pull request 'Define an endpoint to create new guests' () from api-create-guest into main
Reviewed-on: 
2024-11-17 10:51:01 +00:00
1b2c0f0d0a Define an endpoint to create new guests 2024-11-17 11:47:12 +01:00
08f7c1e584 Merge pull request 'Merge docker compose up and build into a single command' () from readme-change into main
Reviewed-on: 
2024-11-17 10:32:14 +00:00
b3cfde445c Merge docker compose up and build into a single command 2024-11-17 11:27:38 +01:00
Renovate Bot
b008777c20 Update dependency solid_queue to v1.0.2 2024-11-17 01:08:15 +00:00
b0ee052b64 Merge pull request 'Remove / server from Swagger configuration' () from remove-localhost-swagger-server into main
Reviewed-on: 
2024-11-16 11:32:32 +00:00
5f47b923d5 Remove / server from Swagger configuration 2024-11-16 12:30:05 +01:00
17c796c375 Merge pull request 'Document expenses endpoint and add some specs' () from document-expenses-controller into main
Reviewed-on: 
2024-11-16 09:32:36 +00:00
73e02a9d95 Add copyright notice 2024-11-16 09:23:51 +00:00
86b9d0b56c Document expenses endpoint and add some specs 2024-11-16 10:22:10 +01:00
8e799bfd2b Merge pull request 'Remove leftover code from Swagger-CSRF experiment' () from remove-swagger-csrf into main
Reviewed-on: 
2024-11-16 09:12:35 +00:00
6e5bbb7b1b Remove leftover code from Swagger-CSRF experiment 2024-11-16 09:59:19 +01:00
d73f59b05c Merge pull request 'Update format of guests API and document endpoints' () from guests-api-changes into main
Reviewed-on: 
2024-11-16 08:56:38 +00:00
5ba1591b02 Merge branch 'main' into guests-api-changes 2024-11-16 08:51:21 +00:00
b7714051e4 Merge pull request 'Update dependency pry to v0.15.0' () from renovate/pry-0.x-lockfile into main
Reviewed-on: 
2024-11-16 08:51:02 +00:00
a8c9c051f0 Add copyright notice 2024-11-16 01:52:48 +00:00
6f6a6aaabf Update format of guests API and document endpoints 2024-11-16 02:16:19 +01:00
Renovate Bot
6709c6b1f1 Update dependency pry to v0.15.0 2024-11-16 01:08:16 +00:00
21d3aa5f4b Merge pull request 'Document the API using OpenAPI' () from rswag-documentation into main
Reviewed-on: 
2024-11-15 18:49:11 +00:00
cc3c8fdd63 Improve documentation of groups endpoint 2024-11-15 19:46:47 +01:00
94b1066c17 Include OpenAPI information in the README 2024-11-15 19:09:55 +01:00
ca0b1b18d3 Use different server URLs for development and testing 2024-11-15 19:04:30 +01:00
41cb719bf4 Add copyright notice 2024-11-15 17:29:56 +00:00
bcbcf9b469 MVP of swagger documentation 2024-11-15 18:28:45 +01:00
2bbcdfbd98 Merge pull request 'Update dependency factory_bot_rails to v6.4.4' () from renovate/factory_bot_rails-6.x-lockfile into main
Reviewed-on: 
2024-11-15 07:47:45 +00:00
8a3469447b Install rswag gem with default configuration 2024-11-15 08:45:56 +01:00
Renovate Bot
c37713af8f Update dependency factory_bot_rails to v6.4.4 2024-11-15 07:43:39 +00:00
b8484edf26 Merge pull request 'Fix ruby version used to run license finder' () from fix-ruby-version into main
Reviewed-on: 
2024-11-15 07:43:31 +00:00
4cd3f24032 Fix ruby version used to run license finder 2024-11-15 08:33:31 +01:00
891741d740 Merge pull request 'Update dependency ruby to v3.3.6' () from renovate/ruby-3.x into main
Reviewed-on: 
2024-11-14 08:43:13 +00:00
2fc4e2debd Use a setup-ruby version that has ruby 3.3.6 2024-11-14 09:40:53 +01:00
8d746e21b9 Merge pull request 'Merge pull request 'Introduce endpoint to retrieve a summary of groups and invite attendance' () from groups-index-stats into main' () from annotate-gem into main
Reviewed-on: 
2024-11-14 07:39:04 +00:00
42f5f7b246 Add copyright notice 2024-11-14 07:33:24 +00:00
d75b117c60 Merge pull request 'Introduce endpoint to retrieve a summary of groups and invite attendance' () from groups-index-stats into main
Reviewed-on: 
2024-11-14 08:30:27 +01:00
c64c440272 Merge pull request 'Update dependency factory_bot_rails to v6.4.4' () from renovate/factory_bot_rails-6.x-lockfile into main
Reviewed-on: 
2024-11-14 07:28:05 +00:00
Renovate Bot
1a4067859d Update dependency factory_bot_rails to v6.4.4 2024-11-14 01:06:51 +00:00
f15545d13a Merge pull request 'Introduce endpoint to retrieve a summary of groups and invite attendance' () from groups-index-stats into main
Reviewed-on: 
2024-11-13 08:12:46 +00:00
4be1fc6cad Add copyright notice 2024-11-13 07:59:07 +00:00
452b5b2040 Introduce endpoint to retrieve a summary of groups and invite attendance 2024-11-13 08:57:20 +01:00
ef10fafae8 Merge pull request 'Update dependency factory_bot_rails to v6.4.4' () from renovate/factory_bot_rails-6.x-lockfile into main
Reviewed-on: 
2024-11-12 08:49:27 +00:00
Renovate Bot
94bf707723 Update dependency factory_bot_rails to v6.4.4 2024-11-12 01:07:37 +00:00
402a4a3e5e Merge pull request 'Define endpoint to update expenses' () from update-expenses into main
Reviewed-on: 
2024-11-11 07:23:40 +00:00
88a7785b46 Define endpoint to update expenses 2024-11-11 08:08:49 +01:00
f7c0fc91b1 Merge pull request 'Merge first and last name and expose guest update endpoint' () from update-guests into main
Reviewed-on: 
2024-11-11 07:02:47 +00:00
3b9ca24bcd Add copyright notice 2024-11-11 06:57:39 +00:00
bd5c4f5482 Merge first and last name and expose guest update endpoint 2024-11-11 07:55:03 +01:00
d2974edcc1 Merge pull request 'Define an endpoint to expose the list of expenses' () from expenses-list-endpoints into main
Reviewed-on: 
2024-11-11 06:50:18 +00:00
810d0740f3 Define an endpoint to expose the list of expenses 2024-11-10 20:40:37 +01:00
b1ec1281b5 Merge pull request 'Remove rake task leftover' () from remove-rake into main
Reviewed-on: 
2024-11-10 19:39:55 +00:00
6f19e4f1bd Remove rake task leftover 2024-11-10 20:37:58 +01:00
8d74d25574 Merge pull request 'Increase penalty for tables under minimum capacity' () from increase-penalty-undercapacity-tables into main
Reviewed-on: 
2024-11-10 18:01:16 +00:00
b389ae0a41 Merge pull request 'Run perturbations in both orders' () from vns-combinations into main
Reviewed-on: 
2024-11-10 18:01:15 +00:00
fe91f75ec0 Add copyright notice 2024-11-10 17:57:29 +00:00
f2e91c8d7a Run perturbations in both orders 2024-11-10 18:55:04 +01:00
5b36526c59 Increase penalty for tables under minimum capacity 2024-11-10 18:40:26 +01:00
b084328d79 Merge pull request 'Implement shift operation for VNS algorithm' () from shift into main
Reviewed-on: 
2024-11-10 17:12:04 +00:00
bcf00fabf0 Add copyright notice 2024-11-10 17:01:57 +00:00
1fcca38576 Implement shift perturbation 2024-11-10 17:53:52 +01:00
c16e8f564c Merge pull request 'Use sets instead of arrays to represent tables' () from table-set into main
Reviewed-on: 
2024-11-10 16:31:49 +00:00
29d9d21916 Use sets instead of arrays to represent tables 2024-11-10 17:30:01 +01:00
aef55abe54 Merge pull request 'Assign similar colors to child groups' () from chosen-colors into main
Reviewed-on: 
2024-11-10 11:26:54 +00:00
4089ff63ba Add copyright notice 2024-11-10 11:24:07 +00:00
fa3d3cf13b Assign similar colors to child groups 2024-11-10 12:22:58 +01:00
172193b0dc Merge pull request 'Use average discomfort instead of sum' () from fix/discomfort-average into main
Reviewed-on: 
2024-11-10 10:38:35 +00:00
021b82b28e Use average discomfort instead of sum 2024-11-10 11:34:26 +01:00
2d96f1c7c2 Merge pull request 'Apply a penalty if table sizes are not honored' () from table-size-discomfort into main
Reviewed-on: 
2024-11-10 10:25:37 +00:00
f3b70f5a31 Apply a penalty if table sizes are not honored 2024-11-10 11:22:51 +01:00
2deaaa64a5 Merge pull request 'Modify initial distribution of tables to guarantee there is no single-person table' () from fix/initial-group-creation into main
Reviewed-on: 
2024-11-10 09:46:53 +00:00
c12e2fc6a4 Add copyright notice 2024-11-10 09:17:34 +00:00
19d309a2cf Modify initial distribution of tables to guarantee there is no single-person table 2024-11-10 10:16:22 +01:00
Renovate Bot
9190348ff2 Update dependency rspec-rails to '~> 7.1.0' 2024-11-10 01:24:55 +00:00
b3bbeff739 Merge pull request 'Update dependency rails to v8' () from renovate/rails-8.x into main
Reviewed-on: 
2024-11-09 16:56:10 +00:00
ffc7fa3801 Update dependency rails to v8 2024-11-09 17:51:12 +01:00
91ebb9afee Merge pull request 'Install shoulda matchers, improve guests specs and change enum syntax' () from deprecation-warning-rails-8 into main
Reviewed-on: 
2024-11-09 16:48:32 +00:00
9035df5178 Install shoulda matchers, improve guests specs and change enum syntax 2024-11-09 17:45:23 +01:00
60a7b1342f Add license finder as a development dependency 2024-11-09 08:52:40 +01:00
Renovate Bot
d04f7211e7 Update dependency solid_queue to v1.0.1 2024-11-09 07:44:14 +00:00
61cfdd2424 Merge pull request 'Install project dependencies before running license finder' () from fix-license-finder-action into main
Reviewed-on: 
2024-11-09 07:43:41 +00:00
004624687a Update dockerfile 2024-11-09 08:39:57 +01:00
f7b227bc97 Update ruby version in Gemfile and .ruby-version 2024-11-09 08:39:17 +01:00
9856974684 Install project dependencies before running license finder 2024-11-09 08:35:57 +01:00
c23ecfbbad Merge pull request 'Update dependency factory_bot_rails to v6.4.4' () from renovate/factory_bot_rails-6.x-lockfile into main
Reviewed-on: 
2024-11-09 07:30:46 +00:00
Renovate Bot
b4664c5e1d Update dependency ruby to v3.3.6 2024-11-09 01:08:10 +00:00
Renovate Bot
c70c0c5f60 Update dependency factory_bot_rails to v6.4.4 2024-11-09 01:07:47 +00:00
Renovate Bot
7a4aad29c1 Update dependency factory_bot_rails to v6.4.4 2024-11-08 01:08:59 +00:00
cded122f22 Merge pull request 'Choose light colors for the groups' () from light-colors into main
Reviewed-on: 
2024-11-04 22:46:08 +00:00
5cceb1e6ed Choose light colors for the groups 2024-11-04 23:39:44 +01:00
1a39364322 Merge pull request 'Initial version of the README' () from documentation-setup into main
Reviewed-on: 
2024-11-04 22:21:54 +00:00
ca1a8a499f Initial version of the README 2024-11-04 23:18:44 +01:00
231ea3fda8 Merge pull request 'Assign a color to every group and expose it via API' () from group-color into main
Reviewed-on: 
2024-11-03 13:50:56 +00:00
1e23ad8e50 Order guests within same table by color 2024-11-03 14:48:54 +01:00
3ae90bfc4e Add copyright notice 2024-11-03 13:43:32 +00:00
a42f938530 Assign a color to every group and expose it via API 2024-11-03 14:41:09 +01:00
01a00cf35f Merge pull request 'Enqueue some simulations after running the seed file' () from seed-executions into main
Reviewed-on: 
2024-11-03 13:30:34 +00:00
a67696747b Enqueue some simulations after running the seed file 2024-11-03 14:18:30 +01:00
ed0e307e33 Merge pull request 'compose-development' () from compose-development into main
Reviewed-on: 
2024-11-03 13:16:29 +00:00
da8086605d Enable hot reload in the frontend 2024-11-03 14:13:13 +01:00
0d122b39d3 Merge pull request 'Fix undefined method' () from fix/undefined-method into main
Reviewed-on: 
2024-11-03 13:00:11 +00:00
a9016ed352 Enable hot reloading of modified code 2024-11-03 13:59:35 +01:00
94d0a42ac1 Fix undefined method 2024-11-03 13:56:10 +01:00
c2398c0d80 Avoid exposing internal ports 2024-11-03 13:53:36 +01:00
c2989b216f Merge pull request 'Recreate simulations whenever a guest changes their attendance' () from simulations-lifecycle into main
Reviewed-on: 
2024-11-03 12:51:13 +00:00
95651ee942 Merge remote-tracking branch 'origin/simulations-lifecycle' into compose-development 2024-11-03 13:50:19 +01:00
bae70b9884 Initial version 2024-11-03 13:40:50 +01:00
865e86e659 Merge branch 'main' into simulations-lifecycle 2024-11-03 11:23:38 +00:00
47c6fc7568 Merge pull request 'Include a step to verify nonfree dependencies are not committed' () from license_checks into main
Reviewed-on: 
2024-11-03 11:23:27 +00:00
ba4384307d Merge remote-tracking branch 'origin/main' into simulations-lifecycle 2024-11-03 12:04:54 +01:00
0d7f602441 Merge pull request 'Assign a name to every tables arrangement' () from arrangement-names into main
Reviewed-on: 
2024-11-03 11:04:07 +00:00
956863a496 Merge pull request 'Install solid queues framework' () from solid-queue-vns into main
Reviewed-on: 
2024-11-03 11:00:54 +00:00
89d9f722e1 Add ruby to the list of permitted licenses 2024-11-03 11:58:53 +01:00
2fd4549b00 Add copyright notice 2024-11-03 10:47:26 +00:00
a5c6def0b6 Merge branch 'main' into solid-queue-vns 2024-11-03 11:43:39 +01:00
eb2b111472 Merge branch 'main' into arrangement-names 2024-11-03 11:42:28 +01:00
3a8a83e60e Merge pull request 'Remove the email property from the guest model' () from remove-emails into main
Reviewed-on: 
2024-11-03 10:41:52 +00:00
dacc0aa74e Merge pull request 'Stop redundant builds' () from actions-concurrency into main
Reviewed-on: 
2024-11-03 10:31:34 +00:00
f0e8c570c9 Merge pull request 'Configure a build timeout of 30 minutes' () from build-timeout into main
Reviewed-on: 
2024-11-03 10:29:07 +00:00
c08f7bd37c Add copyright notice 2024-11-03 10:03:47 +00:00
f21aaa3723 Remove the email property from the guest model 2024-11-03 10:43:06 +01:00
b78165fb08 Merge remote-tracking branch 'origin/main' into actions-concurrency 2024-11-03 10:17:55 +01:00
d3d1e2c821 Merge branch 'main' into license_checks 2024-11-03 09:16:30 +00:00
7a1a2f7107 Merge branch 'main' into build-timeout 2024-11-03 09:16:20 +00:00
44972b7972 Merge pull request 'Stop building docker images on PR' () from build-main-only into main
Reviewed-on: 
2024-11-03 09:13:29 +00:00
22fbdf5ca3 Stop building docker images on PR 2024-11-03 10:07:08 +01:00
4f133ac3f5 Configure a build timeout of 30 minutes 2024-11-03 09:40:57 +01:00
56a8be21cc Include a step to verify nonfree dependencies are not committed 2024-11-03 09:38:51 +01:00
86e982164d Add copyright notice 2024-11-03 08:31:58 +00:00
33434db3f2 Stop redundant builds 2024-11-03 09:31:10 +01:00
35bf272ac8 Restart simulations whenever a guest changes their invitation status 2024-11-03 09:26:25 +01:00
50a5c90728 Only include potential guests in the simulation 2024-11-03 09:15:48 +01:00
15a5c51fb6 Add copyright notice 2024-11-03 08:12:24 +00:00
8d09fd733f Define a job to generate table simulations 2024-11-03 09:06:45 +01:00
3be71406f8 Add copyright notice 2024-11-03 09:06:45 +01:00
d15096b3a7 Install solid queues framework 2024-11-03 09:06:42 +01:00
1f919644a3 Define a job to generate table simulations 2024-11-03 09:06:17 +01:00
8d83c63fa6 Add copyright notice 2024-11-03 08:01:59 +00:00
a1b5e55b39 Install solid queues framework 2024-11-03 08:59:56 +01:00
066fdea504 Assign a name to every tables arrangement 2024-11-03 08:44:31 +01:00
f75f903363 Merge pull request 'Expose tables via API' () from arrangements-api into main
Reviewed-on: 
2024-11-02 12:19:16 +00:00
d6182392f6 Return only the top 3 arrangements 2024-11-02 12:50:45 +01:00
612cb9a789 Refine arrangement detail endpoint 2024-11-02 11:23:00 +01:00
d4c1e5aab0 Rewrite table arrangements endpoints 2024-11-02 10:40:43 +01:00
83fbac76f6 Merge pull request 'Remove acts_as_taggable_on gem' () from remove-acts-as-taggable into main
Reviewed-on: 
2024-11-02 08:54:23 +00:00
bf970af2c5 Remove tags table as well 2024-11-01 19:55:39 +00:00
eb85028eea Add copyright notice 2024-11-01 19:55:39 +00:00
5fef6f1011 Remove references to acts_as_taggable_on gem 2024-11-01 19:55:39 +00:00
ea129602b5 Uninstall acts as taggable on gem 2024-11-01 19:55:39 +00:00
7bb9d83c6e Merge pull request 'Remove HTML views and non-JSON endpoints' () from remove-html-views into main
Reviewed-on: 
2024-11-01 19:54:15 +00:00
db644c85be Remove HTML views 2024-11-01 19:08:01 +01:00
1f93fe42c5 Clean up unused methods and non-API endpoints from controllers 2024-11-01 19:07:12 +01:00
62a7af6db3 Merge pull request 'Adapt discomfort calculator to use groups instead of affinity tags' () from fix-hiearhy-load into main
Reviewed-on: 
2024-11-01 11:22:13 +00:00
d37dd44cd3 Update discomfort calculator to use group ids 2024-11-01 12:04:15 +01:00
b5693f549e Load groups hierarchy from data in DB 2024-11-01 11:55:32 +01:00
Renovate Bot
c1575bb7e1 Update dependency rubocop to v1.68.0 2024-11-01 01:40:11 +00:00
7c0a525c64 Add copyright notice 2024-10-31 23:34:15 +00:00
Renovate Bot
d8ef1f179c Update dependency rails to v7.2.2 2024-10-31 23:08:25 +00:00
5aa86da10c Merge pull request 'Include the copyright notice in spec files' () from copyright-specs into main
Reviewed-on: 
2024-10-28 22:48:04 +00:00
69f35a1e60 Merge pull request 'Feature: Expense summary endpoint' () from expenses-endpoint into main
Reviewed-on: 
2024-10-28 22:46:33 +00:00
658c2331ca Add copyright notice 2024-10-28 22:07:35 +00:00
ace03a6654 Include the copyright notice in spec files 2024-10-28 23:04:10 +01:00
253d6b0591 Add copyright notice 2024-10-28 22:02:49 +00:00
0fb50ea59b Expose expense summary endpoint 2024-10-28 23:01:46 +01:00
6c1bf75db8 Include guest count in the summary 2024-10-28 23:01:00 +01:00
d8884704d8 Implement query to get total expense summary 2024-10-28 22:57:03 +01:00
6eb2f858a3 Write tests for the totals query 2024-10-28 22:38:47 +01:00
e4dbbc78b7 Merge pull request 'Configure license and automated copyright notices' () from license into main
Reviewed-on: 
2024-10-27 22:14:47 +00:00
b7cabc1661 Add copyright notice 2024-10-27 21:42:45 +00:00
f3172bb38f Configure license and automated copyright notices 2024-10-27 22:30:04 +01:00
597a4a6b9c Merge pull request 'Feature: Allow changing the status of guests in bulk' () from bulk-status-changes into main
Reviewed-on: 
2024-10-27 19:45:53 +00:00
b4a30be77e Merge pull request 'Include an additional tentative status' () from tentative-status into main
Reviewed-on: 
2024-10-27 19:43:46 +00:00
5ddcd780f0 Merge pull request 'Order guests consistently within the same group' () from consistent-guest-order into main
Reviewed-on: 
2024-10-27 19:36:21 +00:00
7338bbf0a8 Include an additional tentative status 2024-10-27 19:25:24 +01:00
6219b74cb7 Order guests consistently within the same group 2024-10-27 19:13:32 +01:00
b8dfcf326f Implement the actual status update 2024-10-27 19:01:12 +01:00
9dc6dd3983 Update dependency ruby to v3.3.5 ()
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [ruby](https://www.ruby-lang.org) ([source](https://github.com/ruby/ruby)) | patch | `3.3.4` -> `3.3.5` |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOC42NC4xIiwidXBkYXRlZEluVmVyIjoiMzguNjQuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIl19-->

Co-authored-by: Renovate Bot <renovate@bustikiller.com>
Reviewed-on: 
Co-authored-by: Manuel Bustillo <bustikiller@bustikiller.com>
Co-committed-by: Manuel Bustillo <bustikiller@bustikiller.com>
2024-10-27 17:36:40 +00:00
a0de5eb663 Define nginx configuration file ()
Reviewed-on: 
Co-authored-by: Manuel Bustillo <bustikiller@bustikiller.com>
Co-committed-by: Manuel Bustillo <bustikiller@bustikiller.com>
2024-10-27 17:10:11 +00:00
507d068459 Configure endpoint to support bulk updates 2024-10-27 14:03:13 +01:00
Renovate Bot
50cc31a3cd Update dependency pg to v1.5.9 2024-10-25 22:12:26 +00:00
209 changed files with 6452 additions and 1154 deletions
.annotaterb.yml.dockerignore
.gitea/workflows
.github/workflows
.gitignore.rubocop.yml.ruby-versionCOPYING.mdDockerfileDockerfile.devGemfileGemfile.lockREADME.mdRakefile
app
channels/application_cable
controllers
extensions
helpers
jobs
mailers
models
queries
serializers
services
views
bin
config.ru
config

58
.annotaterb.yml Normal file

@ -0,0 +1,58 @@
---
:position: before
:position_in_additional_file_patterns: before
:position_in_class: before
:position_in_factory: before
:position_in_fixture: before
:position_in_routes: before
:position_in_serializer: before
:position_in_test: before
:classified_sort: true
:exclude_controllers: true
:exclude_factories: true
:exclude_fixtures: false
:exclude_helpers: true
:exclude_scaffolds: true
:exclude_serializers: false
:exclude_sti_subclasses: false
:exclude_tests: true
:force: false
:format_markdown: false
:format_rdoc: false
:format_yard: false
:frozen: false
:ignore_model_sub_dir: false
:ignore_unknown_models: false
:include_version: false
:show_check_constraints: false
:show_complete_foreign_keys: false
:show_foreign_keys: true
:show_indexes: true
:simple_indexes: false
:sort: false
:timestamp: false
:trace: false
:with_comment: true
:with_column_comments: true
:with_table_comments: true
:active_admin: false
:command:
:debug: false
:hide_default_column_types: ''
:hide_limit_column_types: ''
:ignore_columns:
:ignore_routes:
:models: true
:routes: false
:skip_on_db_migrate: false
:target_action: :do_annotations
:wrapper:
:wrapper_close:
:wrapper_open:
:classes_default_to_s: []
:additional_file_patterns: []
:model_dir:
- app/models
:require: []
:root_dir:
- ''

@ -35,3 +35,4 @@
/app/assets/builds/* /app/assets/builds/*
!/app/assets/builds/.keep !/app/assets/builds/.keep
/public/assets /public/assets
.docker-compose.yml

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

@ -1,33 +0,0 @@
name: Build Nginx-based docker image
on:
push:
branches:
- main
pull_request:
jobs:
build-static-assets:
runs-on: ubuntu-latest
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

@ -1,40 +0,0 @@
name: Run unit tests
on:
push:
branches:
- main
pull_request:
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
- 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

4
.gitignore vendored

@ -33,3 +33,7 @@
# Ignore master key for decrypting credentials and more. # Ignore master key for decrypting credentials and more.
/config/master.key /config/master.key
# Ignore swagger generated documentation
swagger/v1/swagger.yaml
wedding-planner.code-workspace

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.4 ruby-3.4.3

660
COPYING.md Normal file

@ -0,0 +1,660 @@
# GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc.
<https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
## Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains
free software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing
under this license.
The precise terms and conditions for copying, distribution and
modification follow.
## TERMS AND CONDITIONS
### 0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public
License.
"Copyright" also means copyright-like laws that apply to other kinds
of works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of
an exact copy. The resulting work is called a "modified version" of
the earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user
through a computer network, with no transfer of a copy, is not
conveying.
An interactive user interface displays "Appropriate Legal Notices" to
the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
### 1. Source Code.
The "source code" for a work means the preferred form of the work for
making modifications to it. "Object code" means any non-source form of
a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users can
regenerate automatically from other parts of the Corresponding Source.
The Corresponding Source for a work in source code form is that same
work.
### 2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not convey,
without conditions so long as your license otherwise remains in force.
You may convey covered works to others for the sole purpose of having
them make modifications exclusively for you, or provide you with
facilities for running those works, provided that you comply with the
terms of this License in conveying all material for which you do not
control copyright. Those thus making or running the covered works for
you must do so exclusively on your behalf, under your direction and
control, on terms that prohibit them from making any copies of your
copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under the
conditions stated below. Sublicensing is not allowed; section 10 makes
it unnecessary.
### 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such
circumvention is effected by exercising rights under this License with
respect to the covered work, and you disclaim any intention to limit
operation or modification of the work as a means of enforcing, against
the work's users, your or third parties' legal rights to forbid
circumvention of technological measures.
### 4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
### 5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these
conditions:
- a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
- b) The work must carry prominent notices stating that it is
released under this License and any conditions added under
section 7. This requirement modifies the requirement in section 4
to "keep intact all notices".
- c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
- d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
### 6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms of
sections 4 and 5, provided that you also convey the machine-readable
Corresponding Source under the terms of this License, in one of these
ways:
- a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
- b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the Corresponding
Source from a network server at no charge.
- c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
- d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
- e) Convey the object code using peer-to-peer transmission,
provided you inform other peers where the object code and
Corresponding Source of the work are being offered to the general
public at no charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal,
family, or household purposes, or (2) anything designed or sold for
incorporation into a dwelling. In determining whether a product is a
consumer product, doubtful cases shall be resolved in favor of
coverage. For a particular product received by a particular user,
"normally used" refers to a typical or common use of that class of
product, regardless of the status of the particular user or of the way
in which the particular user actually uses, or expects or is expected
to use, the product. A product is a consumer product regardless of
whether the product has substantial commercial, industrial or
non-consumer uses, unless such uses represent the only significant
mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to
install and execute modified versions of a covered work in that User
Product from a modified version of its Corresponding Source. The
information must suffice to ensure that the continued functioning of
the modified object code is in no case prevented or interfered with
solely because modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or
updates for a work that has been modified or installed by the
recipient, or for the User Product in which it has been modified or
installed. Access to a network may be denied when the modification
itself materially and adversely affects the operation of the network
or violates the rules and protocols for communication across the
network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
### 7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders
of that material) supplement the terms of this License with terms:
- a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
- b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
- c) Prohibiting misrepresentation of the origin of that material,
or requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
- d) Limiting the use for publicity purposes of names of licensors
or authors of the material; or
- e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
- f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions
of it) with contractual assumptions of liability to the recipient,
for any liability that these contractual assumptions directly
impose on those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions; the
above requirements apply either way.
### 8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your license
from a particular copyright holder is reinstated (a) provisionally,
unless and until the copyright holder explicitly and finally
terminates your license, and (b) permanently, if the copyright holder
fails to notify you of the violation by some reasonable means prior to
60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
### 9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or run
a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
### 10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
### 11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims owned
or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within the
scope of its coverage, prohibits the exercise of, or is conditioned on
the non-exercise of one or more of the rights that are specifically
granted under this License. You may not convey a covered work if you
are a party to an arrangement with a third party that is in the
business of distributing software, under which you make payment to the
third party based on the extent of your activity of conveying the
work, and under which the third party grants, to any of the parties
who would receive the covered work from you, a discriminatory patent
license (a) in connection with copies of the covered work conveyed by
you (or copies made from those copies), or (b) primarily for and in
connection with specific products or compilations that contain the
covered work, unless you entered into that arrangement, or that patent
license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
### 12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under
this License and any other pertinent obligations, then as a
consequence you may not convey it at all. For example, if you agree to
terms that obligate you to collect a royalty for further conveying
from those to whom you convey the Program, the only way you could
satisfy both those terms and this License would be to refrain entirely
from conveying the Program.
### 13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your
version supports such interaction) an opportunity to receive the
Corresponding Source of your version by providing access to the
Corresponding Source from a network server at no charge, through some
standard or customary means of facilitating copying of software. This
Corresponding Source shall include the Corresponding Source for any
work covered by version 3 of the GNU General Public License that is
incorporated pursuant to the following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
### 14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Affero General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever
published by the Free Software Foundation.
If the Program specifies that a proxy can decide which future versions
of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
### 15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT
WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND
PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE
DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
CORRECTION.
### 16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR
CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES
ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT
NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR
LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM
TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER
PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
### 17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
## How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these
terms.
To do so, attach the following notices to the program. It is safest to
attach them to the start of each source file to most effectively state
the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper
mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for
the specific requirements.
You should also get your employer (if you work as a programmer) or
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. For more information on this, and how to apply and follow
the GNU AGPL, see <https://www.gnu.org/licenses/>.

@ -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.4 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 ./

42
Dockerfile.dev Normal file

@ -0,0 +1,42 @@
# syntax = docker/dockerfile:1
# Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile
ARG RUBY_VERSION=3.4.3
FROM registry.docker.com/library/ruby:$RUBY_VERSION-slim as base
# Rails app lives here
WORKDIR /rails
RUN apt-get update && apt-get install -y nodejs wkhtmltopdf
FROM base as build
# Install packages needed to build gems
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y build-essential git libpq-dev libvips pkg-config libyaml-dev
# Install application gems
COPY Gemfile Gemfile.lock ./
RUN bundle install
# Copy application code
COPY . .
# Final stage for app image
FROM base
# Install packages needed for deployment
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y curl libvips postgresql-client && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives
# Copy built artifacts: gems, application
COPY --from=build /usr/local/bundle /usr/local/bundle
COPY --from=build /rails /rails
# Entrypoint prepares the database.
ENTRYPOINT ["/rails/bin/docker-entrypoint"]
# Start the server by default, this can be overwritten at runtime
EXPOSE 3000
CMD ["./bin/rails", "server", "--binding=0.0.0.0"]

32
Gemfile

@ -1,7 +1,8 @@
# frozen_string_literal: true
source 'https://rubygems.org' source 'https://rubygems.org'
ruby '3.3.4' ruby '3.4.3'
gem 'acts-as-taggable-on'
gem 'bootsnap', require: false gem 'bootsnap', require: false
gem 'csv' gem 'csv'
gem 'importmap-rails' gem 'importmap-rails'
@ -9,27 +10,48 @@ gem 'jbuilder'
gem 'money' gem 'money'
gem 'pg', '~> 1.1' gem 'pg', '~> 1.1'
gem 'puma', '>= 5.0' gem 'puma', '>= 5.0'
gem 'rails', '~> 7.2.0', '>= 7.2.1' gem 'rails', '~> 8.0.0', '>= 8.0.0'
gem 'redis', '>= 4.0.1' gem 'redis', '>= 4.0.1'
gem 'sprockets-rails' gem 'sprockets-rails'
gem 'stimulus-rails' 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 '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
gem 'annotaterb'
gem 'debug', platforms: %i[mri windows] gem 'debug', platforms: %i[mri windows]
gem 'factory_bot_rails' gem 'factory_bot_rails'
gem 'faker' gem 'license_finder'
gem 'pry' gem 'pry'
gem 'rspec-rails', '~> 7.0.0' gem 'rspec-rails', '~> 8.0.0'
gem 'shoulda-matchers', '~> 6.0'
end end
group :development do group :development do
gem 'letter_opener_web'
gem 'rubocop' 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' gem 'web-console'
end end
gem 'chroma'
gem 'solid_queue', '~> 1.0'
gem 'devise', '~> 4.9'
gem 'wicked_pdf', '~> 2.8'
gem 'rqrcode', '~> 3.1'

@ -1,67 +1,67 @@
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
actioncable (7.2.1.2) actioncable (8.0.2.1)
actionpack (= 7.2.1.2) actionpack (= 8.0.2.1)
activesupport (= 7.2.1.2) 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 (7.2.1.2) actionmailbox (8.0.2.1)
actionpack (= 7.2.1.2) actionpack (= 8.0.2.1)
activejob (= 7.2.1.2) activejob (= 8.0.2.1)
activerecord (= 7.2.1.2) activerecord (= 8.0.2.1)
activestorage (= 7.2.1.2) activestorage (= 8.0.2.1)
activesupport (= 7.2.1.2) activesupport (= 8.0.2.1)
mail (>= 2.8.0) mail (>= 2.8.0)
actionmailer (7.2.1.2) actionmailer (8.0.2.1)
actionpack (= 7.2.1.2) actionpack (= 8.0.2.1)
actionview (= 7.2.1.2) actionview (= 8.0.2.1)
activejob (= 7.2.1.2) activejob (= 8.0.2.1)
activesupport (= 7.2.1.2) activesupport (= 8.0.2.1)
mail (>= 2.8.0) mail (>= 2.8.0)
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
actionpack (7.2.1.2) actionpack (8.0.2.1)
actionview (= 7.2.1.2) actionview (= 8.0.2.1)
activesupport (= 7.2.1.2) activesupport (= 8.0.2.1)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
racc rack (>= 2.2.4)
rack (>= 2.2.4, < 3.2)
rack-session (>= 1.0.1) rack-session (>= 1.0.1)
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6) rails-html-sanitizer (~> 1.6)
useragent (~> 0.16) useragent (~> 0.16)
actiontext (7.2.1.2) actiontext (8.0.2.1)
actionpack (= 7.2.1.2) actionpack (= 8.0.2.1)
activerecord (= 7.2.1.2) activerecord (= 8.0.2.1)
activestorage (= 7.2.1.2) activestorage (= 8.0.2.1)
activesupport (= 7.2.1.2) activesupport (= 8.0.2.1)
globalid (>= 0.6.0) globalid (>= 0.6.0)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (7.2.1.2) actionview (8.0.2.1)
activesupport (= 7.2.1.2) 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 (7.2.1.2) activejob (8.0.2.1)
activesupport (= 7.2.1.2) activesupport (= 8.0.2.1)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (7.2.1.2) activemodel (8.0.2.1)
activesupport (= 7.2.1.2) activesupport (= 8.0.2.1)
activerecord (7.2.1.2) activerecord (8.0.2.1)
activemodel (= 7.2.1.2) activemodel (= 8.0.2.1)
activesupport (= 7.2.1.2) activesupport (= 8.0.2.1)
timeout (>= 0.4.0) timeout (>= 0.4.0)
activestorage (7.2.1.2) activestorage (8.0.2.1)
actionpack (= 7.2.1.2) actionpack (= 8.0.2.1)
activejob (= 7.2.1.2) activejob (= 8.0.2.1)
activerecord (= 7.2.1.2) activerecord (= 8.0.2.1)
activesupport (= 7.2.1.2) activesupport (= 8.0.2.1)
marcel (~> 1.0) marcel (~> 1.0)
activesupport (7.2.1.2) activesupport (8.0.2.1)
base64 base64
benchmark (>= 0.3)
bigdecimal bigdecimal
concurrent-ruby (~> 1.0, >= 1.3.1) concurrent-ruby (~> 1.0, >= 1.3.1)
connection_pool (>= 2.2.5) connection_pool (>= 2.2.5)
@ -71,56 +71,86 @@ GEM
minitest (>= 5.1) minitest (>= 5.1)
securerandom (>= 0.3) securerandom (>= 0.3)
tzinfo (~> 2.0, >= 2.0.5) tzinfo (~> 2.0, >= 2.0.5)
acts-as-taggable-on (11.0.0) uri (>= 0.13.1)
activerecord (>= 7.0, < 8.0) acts_as_tenant (1.0.1)
zeitwerk (>= 2.4, < 3.0) rails (>= 6.0)
ast (2.4.2) addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
annotaterb (4.19.0)
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)
bigdecimal (3.1.8) bcrypt (3.1.20)
benchmark (0.4.1)
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)
logger (~> 1.5)
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.3.4) 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)
diff-lcs (1.5.1) devise (4.9.4)
drb (2.2.1) bcrypt (~> 3.0)
erubi (1.13.0) orm_adapter (~> 0.1)
railties (>= 4.1.0)
responders
warden (~> 1.2.3)
diff-lcs (1.6.2)
drb (2.2.3)
erb (5.0.2)
erubi (1.13.1)
et-orbi (1.2.11)
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)
et-orbi (~> 1, >= 1.2.11)
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.2) json (2.13.2)
json-schema (5.0.1)
addressable (~> 2.8)
jsonapi-deserializable (0.2.0) jsonapi-deserializable (0.2.0)
jsonapi-parser (0.1.1) jsonapi-parser (0.1.1)
jsonapi-rails (0.4.1) jsonapi-rails (0.4.1)
@ -132,9 +162,28 @@ 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)
logger (1.6.1) launchy (3.0.1)
loofah (2.22.0) addressable (~> 2.8)
childprocess (~> 5.0)
letter_opener (1.10.0)
launchy (>= 2.2, < 4)
letter_opener_web (3.0.0)
actionmailer (>= 6.1)
letter_opener (~> 1.9)
railties (>= 6.1)
rexml
license_finder (7.2.1)
bundler
csv (~> 3.2)
rubyzip (>= 1, < 3)
thor (~> 1.2)
tomlrb (>= 1.3, < 2.1)
with_env (= 1.1.0)
xml-simple (~> 1.1.9)
lint_roller (1.1.0)
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)
@ -143,89 +192,111 @@ GEM
net-pop net-pop
net-smtp net-smtp
marcel (1.0.4) marcel (1.0.4)
method_source (1.0.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.4.17) 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.3) 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)
parallel (1.26.3) orm_adapter (0.5.0)
parser (3.3.5.0) ostruct (0.6.2)
parallel (1.27.0)
parser (3.3.9.0)
ast (~> 2.4.1) ast (~> 2.4.1)
racc racc
pg (1.5.8) pg (1.6.2)
pry (0.14.2) 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.1.2) psych (5.2.6)
date
stringio stringio
puma (6.4.3) public_suffix (6.0.1)
puma (6.6.1)
nio4r (~> 2.0) nio4r (~> 2.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.1.0) rackup (2.2.1)
rack (>= 3) rack (>= 3)
webrick (~> 1.8) rails (8.0.2.1)
rails (7.2.1.2) actioncable (= 8.0.2.1)
actioncable (= 7.2.1.2) actionmailbox (= 8.0.2.1)
actionmailbox (= 7.2.1.2) actionmailer (= 8.0.2.1)
actionmailer (= 7.2.1.2) actionpack (= 8.0.2.1)
actionpack (= 7.2.1.2) actiontext (= 8.0.2.1)
actiontext (= 7.2.1.2) actionview (= 8.0.2.1)
actionview (= 7.2.1.2) activejob (= 8.0.2.1)
activejob (= 7.2.1.2) activemodel (= 8.0.2.1)
activemodel (= 7.2.1.2) activerecord (= 8.0.2.1)
activerecord (= 7.2.1.2) activestorage (= 8.0.2.1)
activestorage (= 7.2.1.2) activesupport (= 8.0.2.1)
activesupport (= 7.2.1.2)
bundler (>= 1.15.0) bundler (>= 1.15.0)
railties (= 7.2.1.2) 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 (7.2.1.2) railties (8.0.2.1)
actionpack (= 7.2.1.2) actionpack (= 8.0.2.1)
activesupport (= 7.2.1.2) 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)
@ -233,46 +304,97 @@ 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.10) reline (0.6.2)
io-console (~> 0.5) io-console (~> 0.5)
rspec-core (3.13.2) responders (3.1.1)
actionpack (>= 5.2)
railties (>= 5.2)
rexml (3.3.9)
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.0.1) 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)
rubocop (1.67.0) rswag (2.16.0)
rswag-api (= 2.16.0)
rswag-specs (= 2.16.0)
rswag-ui (= 2.16.0)
rswag-api (2.16.0)
activesupport (>= 5.2, < 8.1)
railties (>= 5.2, < 8.1)
rswag-specs (2.16.0)
activesupport (>= 5.2, < 8.1)
json-schema (>= 2.2, < 6.0)
railties (>= 5.2, < 8.1)
rspec-core (>= 2.14)
rswag-ui (2.16.0)
actionpack (>= 5.2, < 8.1)
railties (>= 5.2, < 8.1)
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.32.3) 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)
securerandom (0.3.1) rubyzip (2.3.2)
securerandom (0.4.1)
shoulda-matchers (6.5.0)
activesupport (>= 5.2.0)
solid_queue (1.2.1)
activejob (>= 7.1)
activerecord (>= 7.1)
concurrent-ruby (>= 1.3.1)
fugit (~> 1.11.0)
railties (>= 7.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)
@ -282,27 +404,37 @@ 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.1) stringio (3.1.7)
thor (1.3.2) thor (1.4.0)
tilt (2.4.0) tilt (2.4.0)
timeout (0.4.1) timeout (0.4.3)
turbo-rails (2.0.11) tomlrb (2.0.3)
actionpack (>= 6.0.0) turbo-rails (2.0.16)
railties (>= 6.0.0) actionpack (>= 7.1.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)
useragent (0.16.10) uri (1.0.3)
useragent (0.16.11)
warden (1.2.9)
rack (>= 2.0.9)
web-console (4.2.1) web-console (4.2.1)
actionview (>= 6.0.0) actionview (>= 6.0.0)
activemodel (>= 6.0.0) activemodel (>= 6.0.0)
bindex (>= 0.4.0) bindex (>= 0.4.0)
railties (>= 6.0.0) railties (>= 6.0.0)
webrick (1.8.2) websocket-driver (0.7.7)
websocket-driver (0.7.6) base64
websocket-extensions (>= 0.1.0) websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5) websocket-extensions (0.1.5)
zeitwerk (2.7.1) wicked_pdf (2.8.2)
activesupport
ostruct
with_env (1.1.0)
xml-simple (1.1.9)
rexml
zeitwerk (2.7.3)
PLATFORMS PLATFORMS
aarch64-linux aarch64-linux
@ -313,34 +445,222 @@ PLATFORMS
x86_64-linux x86_64-linux
DEPENDENCIES DEPENDENCIES
acts-as-taggable-on acts_as_tenant
annotaterb
bootsnap bootsnap
chroma
csv csv
debug debug
devise (~> 4.9)
factory_bot_rails factory_bot_rails
faker faker
httparty
importmap-rails importmap-rails
jbuilder jbuilder
jsonapi-rails jsonapi-rails
letter_opener_web
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 (~> 7.2.0, >= 7.2.1) rails (~> 8.0.0, >= 8.0.0)
react-rails react-rails
redis (>= 4.0.1) redis (>= 4.0.1)
rspec-rails (~> 7.0.0) rqrcode (~> 3.1)
rspec-rails (~> 8.0.0)
rswag
rubocop rubocop
rubocop-factory_bot
rubocop-rails
rubocop-rspec
rubocop-rspec_rails
rubytree rubytree
shoulda-matchers (~> 6.0)
solid_queue (~> 1.0)
sprockets-rails sprockets-rails
stimulus-rails stimulus-rails
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.4p94 ruby 3.4.3p32
BUNDLED WITH BUNDLED WITH
2.5.17 2.6.1

104
README.md

@ -1,24 +1,102 @@
# README # Libre Wedding Planner
This README would normally document whatever steps are necessary to get the Libre Wedding Planner is Free, Open Source Software that helps organize several aspects of a wedding.
application up and running.
Things you may want to cover: The project is not production-ready yet.
* Ruby version ## Features
* System dependencies The follwing features are either developed or under active development:
* Configuration - Guests management
- Expense management
- Seating chart
* Database creation
* Database initialization ## Next steps
* How to run the test suite Some ideas we would like to implement next:
* Services (job queues, cache servers, search engines, etc.) - Authentication (required to make an instance public)
- Website with wedding information
- Attendance confirmation forms
- Multitenancy
* Deployment instructions # Development setup
Libre Wedding Planner is made of two main pieces:
- The backend (this repo), built with Ruby (on Rails)
- The frontend (repo [here](https://gitea.bustikiller.com/bustikiller/wedding-planner-frontend/)), built with NextJS and React. You will need both to have the service fully working.
Both repositories are expected to live have a common parent directory:
```
projects <or anything else>
|-> wedding-planner
|-> wedding-planner-frontend
```
## Docker compose
Docker compose is the recommended way to run Libre Wedding Planner for development purposes. After downloading both repositories, `cd` to the root of `wedding-planner` and run:
```bash
docker compose up --build
```
Several containers will be started:
- backend: starts a Rails server that will act as an API.
- workers: starts a runner of [solid queue](https://github.com/rails/solid_queue/) that takes .care of async tasks.
- frontend: starts a NextJS application in charge of the frontend.
- nginx: A reverse proxy that the backend and frontend under the same domain, and routes all requests to the upstream services.
- db: A Postgres instance used by the backend service.
The backend service will seed the database with fake data. It's worth noting that the Postgres container does not have a volume, so the application will be seeded every time the container is created.
The backend, frontend and workers have hot-reloading enabled, so changes made to the codebase should be reflected in the application on the next request.
Please, include this in your `/etc/hosts` file:
```
127.0.0.1 libre-wedding-planner.app.localhost
```
Once all containers have started, visit http://libre-wedding-planner.app.localhost/default/dashboard to load the application.
## 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
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.
## Testing
Unit tests can be executed with
```
bundle exec rspec
```
## API documentation
Generate the OpenAPI documentation with the command:
```
rake rswag:specs:swaggerize
```
The documentation is available in Swagger UI in http://libre-wedding-planner.app.localhost/api/api-docs/index.html. If testing the API through the UI, you will need to select the second server (which includes the `/api` path), intended for development.
## Contributing
Contributions of all kinds (code, UX/UI, testing, translations, etc.) are welcome. The procedures to contribute are still being defined, but don't hesitate to reach out in case you want to participate.
# License
This project is licensed under the GNU Affero General Public License (AGPL). Check [COPYING.md](./COPYING.md) for additional information.
* ...

@ -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,3 +1,7 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# frozen_string_literal: true
module ApplicationCable module ApplicationCable
class Channel < ActionCable::Channel::Base class Channel < ActionCable::Channel::Base
end end

@ -1,3 +1,7 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# frozen_string_literal: true
module ApplicationCable module ApplicationCable
class Connection < ActionCable::Connection::Base class Connection < ActionCable::Connection::Base
end end

@ -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,2 +1,68 @@
# 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!
after_action :set_csrf_cookie
skip_before_action :verify_authenticity_token, if: :development_swagger?
rescue_from ActiveRecord::RecordInvalid do |exception|
render json: {
message: 'Record invalid',
errors: exception.record.errors.full_messages
}, status: :unprocessable_entity
end
rescue_from ActionController::ParameterMissing do |exception|
render json: {
message: 'Parameter missing',
errors: [exception.message]
}, status: :bad_request
end
rescue_from ActiveRecord::RecordNotFound do |exception|
render json: {
message: 'Record not found',
errors: [exception.message]
}, status: :not_found
end
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?
Rails.env.test? ||
(Rails.env.development? && request.headers['referer']&.include?('/api-docs/index.html'))
end
def set_csrf_cookie
cookies['csrf-token'] = {
value: form_authenticity_token,
secure: false,
same_site: :strict
}
end
end end

@ -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,70 +1,34 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# frozen_string_literal: true
class ExpensesController < ApplicationController class ExpensesController < ApplicationController
before_action :set_expense, only: %i[ show edit update destroy ] def summary
render json: Expenses::TotalQuery.new.call
end
# GET /expenses or /expenses.json
def index def index
@expenses = Expense.all render json: Expense.order(pricing_type: :asc, amount: :desc).as_json(only: %i[id name amount pricing_type])
end end
# GET /expenses/1 or /expenses/1.json
def show
end
# GET /expenses/new
def new
@expense = Expense.new
end
# GET /expenses/1/edit
def edit
end
# POST /expenses or /expenses.json
def create def create
@expense = Expense.new(expense_params) Expense.create!(expense_params)
render json: {}, status: :created
respond_to do |format|
if @expense.save
format.html { redirect_to expense_url(@expense), notice: "Expense was successfully created." }
format.json { render :show, status: :created, location: @expense }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: @expense.errors, status: :unprocessable_entity }
end
end
end end
# PATCH/PUT /expenses/1 or /expenses/1.json
def update def update
respond_to do |format| Expense.find(params[:id]).update!(expense_params)
if @expense.update(expense_params) render json: {}, status: :ok
format.html { redirect_to expense_url(@expense), notice: "Expense was successfully updated." }
format.json { render :show, status: :ok, location: @expense }
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: @expense.errors, status: :unprocessable_entity }
end
end
end end
# DELETE /expenses/1 or /expenses/1.json
def destroy def destroy
@expense.destroy! Expense.find(params[:id]).destroy!
render json: {}, status: :ok
respond_to do |format|
format.html { redirect_to expenses_url, notice: "Expense was successfully destroyed." }
format.json { head :no_content }
end
end end
private private
# Use callbacks to share common setup or constraints between actions.
def set_expense
@expense = Expense.find(params[:id])
end
# Only allow a list of trusted parameters through.
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,6 +1,46 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# frozen_string_literal: true
class GroupsController < ApplicationController class GroupsController < ApplicationController
def index def index
roots = Group.where(parent_id: nil) query_result = Groups::SummaryQuery.new.call.as_json.map(&:deep_symbolize_keys).map do |group|
render jsonapi: roots, include: [children: [children: [:children]]] {
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,89 +1,45 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# frozen_string_literal: true
require 'csv' require 'csv'
class GuestsController < ApplicationController class GuestsController < ApplicationController
before_action :set_guest, only: %i[show edit update destroy] GUEST_PARAMS = { only: %i[id name status], include: { group: { only: %i[id name] } } }.freeze
skip_before_action :authenticate_user!, only: :update
# GET /guests or /guests.json
def index def index
@guests = Guest.all render json: Guest.includes(:group)
.joins(:group) .left_joins(:group)
.order('groups.name' => :asc) .order('groups.name' => :asc, invitation_id: :asc, name: :asc)
.as_json(GUEST_PARAMS)
render jsonapi: @guests
end end
# GET /guests/1 or /guests/1.json
def show; end
# GET /guests/new
def new
@guest = Guest.new
end
# GET /guests/1/edit
def edit; end
# POST /guests or /guests.json
def create def create
@guest = Guest.new(guest_params) guest = Guest.create!(guest_params)
render json: guest.as_json(GUEST_PARAMS), status: :created
respond_to do |format|
if @guest.save
format.html { redirect_to guest_url(@guest), notice: 'Guest was successfully created.' }
format.json { render :show, status: :created, location: @guest }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: @guest.errors, status: :unprocessable_entity }
end
end
end end
# PATCH/PUT /guests/1 or /guests/1.json
def update def update
respond_to do |format| guest = Guest.find(params[:id])
if @guest.update(guest_params) guest.update!(guest_params)
format.html { redirect_to guest_url(@guest), notice: 'Guest was successfully updated.' }
format.json { render :show, status: :ok, location: @guest } if !user_signed_in? && guest.saved_change_to_status?
else AdminMailer.with(guest_id: guest.id).attendance_change_email.deliver_later
format.html { render :edit, status: :unprocessable_entity } end
format.json { render json: @guest.errors, status: :unprocessable_entity }
end render json: guest.as_json(GUEST_PARAMS), status: :ok
end
end end
# DELETE /guests/1 or /guests/1.json
def destroy def destroy
@guest.destroy! Guest.find(params[:id]).destroy!
render json: {}, status: :ok
respond_to do |format|
format.html { redirect_to guests_url, notice: 'Guest was successfully destroyed.' }
format.json { head :no_content }
end
end
def import
csv = CSV.parse(params[:file].read, headers: true)
ActiveRecord::Base.transaction do
csv.each do |row|
guest = Guest.create!(first_name: row['name'])
guest.affinity_group_list.add(row['affinity_group'])
guest.save!
end
end
redirect_to guests_url
end end
private private
# Use callbacks to share common setup or constraints between actions.
def set_guest
@guest = Guest.find(params[:id])
end
# Only allow a list of trusted parameters through.
def guest_params def guest_params
params.require(:guest).permit(:first_name, :last_name, :email, :phone) user_signed_in? ? params.expect(guest: %i[name group_id status]) : params.expect(guest: %i[status])
end end
end end

@ -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

@ -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,12 +1,57 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# frozen_string_literal: true
class TablesArrangementsController < ApplicationController class TablesArrangementsController < ApplicationController
def index def index
@tables_arrangements = TablesArrangement.all.order(discomfort: :asc).limit(10) 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
@tables_arrangement = TablesArrangement.find(params[:id]) Guest.joins(:seats, :group)
@seats = @tables_arrangement.seats .where(seats: { tables_arrangement_id: params[:id] })
.includes(guest: %i[affinity_groups unbreakable_bonds]) .select('guests.*', 'groups.color', 'seats.table_number')
.group_by(&:table_number) .group_by(&:table_number)
.map { |number, guests| format(number:, guests:) }
.then { |result| render json: { id: params[:id], tables: result } }
end
def create
ActiveRecord::Base.transaction do
tables_arrangement = TablesArrangement.create!(status: :not_started)
TableSimulatorJob.perform_later(current_tenant.id, tables_arrangement.id)
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

@ -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

@ -0,0 +1,27 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# frozen_string_literal: true
module Users
class ConfirmationsController < Devise::ConfirmationsController
clear_respond_to
respond_to :json
def show
super do |resource|
if resource.errors.empty?
respond_to do |format|
format.json { render json: resource, status: :ok }
format.any { redirect_to root_path }
end
else
render json: {
message: 'Record invalid',
errors: resource.errors.full_messages
}, status: :unprocessable_entity
end
return
end
end
end
end

@ -0,0 +1,32 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# frozen_string_literal: true
module Users
class RegistrationsController < Devise::RegistrationsController
clear_respond_to
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

@ -0,0 +1,10 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# frozen_string_literal: true
module Users
class SessionsController < Devise::SessionsController
clear_respond_to
respond_to :json
end
end

@ -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,3 +1,7 @@
# 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)
return 0 if self == another_node return 0 if self == another_node

@ -1,2 +1,6 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# frozen_string_literal: true
module ApplicationHelper module ApplicationHelper
end end

@ -1,2 +1,6 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# frozen_string_literal: true
module ExpensesHelper module ExpensesHelper
end end

@ -1,2 +1,6 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# frozen_string_literal: true
module GroupsHelper module GroupsHelper
end end

@ -1,2 +1,6 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# frozen_string_literal: true
module GuestsHelper module GuestsHelper
end end

@ -1,2 +1,6 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# frozen_string_literal: true
module TablesArrangementsHelper module TablesArrangementsHelper
end end

@ -1,3 +1,7 @@
# 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
# retry_on ActiveRecord::Deadlocked # retry_on ActiveRecord::Deadlocked

@ -0,0 +1,51 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# frozen_string_literal: true
class TableSimulatorJob < ApplicationJob
queue_as :default
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.add_optimization(Tables::Swap)
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.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.target_function(&:discomfort)
best_solution = engine.run
best_solution.save!
tables_arrangement.update_columns(status: :completed)
end
end
end

@ -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,4 +1,18 @@
# 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,3 +1,7 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# frozen_string_literal: true
class ApplicationRecord < ActiveRecord::Base class ApplicationRecord < ActiveRecord::Base
primary_abstract_class primary_abstract_class
end end

@ -1,2 +1,34 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# frozen_string_literal: true
# == Schema Information
#
# Table name: expenses
#
# id :uuid not null, primary key
# amount :decimal(, )
# name :string
# pricing_type :enum default("fixed"), not null
# created_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,
fixed: 'fixed',
per_person: 'per_person'
validates :name, presence: true
validates :amount, presence: true, numericality: { greater_than: 0 }
validates :pricing_type, presence: true
end end

@ -1,9 +1,77 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# frozen_string_literal: true
# == Schema Information
#
# Table name: groups
#
# id :uuid not null, primary key
# color :string
# icon :string
# name :string not null
# order :integer default(1), not null
# created_at :datetime not null
# updated_at :datetime not null
# parent_id :uuid
# wedding_id :uuid not null
#
# Indexes
#
# index_groups_on_name (name) UNIQUE
# index_groups_on_parent_id (parent_id)
# index_groups_on_wedding_id (wedding_id)
#
# Foreign Keys
#
# 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
has_many :guests before_create :set_color
scope :roots, -> { where(parent_id: nil) }
has_many :guests, dependent: :nullify
def colorize_children(generation = 1)
children.zip(palette(generation)) do |child, raw_color|
final_color = raw_color.paint
final_color.brighten(60) if final_color.dark?
child.update!(color: final_color)
child.colorize_children(generation + 1)
end
end
def affinities
GroupAffinity.where(group_a_id: id).or(GroupAffinity.where(group_b_id: id))
end
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
return if color.present?
new_color = "##{SecureRandom.hex(3)}".paint
new_color = new_color.lighten(30) if new_color.dark?
self.color = new_color
end
end end

@ -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,15 +1,49 @@
class Guest < ApplicationRecord # Copyright (C) 2024-2025 LibreWeddingPlanner contributors
acts_as_taggable_on :affinity_groups, :unbreakable_bonds
belongs_to :group
enum status: { # frozen_string_literal: true
# == Schema Information
#
# Table name: guests
#
# id :uuid not null, primary key
# name :string
# phone :string
# status :integer default("considered")
# created_at :datetime not null
# updated_at :datetime not null
# group_id :uuid
# invitation_id :uuid
# wedding_id :uuid not null
#
# Indexes
#
# index_guests_on_group_id (group_id)
# index_guests_on_invitation_id (invitation_id)
# index_guests_on_wedding_id (wedding_id)
#
# Foreign Keys
#
# fk_rails_... (group_id => groups.id)
# fk_rails_... (invitation_id => invitations.id)
# fk_rails_... (wedding_id => weddings.id) ON DELETE => cascade
#
class Guest < ApplicationRecord
acts_as_tenant :wedding
belongs_to :group, optional: true
belongs_to :invitation, optional: true
enum :status, {
considered: 0, considered: 0,
invited: 10, invited: 10,
confirmed: 20, confirmed: 20,
declined: 30 declined: 30,
} tentative: 40
}, validate: true
def full_name validates :name, presence: true
"#{first_name} #{last_name}"
end scope :potential, -> { where.not(status: %i[declined considered]) }
has_many :seats, dependent: :delete_all
end end

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,33 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# frozen_string_literal: true
# == Schema Information
#
# Table name: seats
#
# id :uuid not null, primary key
# table_number :integer
# created_at :datetime not null
# updated_at :datetime not null
# guest_id :uuid not null
# tables_arrangement_id :uuid not null
# wedding_id :uuid not null
#
# Indexes
#
# index_seats_on_guest_id (guest_id)
# index_seats_on_tables_arrangement_id (tables_arrangement_id)
# index_seats_on_wedding_id (wedding_id)
#
# Foreign Keys
#
# fk_rails_... (guest_id => guests.id)
# 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,3 +1,39 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# frozen_string_literal: true
# == Schema Information
#
# Table name: tables_arrangements
#
# id :uuid not null, primary key
# digest :uuid not null
# discomfort :integer
# name :string not null
# progress :float default(0.0), not null
# status :string default("complete"), not null
# created_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
before_create :assign_name
private
def assign_name
self.name = "#{Faker::Adjective.positive} #{Faker::Creature::Animal.name}".capitalize
end
end end

42
app/models/user.rb Normal file

@ -0,0 +1,42 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# frozen_string_literal: true
# == Schema Information
#
# Table name: users
#
# id :uuid not null, primary key
# confirmation_sent_at :datetime
# confirmation_token :string
# confirmed_at :datetime
# email :string default(""), not null
# encrypted_password :string default(""), not null
# failed_attempts :integer default(0), not null
# locked_at :datetime
# reset_password_sent_at :datetime
# reset_password_token :string
# unconfirmed_email :string
# unlock_token :string
# created_at :datetime not null
# updated_at :datetime not null
# wedding_id :uuid not null
#
# Indexes
#
# index_users_on_confirmation_token (confirmation_token) UNIQUE
# index_users_on_email (email) UNIQUE
# index_users_on_reset_password_token (reset_password_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
acts_as_tenant :wedding
devise :database_authenticatable, :registerable,
:recoverable, :validatable, :confirmable, :lockable
end

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

@ -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

@ -0,0 +1,50 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# frozen_string_literal: true
module Expenses
class TotalQuery
private attr_reader :wedding
def initialize(wedding:)
@wedding = wedding
end
def call
ActiveRecord::Base.connection.execute(
ActiveRecord::Base.sanitize_sql_array([query, { wedding_id: wedding.id }])
).first
end
private
def query
<<~SQL.squish
WITH guest_count AS (#{guest_count_per_status}),
expense_summary AS (#{expense_summary})
SELECT guest_count.confirmed as confirmed_guests,
guest_count.projected as projected_guests,
expense_summary.fixed + expense_summary.variable * guest_count.confirmed as total_confirmed,
expense_summary.fixed + expense_summary.variable * guest_count.projected as total_projected
FROM guest_count, expense_summary;
SQL
end
def expense_summary
<<~SQL.squish
SELECT coalesce(sum(amount) filter (where pricing_type = 'fixed'), 0) as fixed,
coalesce(sum(amount) filter (where pricing_type = 'per_person'), 0) as variable
FROM expenses
WHERE wedding_id = :wedding_id
SQL
end
def guest_count_per_status
<<~SQL.squish
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
FROM guests
WHERE wedding_id = :wedding_id
SQL
end
end
end

@ -0,0 +1,31 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# frozen_string_literal: true
module Groups
class SummaryQuery
def call
Group.left_joins(:guests).group(:id).pluck_to_hash(
:id,
:name,
:icon,
:parent_id,
:color,
*count_expressions
)
end
private
def count_expressions
[
Arel.sql('count(*) filter (where status IS NOT NULL) as total'),
Arel.sql('count(*) filter (where status = 0) as considered'),
Arel.sql('count(*) filter (where status = 10) as invited'),
Arel.sql('count(*) filter (where status = 20) as confirmed'),
Arel.sql('count(*) filter (where status = 30) as declined'),
Arel.sql('count(*) filter (where status = 40) as tentative')
]
end
end
end

@ -1,3 +1,7 @@
# 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,14 +1,14 @@
# 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, :email, :group_id, :status attributes :id, :status
attribute :name do attribute :name do
"#{@object.first_name} #{@object.last_name}" @object.name
end
attribute :group_name do
@object.group.name
end end
attribute :status do attribute :status do

@ -1,27 +1,86 @@
# 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
@references = {} @references = {}
Group.roots.each do |group|
self << group.id
hydrate(group)
end end
def find(name) discomforts
@references[name] invitation_counts
freeze
end end
def <<(name) def find(id)
new_node = Tree::TreeNode.new(name) @references[id]
super(new_node).tap { @references[name] = new_node }
end end
def register_child(parent_name, child_name) def <<(id)
@references[parent_name] << Tree::TreeNode.new(child_name).tap { |child_node| @references[child_name] = child_node } new_node = Tree::TreeNode.new(id)
super(new_node).tap { @references[id] = new_node }
end end
def distance(name_a, name_b) def register_child(parent_id, child_id)
return nil if @references[name_a].nil? || @references[name_b].nil? @references[parent_id] << Tree::TreeNode.new(child_id).tap { |child_node| @references[child_id] = child_node }
end
@references[name_a].distance_to_common_ancestor(@references[name_b]) def distance(id_a, id_b)
return nil if @references[id_a].nil? || @references[id_b].nil?
@references[id_a].distance_to_common_ancestor(@references[id_b])
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
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)
group.children.each do |child|
register_child(group.id, child.id)
hydrate(child)
end
end end
end end

@ -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,24 +1,60 @@
# 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
cohesion_penalty breakdown.values.sum
end
def breakdown
@breakdown ||= { table_size_penalty:, cohesion_penalty:, invitations_penalty: }
end end
private private
#
# Calculates the penalty associated with violating the table size constraints. The penalty is
# zero when the limits are honored, and it increases linearly as the number of guests deviates
# from the limits. Overcapacity is penalized more severely than undercapacity.
#
# @return [Number] The penalty associated with violating the table size constraints.
#
def table_size_penalty
case table.size
when 0...table.min_per_table then 5 * (table.min_per_table - table.size)
when table.min_per_table..table.max_per_table then 0
else 5 * (table.size - table.max_per_table)
end
end
def cohesion_penalty def cohesion_penalty
table.map { |guest| guest.affinity_group_list.first }.tally.to_a.combination(2).sum do |(a, count_a), (b, count_b)| 10 * (cohesion_discomfort * 1.0 / table.size)
distance = AffinityGroupsHierarchy.instance.distance(a, b) end
next count_a * count_b if distance.nil? def invitations_penalty
next 0 if distance.zero? 2 * table.map(&:invitation_id)
.tally
.sum { |invitation_id, guests_in_table| hierarchy.guest_count(invitation_id) - guests_in_table }
end
count_a * count_b * Rational(distance, distance + 1) #
# 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
# guests is a rational number between 1 (unrelated groups) and 0 (same group).
#
# @return [Number] Total discomfort of the table.
#
def cohesion_discomfort
table.map(&:group_id).tally.to_a.combination(2).sum do |(a, count_a), (b, count_b)|
count_a * count_b * hierarchy.discomfort(a, b)
end end
end end
end end

@ -1,19 +1,35 @@
# 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 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
@tables = []
@tables << Table.new(people.slice!(0..rand(@min_per_table..@max_per_table))) while people.any? 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
max_tables = (people.count * 1.0 / @min_per_table).ceil
table_size = random.rand(min_tables..max_tables)
@tables = people.in_groups(table_size, false)
.map { |group| Table.new(group) }
.each { |table| table.min_per_table = @min_per_table }
.each { |table| table.max_per_table = @max_per_table }
end end
def discomfort def discomfort
@ -26,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(&:full_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 = []
@ -52,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

@ -0,0 +1,32 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# frozen_string_literal: true
module Tables
class Shift
private attr_reader :initial_solution
def initialize(initial_solution)
@initial_solution = initial_solution
end
def each
@initial_solution.tables.permutation(2) do |table_a, table_b|
table_a.dup.each do |person|
original_discomfort_a = table_a.reset
original_discomfort_b = table_b.reset
table_a.delete(person)
table_b << person
yield(@initial_solution)
ensure
table_b.delete(person)
table_a << person
table_a.discomfort = original_discomfort_a
table_b.discomfort = original_discomfort_b
end
end
end
end
end

@ -1,3 +1,7 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# frozen_string_literal: true
module Tables module Tables
class Swap class Swap
private attr_reader :initial_solution private attr_reader :initial_solution
@ -7,7 +11,7 @@ module Tables
def each def each
@initial_solution.tables.combination(2) do |table_a, table_b| @initial_solution.tables.combination(2) do |table_a, table_b|
table_a.product(table_b).each do |(person_a, person_b)| table_a.to_a.product(table_b.to_a).each do |(person_a, person_b)|
original_discomfort_a = table_a.reset original_discomfort_a = table_a.reset
original_discomfort_b = table_b.reset original_discomfort_b = table_b.reset

@ -1,6 +1,11 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# frozen_string_literal: true
module Tables module Tables
class Table < Array class Table < Set
attr_accessor :discomfort attr_accessor :discomfort, :min_per_table, :max_per_table
def initialize(*args) def initialize(*args)
super super
reset reset

@ -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,47 +1,112 @@
# 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
def sequence(elements)
elements = elements.to_a
(elements + elements.reverse).chunk(&:itself).map(&:first)
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
@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

@ -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>

@ -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") %>

@ -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>

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

@ -1,17 +0,0 @@
<div id="<%= dom_id expense %>">
<p>
<strong>Name:</strong>
<%= expense.name %>
</p>
<p>
<strong>Amount:</strong>
<%= expense.amount %>
</p>
<p>
<strong>Pricing type:</strong>
<%= expense.pricing_type %>
</p>
</div>

@ -1,32 +0,0 @@
<%= form_with(model: expense) do |form| %>
<% if expense.errors.any? %>
<div style="color: red">
<h2><%= pluralize(expense.errors.count, "error") %> prohibited this expense from being saved:</h2>
<ul>
<% expense.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<div>
<%= form.label :name, style: "display: block" %>
<%= form.text_field :name %>
</div>
<div>
<%= form.label :amount, style: "display: block" %>
<%= form.text_field :amount %>
</div>
<div>
<%= form.label :pricing_type, style: "display: block" %>
<%= form.text_field :pricing_type %>
</div>
<div>
<%= form.submit %>
</div>
<% end %>

@ -1,10 +0,0 @@
<h1>Editing expense</h1>
<%= render "form", expense: @expense %>
<br>
<div>
<%= link_to "Show this expense", @expense %> |
<%= link_to "Back to expenses", expenses_path %>
</div>

@ -1,28 +0,0 @@
<p style="color: green"><%= notice %></p>
<h1>Expenses</h1>
<div id="expenses">
<table>
<tr>
<th>Name</th>
<th>Amount</th>
<th colspan="2"></th>
</tr>
<% @expenses.each do |expense| %>
<tr>
<td><%= expense.name %></td>
<td><%= expense.amount.to_currency %></td>
<td><%= link_to "Show", expense %></td>
<td><%= link_to "Edit", edit_expense_path(expense) %></td>
</tr>
<% end %>
<tr>
<td>Total</td>
<td><%= @expenses.sum(&:amount).to_currency %></td>
<td colspan="2"></td>
</tr>
</table>
</div>
<%= link_to "New expense", new_expense_path %>

@ -1,9 +0,0 @@
<h1>New expense</h1>
<%= render "form", expense: @expense %>
<br>
<div>
<%= link_to "Back to expenses", expenses_path %>
</div>

@ -1,10 +0,0 @@
<p style="color: green"><%= notice %></p>
<%= render @expense %>
<div>
<%= link_to "Edit this expense", edit_expense_path(@expense) %> |
<%= link_to "Back to expenses", expenses_path %>
<%= button_to "Destroy this expense", @expense, method: :delete %>
</div>

@ -1,37 +0,0 @@
<%= form_with(model: guest) do |form| %>
<% if guest.errors.any? %>
<div style="color: red">
<h2><%= pluralize(guest.errors.count, "error") %> prohibited this guest from being saved:</h2>
<ul>
<% guest.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<div>
<%= form.label :first_name, style: "display: block" %>
<%= form.text_field :first_name %>
</div>
<div>
<%= form.label :last_name, style: "display: block" %>
<%= form.text_field :last_name %>
</div>
<div>
<%= form.label :email, style: "display: block" %>
<%= form.text_field :email %>
</div>
<div>
<%= form.label :phone, style: "display: block" %>
<%= form.text_field :phone %>
</div>
<div>
<%= form.submit %>
</div>
<% end %>

@ -1,22 +0,0 @@
<div id="<%= dom_id guest %>">
<p>
<strong>First name:</strong>
<%= guest.first_name %>
</p>
<p>
<strong>Last name:</strong>
<%= guest.last_name %>
</p>
<p>
<strong>Email:</strong>
<%= guest.email %>
</p>
<p>
<strong>Phone:</strong>
<%= guest.phone %>
</p>
</div>

@ -1,10 +0,0 @@
<h1>Editing guest</h1>
<%= render "form", guest: @guest %>
<br>
<div>
<%= link_to "Show this guest", @guest %> |
<%= link_to "Back to guests", guests_path %>
</div>

@ -1,37 +0,0 @@
<p style="color: green"><%= notice %></p>
<h1>Guests</h1>
<div id="guests">
<table>
<tr>
<th>Row #</th>
<th>Name</th>
<th>Email</th>
<th>Phone</th>
<th>Affinity groups</th>
<th>Unbreakable bonds</th>
<th colspan="2"></th>
</tr>
<% @guests.each_with_index do |guest, i| %>
<tr>
<td><%= i + 1 %></td>
<td><%= guest.full_name %></td>
<td><%= guest.email %></td>
<td><%= guest.phone %></td>
<td><%= guest.affinity_groups.pluck(:name).join(", ") %></td>
<td><%= guest.unbreakable_bonds.pluck(:name).join(", ") %></td>
<td><%= link_to "Show", guest %></td>
<td><%= link_to "Edit", edit_guest_path(guest) %></td>
</tr>
<% end %>
</table>
</div>
<%= link_to "New guest", new_guest_path %>
<%= form_with url: import_guests_path, method: :post do |form| %>
<%= form.label :file %>
<%= form.file_field :file %>
<%= form.submit "Import" %>
<% end %>

@ -1,9 +0,0 @@
<h1>New guest</h1>
<%= render "form", guest: @guest %>
<br>
<div>
<%= link_to "Back to guests", guests_path %>
</div>

@ -1,10 +0,0 @@
<p style="color: green"><%= notice %></p>
<%= render @guest %>
<div>
<%= link_to "Edit this guest", edit_guest_path(@guest) %> |
<%= link_to "Back to guests", guests_path %>
<%= button_to "Destroy this guest", @guest, method: :delete %>
</div>

@ -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,16 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>WeddingPlanner</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %>
</head>
<body>
<%= yield %>
</body>
</html>

@ -1,3 +1,5 @@
<%# Copyright (C) 2024-2025 LibreWeddingPlanner contributors %>
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>

@ -1 +1,3 @@
<%# Copyright (C) 2024-2025 LibreWeddingPlanner contributors %>
<%= yield %> <%= yield %>

@ -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>

@ -1,9 +0,0 @@
<h1>Tables arrangements</h1>
<ol>
<% @tables_arrangements.each_with_index do |tables_arrangement, i| %>
<li>
<p><%= link_to "Arrangement ##{i+1}", tables_arrangement_path(tables_arrangement) %> Discomfort: <%= tables_arrangement.discomfort %></p>
</li>
<% end %>
</ol>

@ -1,16 +0,0 @@
<h1>ID: <%= @tables_arrangement.id %></h1>
<p>Discomfort: <%= @tables_arrangement.discomfort %></p>
<h2>Seats</h2>
<% @seats.each do |table_number, seats| %>
<h3>Table <%= table_number %></h3>
<ul>
<% seats.each do |seat| %>
<li><%= seat.guest.full_name %> (<%= seat.guest.affinity_groups.pluck(:name).join(", ") %>)</li>
<% end %>
</ul>
<% end %>

@ -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>

@ -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 %>

@ -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>

@ -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>

@ -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>

7
bin/jobs Executable file

@ -0,0 +1,7 @@
#!/usr/bin/env ruby
require_relative "../config/environment"
require "solid_queue/cli"
SolidQueue.logger = ActiveSupport::Logger.new($stdout)
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,3 +1,5 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
require_relative 'boot' require_relative 'boot'
require 'rails' require 'rails'
@ -28,6 +30,9 @@ module WeddingPlanner
# Common ones are `templates`, `generators`, or `middleware`, for example. # Common ones are `templates`, `generators`, or `middleware`, for example.
config.autoload_lib(ignore: %w[assets tasks]) config.autoload_lib(ignore: %w[assets tasks])
# Use a real queuing backend for Active Job (and separate queues per environment).
config.active_job.queue_adapter = :solid_queue
# Configuration for the application, engines, and railties goes here. # Configuration for the application, engines, and railties goes here.
# #
# These settings can be overridden in specific environments using the files # These settings can be overridden in specific environments using the files

@ -1,3 +1,5 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
require "bundler/setup" # Set up gems listed in the Gemfile. require "bundler/setup" # Set up gems listed in the Gemfile.

@ -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,3 +1,5 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# Load the Rails application. # Load the Rails application.
require_relative "application" require_relative "application"

@ -1,3 +1,5 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
require "active_support/core_ext/integer/time" require "active_support/core_ext/integer/time"
Rails.application.configure do Rails.application.configure do
@ -38,8 +40,10 @@ Rails.application.configure do
# Don't care if the mailer can't send. # Don't care if the mailer can't send.
config.action_mailer.raise_delivery_errors = false config.action_mailer.raise_delivery_errors = false
config.action_mailer.perform_caching = false config.action_mailer.perform_caching = false
config.action_mailer.default_url_options = { host: 'libre-wedding-planner.app.localhost/api' }
config.action_mailer.delivery_method = :letter_opener_web
config.action_mailer.perform_deliveries = true
# Print deprecation notices to the Rails logger. # Print deprecation notices to the Rails logger.
config.active_support.deprecation = :log config.active_support.deprecation = :log
@ -73,4 +77,7 @@ Rails.application.configure do
# Raise error when a before_action's only/except options reference missing actions # Raise error when a before_action's only/except options reference missing actions
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"
Rails.application.routes.default_url_options[:host] = "libre-wedding-planner.app.localhost"
end end

Some files were not shown because too many files have changed in this diff Show More