Compare commits

..

No commits in common. "998706da97f0491b5355fdeed3dbdf62b7bb70a4" and "278faa73194e9f2208b723baaff597bfaabbcc6b" have entirely different histories.

28 changed files with 216 additions and 580 deletions

View File

@ -23,7 +23,6 @@ gem 'rubytree'
gem 'acts_as_tenant'
gem 'httparty'
gem 'rswag'
gem 'pluck_to_hash'
group :development, :test do
gem 'annotaterb'

View File

@ -1,29 +1,29 @@
GEM
remote: https://rubygems.org/
specs:
actioncable (8.0.0.1)
actionpack (= 8.0.0.1)
activesupport (= 8.0.0.1)
actioncable (8.0.0)
actionpack (= 8.0.0)
activesupport (= 8.0.0)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6)
actionmailbox (8.0.0.1)
actionpack (= 8.0.0.1)
activejob (= 8.0.0.1)
activerecord (= 8.0.0.1)
activestorage (= 8.0.0.1)
activesupport (= 8.0.0.1)
actionmailbox (8.0.0)
actionpack (= 8.0.0)
activejob (= 8.0.0)
activerecord (= 8.0.0)
activestorage (= 8.0.0)
activesupport (= 8.0.0)
mail (>= 2.8.0)
actionmailer (8.0.0.1)
actionpack (= 8.0.0.1)
actionview (= 8.0.0.1)
activejob (= 8.0.0.1)
activesupport (= 8.0.0.1)
actionmailer (8.0.0)
actionpack (= 8.0.0)
actionview (= 8.0.0)
activejob (= 8.0.0)
activesupport (= 8.0.0)
mail (>= 2.8.0)
rails-dom-testing (~> 2.2)
actionpack (8.0.0.1)
actionview (= 8.0.0.1)
activesupport (= 8.0.0.1)
actionpack (8.0.0)
actionview (= 8.0.0)
activesupport (= 8.0.0)
nokogiri (>= 1.8.5)
rack (>= 2.2.4)
rack-session (>= 1.0.1)
@ -31,35 +31,35 @@ GEM
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
useragent (~> 0.16)
actiontext (8.0.0.1)
actionpack (= 8.0.0.1)
activerecord (= 8.0.0.1)
activestorage (= 8.0.0.1)
activesupport (= 8.0.0.1)
actiontext (8.0.0)
actionpack (= 8.0.0)
activerecord (= 8.0.0)
activestorage (= 8.0.0)
activesupport (= 8.0.0)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (8.0.0.1)
activesupport (= 8.0.0.1)
actionview (8.0.0)
activesupport (= 8.0.0)
builder (~> 3.1)
erubi (~> 1.11)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
activejob (8.0.0.1)
activesupport (= 8.0.0.1)
activejob (8.0.0)
activesupport (= 8.0.0)
globalid (>= 0.3.6)
activemodel (8.0.0.1)
activesupport (= 8.0.0.1)
activerecord (8.0.0.1)
activemodel (= 8.0.0.1)
activesupport (= 8.0.0.1)
activemodel (8.0.0)
activesupport (= 8.0.0)
activerecord (8.0.0)
activemodel (= 8.0.0)
activesupport (= 8.0.0)
timeout (>= 0.4.0)
activestorage (8.0.0.1)
actionpack (= 8.0.0.1)
activejob (= 8.0.0.1)
activerecord (= 8.0.0.1)
activesupport (= 8.0.0.1)
activestorage (8.0.0)
actionpack (= 8.0.0)
activejob (= 8.0.0)
activerecord (= 8.0.0)
activesupport (= 8.0.0)
marcel (~> 1.0)
activesupport (8.0.0.1)
activesupport (8.0.0)
base64
benchmark (>= 0.3)
bigdecimal
@ -137,7 +137,7 @@ GEM
activesupport (>= 6.0.0)
railties (>= 6.0.0)
io-console (0.8.0)
irb (1.14.2)
irb (1.14.1)
rdoc (>= 4.0.0)
reline (>= 0.4.2)
jbuilder (2.13.0)
@ -176,7 +176,7 @@ GEM
tomlrb (>= 1.3, < 2.1)
with_env (= 1.1.0)
xml-simple (~> 1.1.9)
logger (1.6.3)
logger (1.6.2)
loofah (2.23.1)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
@ -222,9 +222,6 @@ GEM
ast (~> 2.4.1)
racc
pg (1.5.9)
pluck_to_hash (1.0.2)
activerecord (>= 4.0.2)
activesupport (>= 4.0.2)
pry (0.15.0)
coderay (~> 1.1)
method_source (~> 1.0)
@ -245,30 +242,30 @@ GEM
rack (>= 1.3)
rackup (2.2.1)
rack (>= 3)
rails (8.0.0.1)
actioncable (= 8.0.0.1)
actionmailbox (= 8.0.0.1)
actionmailer (= 8.0.0.1)
actionpack (= 8.0.0.1)
actiontext (= 8.0.0.1)
actionview (= 8.0.0.1)
activejob (= 8.0.0.1)
activemodel (= 8.0.0.1)
activerecord (= 8.0.0.1)
activestorage (= 8.0.0.1)
activesupport (= 8.0.0.1)
rails (8.0.0)
actioncable (= 8.0.0)
actionmailbox (= 8.0.0)
actionmailer (= 8.0.0)
actionpack (= 8.0.0)
actiontext (= 8.0.0)
actionview (= 8.0.0)
activejob (= 8.0.0)
activemodel (= 8.0.0)
activerecord (= 8.0.0)
activestorage (= 8.0.0)
activesupport (= 8.0.0)
bundler (>= 1.15.0)
railties (= 8.0.0.1)
railties (= 8.0.0)
rails-dom-testing (2.2.0)
activesupport (>= 5.0.0)
minitest
nokogiri (>= 1.6)
rails-html-sanitizer (1.6.2)
rails-html-sanitizer (1.6.1)
loofah (~> 2.21)
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
railties (8.0.0.1)
actionpack (= 8.0.0.1)
activesupport (= 8.0.0.1)
railties (8.0.0)
actionpack (= 8.0.0)
activesupport (= 8.0.0)
irb (~> 1.13)
rackup (>= 1.0.0)
rake (>= 12.2)
@ -416,7 +413,6 @@ DEPENDENCIES
license_finder
money
pg (~> 1.1)
pluck_to_hash
pry
puma (>= 5.0)
rack-cors

