Compare commits

..

No commits in common. "f957d0acb31893af0ae6979133e3eacbbed01310" and "c5e1c2aef10c4301b269637424b769d57b708755" have entirely different histories.

18 changed files with 51 additions and 199 deletions

1
.gitignore vendored
View File

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

View File

@ -1,48 +0,0 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# frozen_string_literal: true
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
class InvitationsController < ApplicationController
def index
render json: Invitation.includes(:guests).as_json(
only: :id,
include: {
guests: {
only: %i[id name]
}
}
)
end
def create
invitation = Invitation.create
if invitation.persisted?
render json: invitation, only: :id, status: :created
else
render json: { errors: invitation.errors.full_messages }, status: :unprocessable_entity
end
end
def update
invitation = Invitation.find(params[:id])
if invitation.update(guest_ids: params[:invitation][:guest_ids])
render json: invitation, only: :id, include: { guests: { only: %i[id name] } }, status: :ok
else
render json: { errors: invitation.errors.full_messages }, status: :unprocessable_entity
end
end
def destroy
invitation = Invitation.find(params[:id])
if invitation.destroy
head :no_content
else
render json: { errors: invitation.errors.full_messages }, status: :unprocessable_entity
end
end
end

View File

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

View File

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

View File

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

View File

@ -1,25 +0,0 @@
# Copyright (C) 2024-2025 LibreWeddingPlanner contributors
# frozen_string_literal: true
# == Schema Information
#
# Table name: invitations
#
# id :uuid not null, primary key
# created_at :datetime not null
# updated_at :datetime not null
# wedding_id :uuid not null
#
# Indexes
#
# index_invitations_on_wedding_id (wedding_id)
#
# Foreign Keys
#
# fk_rails_... (wedding_id => weddings.id) ON DELETE => cascade
#
class Invitation < ApplicationRecord
acts_as_tenant :wedding
has_many :guests, dependent: :nullify
end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -39,7 +39,6 @@ Rails.application.routes.draw do
end end
resources :tables_arrangements, only: %i[index show create] resources :tables_arrangements, only: %i[index show create]
resources :summary, only: :index resources :summary, only: :index
resources :invitations, only: %i[index create update destroy]
root to: redirect("/%{slug}") root to: redirect("/%{slug}")
end end

View File

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

View File

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

25
db/schema.rb generated
View File

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.0].define(version: 2025_01_27_190131) do ActiveRecord::Schema[8.0].define(version: 2025_01_26_091823) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "pg_catalog.plpgsql" enable_extension "pg_catalog.plpgsql"
@ -63,19 +63,10 @@ ActiveRecord::Schema[8.0].define(version: 2025_01_27_190131) do
t.integer "status", default: 0 t.integer "status", default: 0
t.string "name" t.string "name"
t.uuid "wedding_id", null: false t.uuid "wedding_id", null: false
t.uuid "invitation_id"
t.index ["group_id"], name: "index_guests_on_group_id" t.index ["group_id"], name: "index_guests_on_group_id"
t.index ["invitation_id"], name: "index_guests_on_invitation_id"
t.index ["wedding_id"], name: "index_guests_on_wedding_id" t.index ["wedding_id"], name: "index_guests_on_wedding_id"
end end
create_table "invitations", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.uuid "wedding_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["wedding_id"], name: "index_invitations_on_wedding_id"
end
create_table "seats", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| create_table "seats", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.uuid "guest_id", null: false t.uuid "guest_id", null: false
t.uuid "tables_arrangement_id", null: false t.uuid "tables_arrangement_id", null: false
@ -248,24 +239,22 @@ ActiveRecord::Schema[8.0].define(version: 2025_01_27_190131) do
t.index ["slug"], name: "index_weddings_on_slug", unique: true t.index ["slug"], name: "index_weddings_on_slug", unique: true
end end
add_foreign_key "expenses", "weddings", on_delete: :cascade add_foreign_key "expenses", "weddings"
add_foreign_key "group_affinities", "groups", column: "group_a_id" add_foreign_key "group_affinities", "groups", column: "group_a_id"
add_foreign_key "group_affinities", "groups", column: "group_b_id" add_foreign_key "group_affinities", "groups", column: "group_b_id"
add_foreign_key "groups", "groups", column: "parent_id" add_foreign_key "groups", "groups", column: "parent_id"
add_foreign_key "groups", "weddings", on_delete: :cascade add_foreign_key "groups", "weddings"
add_foreign_key "guests", "groups" add_foreign_key "guests", "groups"
add_foreign_key "guests", "invitations" add_foreign_key "guests", "weddings"
add_foreign_key "guests", "weddings", on_delete: :cascade
add_foreign_key "invitations", "weddings", on_delete: :cascade
add_foreign_key "seats", "guests" add_foreign_key "seats", "guests"
add_foreign_key "seats", "tables_arrangements", on_delete: :cascade add_foreign_key "seats", "tables_arrangements", on_delete: :cascade
add_foreign_key "seats", "weddings", on_delete: :cascade add_foreign_key "seats", "weddings"
add_foreign_key "solid_queue_blocked_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade add_foreign_key "solid_queue_blocked_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade
add_foreign_key "solid_queue_claimed_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade add_foreign_key "solid_queue_claimed_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade
add_foreign_key "solid_queue_failed_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade add_foreign_key "solid_queue_failed_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade
add_foreign_key "solid_queue_ready_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade add_foreign_key "solid_queue_ready_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade
add_foreign_key "solid_queue_recurring_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade add_foreign_key "solid_queue_recurring_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade
add_foreign_key "solid_queue_scheduled_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade add_foreign_key "solid_queue_scheduled_executions", "solid_queue_jobs", column: "job_id", on_delete: :cascade
add_foreign_key "tables_arrangements", "weddings", on_delete: :cascade add_foreign_key "tables_arrangements", "weddings"
add_foreign_key "users", "weddings", on_delete: :cascade add_foreign_key "users", "weddings"
end end

View File

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

View File

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

View File

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