View File

@ -59,7 +59,7 @@ class ApplicationController < ActionController::Base
def set_csrf_cookie
cookies['csrf-token'] = {
value: form_authenticity_token,
secure: false,
secure: Rails.env.production?,
same_site: :strict
}
end

View File

@ -9,21 +9,11 @@ class ExpensesController < ApplicationController
render json: Expense.all.order(pricing_type: :asc, amount: :desc).as_json(only: %i[id name amount pricing_type])
end
def create
Expense.create!(expense_params)
render json: {}, status: :created
end
def update
Expense.find(params[:id]).update!(expense_params)
render json: {}, status: :ok
end
def destroy
Expense.find(params[:id]).destroy!
render json: {}, status: :ok
end
private
def expense_params

View File

@ -2,43 +2,6 @@
class GroupsController < ApplicationController
def index
query_result = Groups::SummaryQuery.new.call.as_json.map(&:deep_symbolize_keys).map do |group|
{
id: group[:id],
name: group[:name],
icon: group[:icon],
color: group[:color],
parent_id: group[:parent_id],
attendance: group.slice(:total, :considered, :invited, :confirmed, :declined, :tentative)
}
end
render json: query_result
end
def create
group = Group.create!(**group_params, parent:)
render json: group.as_json(only: %i[id name icon color parent_id]), status: :created
end
def update
group = Group.find(params[:id])
group.update!(**group_params, parent:)
render json: group.as_json(only: %i[id name icon color parent_id]), status: :ok
end
def destroy
Group.find(params[:id]).destroy!
render json: {}, status: :ok
end
private
def parent
params[:group][:parent_id].present? ? Group.find(params[:group][:parent_id]) : nil
end
def group_params
params.expect(group: [:name, :icon, :color])
render json: Groups::SummaryQuery.new.call.as_json
end
end

View File

@ -5,7 +5,7 @@ require 'csv'
class GuestsController < ApplicationController
def index
render json: Guest.all.includes(:group)
.left_joins(:group)
.joins(:group)
.order('groups.name' => :asc, name: :asc)
.as_json(only: %i[id name status], include: { group: { only: %i[id name] } })
end

View File

@ -1,30 +0,0 @@
# Copyright (C) 2024 Manuel Bustillo
class SummaryController < ApplicationController
def index
expense_summary = Expenses::TotalQuery.new(wedding: ActsAsTenant.current_tenant).call
guest_summary = Guest.group(:status).count
render json: {
expenses: {
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
}
},
guests: {
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
end

View File

@ -1,10 +0,0 @@
# Copyright (C) 2024 Manuel Bustillo
class TokensController < ApplicationController
skip_before_action :authenticate_user!
skip_before_action :set_tenant
def show
head :ok
end
end

View File

@ -38,7 +38,7 @@ class Group < ApplicationRecord
scope :roots, -> { where(parent_id: nil) }
has_many :guests, dependent: :nullify
has_many :guests
def colorize_children(generation = 1)
derived_colors = generation == 1 ? color.paint.palette.analogous(size: children.count) : color.paint.palette.decreasing_saturation

View File

@ -10,7 +10,7 @@
# status :integer default("considered")
# created_at :datetime not null
# updated_at :datetime not null
# group_id :uuid
# group_id :uuid not null
# wedding_id :uuid not null
#
# Indexes
@ -25,7 +25,7 @@
#
class Guest < ApplicationRecord
acts_as_tenant :wedding
belongs_to :group, optional: true
belongs_to :group
enum :status, {
considered: 0,

View File

@ -2,15 +2,8 @@
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
ActiveRecord::Base.connection.execute(query).first
end
private
@ -19,10 +12,16 @@ module Expenses
<<~SQL
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
SELECT expense_summary.fixed,
expense_summary.fixed_count,
expense_summary.variable,
expense_summary.variable_count,
expense_summary.total_count,
guest_count.confirmed as confirmed_guests,
guest_count.projected as projected_guests,
expense_summary.fixed + expense_summary.variable * guest_count.confirmed as total,
expense_summary.fixed + expense_summary.variable * guest_count.projected as max_projected,
(expense_summary.fixed + expense_summary.variable * guest_count.confirmed) / guest_count.confirmed as per_person
FROM guest_count, expense_summary;
SQL
end
@ -30,19 +29,20 @@ module Expenses
def expense_summary
<<~SQL
SELECT coalesce(sum(amount) filter (where pricing_type = 'fixed'), 0) as fixed,
coalesce(sum(amount) filter (where pricing_type = 'per_person'), 0) as variable
coalesce(count(amount) filter (where pricing_type = 'fixed'), 0) as fixed_count,
coalesce(sum(amount) filter (where pricing_type = 'per_person'), 0) as variable,
coalesce(count(amount) filter (where pricing_type = 'per_person'), 0) as variable_count,
count(*) as total_count
FROM expenses
WHERE wedding_id = :wedding_id
SQL
end
def guest_count_per_status
<<~SQL
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
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
end

View File

@ -3,19 +3,29 @@
module Groups
class SummaryQuery
def call
Group.left_joins(:guests).group(:id).pluck_to_hash(
:id,
:name,
:icon,
:parent_id,
:color,
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'),
)
ActiveRecord::Base.connection.execute(query).to_a
end
private
def query
<<~SQL.squish
SELECT
groups.id,
groups.name,
groups.icon,
groups.parent_id,
groups.color,
count(*) filter (where status IS NOT NULL) as total,
count(*) filter (where status = 0) as considered,
count(*) filter (where status = 10) as invited,
count(*) filter (where status = 20) as confirmed,
count(*) filter (where status = 30) as declined,
count(*) filter (where status = 40) as tentative
FROM groups
LEFT JOIN guests on groups.id = guests.group_id
GROUP BY groups.id
SQL
end
end
end

View File

@ -29,7 +29,7 @@ module VNS
@best_score = @target_function.call(@best_solution)
self.class.sequence(@perturbations).each do |perturbation|
optimize(perturbation)
optimize(perturbation.new(@best_solution))
end
@best_solution
@ -37,22 +37,15 @@ module VNS
private
def optimize(perturbation_klass)
loop do
optimized = false
def optimize(perturbation)
perturbation.each do |alternative_solution|
score = @target_function.call(alternative_solution)
next if score >= @best_score
perturbation_klass.new(@best_solution).each do |alternative_solution|
score = @target_function.call(alternative_solution)
next if score >= @best_score
@best_solution = alternative_solution.deep_dup
@best_score = score
@best_solution = alternative_solution.deep_dup
@best_score = score
optimized = true
break
end
return unless optimized
return optimize(perturbation.class.new(@best_solution))
end
end
end

View File

@ -92,9 +92,6 @@ Rails.application.configure do
# "example.com", # Allow requests from example.com
# /.*\.example\.com/ # Allow requests from subdomains like `www.example.com`
# ]
config.hosts << "app.libreweddingplanner.org"
# Skip DNS rebinding protection for the default health check endpoint.
config.host_authorization = { exclude: ->(request) { request.path == "/up" } }
# config.host_authorization = { exclude: ->(request) { request.path == "/up" } }
end

View File

@ -2,16 +2,6 @@
Rails.application.routes.draw do
mount LetterOpenerWeb::Engine, at: "/letter_opener" if Rails.env.development?
get 'token' => 'tokens#show', as: :token
get 'up' => 'rails/health#show', as: :rails_health_check
resources :captcha, only: :create do
get 'v2/media', to: 'captcha#media', on: :collection, as: :media
end
mount Rswag::Ui::Engine => '/api-docs'
mount Rswag::Api::Engine => '/api-docs'
scope ":slug", constraints: { slug: Wedding::SLUG_REGEX } do
devise_for :users, skip: [:registration, :session, :confirmation]
devise_scope :user do
@ -23,16 +13,24 @@ Rails.application.routes.draw do
get '/users/confirmation', to: 'users/confirmations#show', as: :confirmation
end
resources :groups, only: %i[index create update destroy]
resources :groups, only: :index
resources :guests, only: %i[index create update destroy] do
post :bulk_update, on: :collection
end
resources :expenses, only: %i[index create update destroy] do
resources :expenses, only: %i[index update] do
get :summary, on: :collection
end
resources :tables_arrangements, only: %i[index show]
resources :summary, only: :index
root to: redirect("/%{slug}")
end
resources :captcha, only: :create do
get 'v2/media', to: 'captcha#media', on: :collection, as: :media
end
mount Rswag::Ui::Engine => '/api-docs'
mount Rswag::Api::Engine => '/api-docs'
get 'up' => 'rails/health#show', as: :rails_health_check
end

View File

@ -1,7 +0,0 @@
# Copyright (C) 2024 Manuel Bustillo
class AllowUngroupedGuests < ActiveRecord::Migration[8.0]
def change
change_column_null :guests, :group_id, true
end
end

4
db/schema.rb generated
View File

@ -12,7 +12,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.0].define(version: 2024_12_08_102932) do
ActiveRecord::Schema[8.0].define(version: 2024_12_07_112305) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_catalog.plpgsql"
@ -48,7 +48,7 @@ ActiveRecord::Schema[8.0].define(version: 2024_12_08_102932) do
t.string "phone"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.uuid "group_id"
t.uuid "group_id", null: false
t.integer "status", default: 0
t.string "name"
t.uuid "wedding_id", null: false

View File

@ -40,7 +40,6 @@ services:
image: librecaptcha/lc-core:latest
volumes:
- "./tmp/libre-captcha-data:/lc-core/data"
- "./libre-captcha-config.json:/lc-core/data/config.json"
ports:
- 8888
nginx:

View File

@ -1,29 +0,0 @@
{
"randomSeed": -1534087241,
"port": 8888,
"address": "0.0.0.0",
"captchaExpiryTimeLimit": 5,
"bufferCount": 1000,
"threadDelay": 2,
"playgroundEnabled": false,
"corsHeader": "",
"maxAttemptsRatio": 0.009999999776482582,
"captchas": [
{
"name": "FilterChallenge",
"allowedLevels": [
"hard"
],
"allowedMedia": [
"image/png"
],
"allowedInputType": [
"text"
],
"allowedSizes": [
"350x100"
],
"config": {}
}
]
}

View File

@ -16,7 +16,7 @@ RSpec.describe Guest, type: :model do
end
end
it { should belong_to(:group).optional }
it { should belong_to(:group) }
describe 'scopes' do
describe '.potential' do

View File

@ -5,66 +5,88 @@ require 'rails_helper'
module Expenses
RSpec.describe TotalQuery do
describe '#call' do
let(:wedding) { create(:wedding) }
let(:response) { described_class.new(wedding:).call }
let(:response) { described_class.new.call }
before do
create_list(:guest, 2, wedding:, status: :confirmed)
create_list(:guest, 3, wedding:, status: :considered)
create_list(:guest, 4, wedding:, status: :invited)
create_list(:guest, 5, wedding:, status: :tentative)
create_list(:guest, 6, wedding:, status: :declined)
create_list(:guest, 2, status: :confirmed)
create_list(:guest, 3, status: :considered)
create_list(:guest, 4, status: :invited)
create_list(:guest, 5, status: :tentative)
create_list(:guest, 6, status: :declined)
end
context 'when there is no expense' do
it 'returns zero in all values', :aggregate_failures do
expect(response['total_confirmed']).to be_zero
expect(response['total_projected']).to be_zero
expect(response['confirmed_guests']).to eq(2)
expect(response['projected_guests']).to eq(2 + 4 + 5)
context "when there is no expense" do
it "returns zero in all values", :aggregate_failures do
expect(response["fixed"]).to be_zero
expect(response["fixed_count"]).to be_zero
expect(response["variable"]).to be_zero
expect(response["variable_count"]).to be_zero
expect(response["total"]).to be_zero
expect(response["total_count"]).to be_zero
expect(response["max_projected"]).to be_zero
expect(response["per_person"]).to be_zero
expect(response["confirmed_guests"]).to eq(2)
expect(response["projected_guests"]).to eq(2 + 4 + 5)
end
end
context 'when there are only fixed expenses' do
context "when there are only fixed expenses" do
before do
create(:expense, :fixed, wedding:, amount: 100)
create(:expense, :fixed, wedding:, amount: 200)
create(:expense, :fixed, amount: 100)
create(:expense, :fixed, amount: 200)
end
it 'returns the sum of fixed expenses', :aggregate_failures do
expect(response['total_confirmed']).to eq(300)
expect(response['total_projected']).to eq(300)
expect(response['confirmed_guests']).to eq(2)
expect(response['projected_guests']).to eq(2 + 4 + 5)
it "returns the sum of fixed expenses", :aggregate_failures do
expect(response["fixed"]).to eq(300)
expect(response["fixed_count"]).to eq(2)
expect(response["variable"]).to be_zero
expect(response["variable_count"]).to be_zero
expect(response["total"]).to eq(300)
expect(response["total_count"]).to eq(2)
expect(response["max_projected"]).to eq(300)
expect(response["per_person"]).to eq(150)
expect(response["confirmed_guests"]).to eq(2)
expect(response["projected_guests"]).to eq(2 + 4 + 5)
end
end
context 'when there are only variable expenses' do
context "when there are only variable expenses" do
before do
create(:expense, :per_person, wedding:, amount: 100)
create(:expense, :per_person, wedding:, amount: 200)
create(:expense, :per_person, amount: 100)
create(:expense, :per_person, amount: 200)
end
it 'returns zero in the values and nonzero in the count', :aggregate_failures do
expect(response['total_confirmed']).to eq(2 * 300)
expect(response['total_projected']).to eq(11 * 300)
expect(response['confirmed_guests']).to eq(2)
expect(response['projected_guests']).to eq(2 + 4 + 5)
it "returns zero in the values and nonzero in the count", :aggregate_failures do
expect(response["fixed"]).to be_zero
expect(response["fixed_count"]).to be_zero
expect(response["variable"]).to eq(300)
expect(response["variable_count"]).to eq(2)
expect(response["total"]).to eq(2*300)
expect(response["total_count"]).to eq(2)
expect(response["max_projected"]).to eq(11*300)
expect(response["confirmed_guests"]).to eq(2)
expect(response["projected_guests"]).to eq(2 + 4 + 5)
end
end
context 'when there are both fixed and variable expenses' do
context "when there are both fixed and variable expenses" do
before do
create(:expense, :fixed, wedding:, amount: 100)
create(:expense, :fixed, wedding:, amount: 200)
create(:expense, :per_person, wedding:, amount: 50)
create(:expense, :fixed, amount: 100)
create(:expense, :fixed, amount: 200)
create(:expense, :per_person, amount: 50)
end
it 'returns the sum of fixed and variable expenses', :aggregate_failures do
expect(response['total_confirmed']).to eq(100 + 200 + 50 * 2)
expect(response['total_projected']).to eq(100 + 200 + 11 * 50)
expect(response['confirmed_guests']).to eq(2)
expect(response['projected_guests']).to eq(2 + 4 + 5)
it "returns the sum of fixed and variable expenses", :aggregate_failures do
expect(response["fixed"]).to eq(300)
expect(response["fixed_count"]).to eq(2)
expect(response["variable"]).to eq(50)
expect(response["variable_count"]).to eq(1)
expect(response["total"]).to eq(100 + 200 + 50 * 2)
expect(response["total_count"]).to eq(3)
expect(response["max_projected"]).to eq(100 + 200 + 11*50)
expect(response["per_person"]).to eq(200)
expect(response["confirmed_guests"]).to eq(2)
expect(response["projected_guests"]).to eq(2 + 4 + 5)
end
end
end

View File

@ -16,7 +16,9 @@ RSpec.describe 'expenses', type: :request do
required: %i[id name amount pricing_type],
properties: {
id: { type: :string, format: :uuid },
**Swagger::Schema::EXPENSE
name: { type: :string },
amount: { type: :number },
pricing_type: { type: :string, enum: Expense.pricing_types.keys }
}
}
@ -24,31 +26,10 @@ RSpec.describe 'expenses', type: :request do
end
regular_api_responses
end
post 'create expense' do
tags 'Expenses'
consumes 'application/json'
produces 'application/json'
parameter Swagger::Schema::SLUG
parameter name: :body, in: :body, schema: {
type: :object,
required: %i[expense],
properties: {
expense: {
type: :object,
required: %i[name amount pricing_type],
properties: Swagger::Schema::EXPENSE
}
}
}
response_empty_201
response_422
regular_api_responses
end
end
path '/{slug}/expenses/{id}' do
patch('update expense') do
tags 'Expenses'
consumes 'application/json'
@ -57,7 +38,11 @@ RSpec.describe 'expenses', type: :request do
parameter name: 'id', in: :path, type: :string, format: :uuid, description: 'id'
parameter name: :body, in: :body, schema: {
type: :object,
properties: Swagger::Schema::EXPENSE
properties: {
name: { type: :string },
amount: { type: :number, minimum: 0 },
pricing_type: { type: :string, enum: Expense.pricing_types.keys }
}
}
response_empty_200
@ -65,15 +50,5 @@ RSpec.describe 'expenses', type: :request do
response_404
regular_api_responses
end
delete('delete expense') do
tags 'Expenses'
produces 'application/json'
parameter Swagger::Schema::SLUG
parameter Swagger::Schema::ID
response_empty_200
response_404
regular_api_responses
end
end
end

View File

@ -10,99 +10,26 @@ RSpec.describe 'groups', type: :request do
parameter Swagger::Schema::SLUG
response(200, 'successful') do
schema type: :array,
items: {
type: :object,
required: %i[id name icon parent_id color attendance],
properties: {
id: { type: :string, format: :uuid, required: true },
name: { type: :string },
icon: { type: :string, example: 'pi pi-crown', description: 'The CSS classes used by the icon' },
parent_id: { type: :string, format: :uuid },
color: { type: :string, pattern: '^#(?:[0-9a-fA-F]{3}){1,2}$' },
attendance: {
type: :object,
required: %i[total considered invited confirmed declined tentative],
properties: {
total: { type: :integer, minimum: 0, description: 'Total number of guests in any status' },
considered: { type: :integer, minimum: 0 },
invited: { type: :integer, minimum: 0 },
confirmed: { type: :integer, minimum: 0 },
declined: { type: :integer, minimum: 0 },
tentative: { type: :integer, minimum: 0 }
}
}
}
items: {
type: :object,
required: %i[id name icon parent_id color total considered invited confirmed declined tentative],
properties: {
id: { type: :string, format: :uuid, required: true },
name: { type: :string },
icon: { type: :string, example: 'pi pi-crown', description: 'The CSS classes used by the icon' },
parent_id: { type: :string, format: :uuid },
color: { type: :string, pattern: '^#(?:[0-9a-fA-F]{3}){1,2}$' },
total: { type: :integer, minimum: 0, description: 'Total number of guests in any status' },
considered: { type: :integer, minimum: 0 },
invited: { type: :integer, minimum: 0 },
confirmed: { type: :integer, minimum: 0 },
declined: { type: :integer, minimum: 0 },
tentative: { type: :integer, minimum: 0 }
}
}
xit
end
regular_api_responses
end
post('create group') do
tags 'Groups'
consumes 'application/json'
produces 'application/json'
parameter Swagger::Schema::SLUG
parameter name: :body, in: :body, schema: {
type: :object,
required: %i[group],
properties: {
group: {
type: :object,
required: %i[name],
properties: Swagger::Schema::GROUP
}
}
}
response(201, 'created') do
schema type: :object, properties: {
id: { type: :string, format: :uuid, required: true },
**Swagger::Schema::GROUP
}
xit
end
regular_api_responses
end
path '/{slug}/groups/{id}' do
put('update group') do
tags 'Groups'
consumes 'application/json'
produces 'application/json'
parameter Swagger::Schema::SLUG
parameter name: :id, in: :path, type: :string, format: :uuid
parameter name: :body, in: :body, schema: {
type: :object,
required: %i[group],
properties: {
group: {
type: :object,
required: %i[name],
properties: Swagger::Schema::GROUP
}
}
}
response(200, 'updated') do
schema type: :object, properties: {
id: { type: :string, format: :uuid, required: true },
**Swagger::Schema::GROUP
}
xit
end
regular_api_responses
end
delete('delete group') do
tags 'Groups'
produces 'application/json'
parameter Swagger::Schema::SLUG
parameter name: :id, in: :path, type: :string, format: :uuid
response_empty_200
regular_api_responses
end
end
end
end

View File

@ -41,7 +41,7 @@ RSpec.describe 'guests', type: :request do
properties: {
guest: {
type: :object,
required: %i[name status],
required: %i[name group_id status],
properties: {
name: { type: :string },
group_id: { type: :string, format: :uuid },
@ -70,7 +70,6 @@ RSpec.describe 'guests', type: :request do
properties: {
guest: {
type: :object,
required: %i[name status],
properties: {
name: { type: :string },
group_id: { type: :string, format: :uuid },

View File

@ -4,29 +4,10 @@ module Swagger
module Schema
USER = {
id: { type: :string, format: :uuid },
email: { type: :string, format: :email },
created_at: SwaggerResponseHelper::TIMESTAMP,
updated_at: SwaggerResponseHelper::TIMESTAMP
}
ID = {
name: 'id',
in: :path,
type: :string,
format: :uuid,
}
GROUP = {
name: { type: :string },
icon: { type: :string, example: 'pi pi-crown', description: 'The CSS classes used by the icon' },
parent_id: { type: :string, format: :uuid },
color: { type: :string, pattern: '^#(?:[0-9a-fA-F]{3}){1,2}$' }
}
EXPENSE = {
name: { type: :string },
amount: { type: :number, minimum: 0 },
pricing_type: { type: :string, enum: Expense.pricing_types.keys }
email: { type: :string, format: :email },
created_at: SwaggerResponseHelper::TIMESTAMP,
updated_at: SwaggerResponseHelper::TIMESTAMP
}
SLUG = {

View File

@ -1,61 +0,0 @@
# Copyright (C) 2024 Manuel Bustillo
require 'swagger_helper'
RSpec.describe 'summary', type: :request do
path '/{slug}/summary' do
get('list summaries') do
tags 'Summary'
produces 'application/json'
consumes 'application/json'
parameter Swagger::Schema::SLUG
response(200, 'successful') do
schema type: :object,
required: %i[expenses guests],
properties: {
expenses: {
type: :object,
required: %i[projected confirmed status],
properties: {
projected: {
type: :object,
required: %i[total guests],
properties: {
total: { type: :number },
guests: { type: :number }
}
},
confirmed: {
type: :object,
required: %i[total guests],
properties: {
total: { type: :number },
guests: { type: :number }
}
},
status: {
type: :object,
required: [:paid],
properties: {
paid: { type: :number }
}
}
}
},
guests: {
type: :object,
required: %i[total confirmed declined tentative invited],
properties: {
total: { type: :number },
confirmed: { type: :number },
declined: { type: :number },
tentative: { type: :number },
invited: { type: :number }
}
}
}
xit
end
end
end
end

View File

@ -1,61 +0,0 @@
# Copyright (C) 2024 Manuel Bustillo
require 'swagger_helper'
RSpec.describe 'tables_arrangements', type: :request do
path '/{slug}/tables_arrangements' do
get('list tables arrangements') do
tags 'Tables Arrangements'
produces 'application/json'
parameter Swagger::Schema::SLUG
response(200, 'successful') do
schema type: :array,
items: {
type: :object,
required: %i[id name discomfort],
properties: {
id: { type: :string, format: :uuid },
name: { type: :string },
discomfort: { type: :integer }
}
}
xit
end
regular_api_responses
end
end
path '/{slug}/tables_arrangements/{id}' do
get('show tables arrangement') do
tags 'Tables Arrangements'
produces 'application/json'
parameter Swagger::Schema::SLUG
parameter Swagger::Schema::ID
response(200, 'successful') do
schema type: :array,
items: {
type: :object,
required: %i[number guests],
properties: {
number: { type: :integer },
guests: {
type: :array,
items: {
type: :object,
required: %i[id name color],
properties: {
id: { type: :string, format: :uuid },
name: { type: :string },
color: { type: :string }
}
}
}
}
}
xit
end
regular_api_responses
end
end
end

View File

@ -1,15 +0,0 @@
# Copyright (C) 2024 Manuel Bustillo
require 'swagger_helper'
RSpec.describe 'tokens', type: :request do
path '/token' do
get('get a cookie with CSRF token') do
tags 'CSRF token'
consumes 'application/json'
produces 'application/json'
response_empty_200
end
end
end