From c0207bf33f9fab4639392e2bb4bd8295660cbbb3 Mon Sep 17 00:00:00 2001 From: Konstantin Gredeskoul Date: Thu, 16 May 2024 17:26:27 -0700 Subject: [PATCH] Ticket Request logical delete + CSV generation * With RSpecs --- Gemfile | 2 + Gemfile.lock | 3 + app/controllers/ticket_requests_controller.rb | 25 +-- app/models/eald_payment.rb | 12 +- app/models/event.rb | 10 +- app/models/event_admin.rb | 10 +- app/models/job.rb | 12 +- app/models/payment.rb | 8 +- app/models/price_rule.rb | 10 +- app/models/price_rule/kids_equal_to.rb | 10 +- app/models/shift.rb | 15 +- app/models/site_admin.rb | 6 +- app/models/ticket_request.rb | 114 +++++++++-- app/models/time_slot.rb | 12 +- app/models/user.rb | 22 +-- ...25937_add_deleted_at_to_ticket_requests.rb | 8 + db/structure.sql | 177 +++++++++++------- .../ticket_requests_controller_spec.rb | 74 +++++++- spec/factories/ticket_request.rb | 7 +- spec/factories/user.rb | 1 + spec/models/ticket_request_spec.rb | 76 +++++++- 21 files changed, 460 insertions(+), 154 deletions(-) create mode 100644 db/migrate/20240516225937_add_deleted_at_to_ticket_requests.rb diff --git a/Gemfile b/Gemfile index 709f8720..6b960ab2 100644 --- a/Gemfile +++ b/Gemfile @@ -126,3 +126,5 @@ group :test do gem 'timecop' gem 'timeout' end + +gem 'paranoia', '~> 2.6' diff --git a/Gemfile.lock b/Gemfile.lock index 7dc64c54..14df7054 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -261,6 +261,8 @@ GEM racc (~> 1.4) orm_adapter (0.5.0) parallel (1.24.0) + paranoia (2.6.3) + activerecord (>= 5.1, < 7.2) parser (3.3.1.0) ast (~> 2.4.1) racc @@ -504,6 +506,7 @@ DEPENDENCIES mini_magick mini_racer newrelic_rpm + paranoia (~> 2.6) pg propshaft protected_attributes_continued diff --git a/app/controllers/ticket_requests_controller.rb b/app/controllers/ticket_requests_controller.rb index 5c925e41..1cbdbcbd 100644 --- a/app/controllers/ticket_requests_controller.rb +++ b/app/controllers/ticket_requests_controller.rb @@ -40,22 +40,22 @@ def index end def download - temp_csv = Tempfile.new('csv') + csv_file = Tempfile.new('csv') - raise ArgumentError('Tempfile is nil') if temp_csv.nil? || temp_csv.path.nil? - - CSV.open(temp_csv.path, 'wb') do |csv| - csv << (%w[name email] + TicketRequest.columns.map(&:name)) - TicketRequest.where(event_id: @event).find_each do |ticket_request| - csv << ([ticket_request.user.name, ticket_request.user.email] + - ticket_request.attributes.values) + CSV.open(csv_file.path, 'w', + write_headers: true, + headers: TicketRequest.csv_header) do |csv| + TicketRequest.for_csv(@event).each do |row| + csv << row end end - temp_csv.close - send_file(temp_csv.path, - filename: "#{@event.name} Ticket Requests.csv", + send_file(csv_file.path, + filename: "#{@event.to_param}-ticket-requests.csv", type: 'text/csv') + rescue StandardError => e + flash.now[:error] = "Error creating CSV file: #{e.message}" + render_flash(flash) end def show @@ -189,7 +189,10 @@ def update end def destroy + Rails.logger.error("destroy# params: #{permitted_params}".colorize(:yellow)) + unless @event.admin?(current_user) || current_user == @ticket_request.user + Rails.logger.error("ATTEMPT TO DELETE TICKET REQUEST #{@ticket_request} by #{current_user}".colorize(:red)) flash.now[:error] = 'You do not have sufficient privileges to delete this request.' return render_flash(flash) end diff --git a/app/models/eald_payment.rb b/app/models/eald_payment.rb index be8510ab..b13cd5a5 100644 --- a/app/models/eald_payment.rb +++ b/app/models/eald_payment.rb @@ -4,17 +4,21 @@ # # Table name: eald_payments # -# id :integer not null, primary key +# id :bigint not null, primary key # amount_charged_cents :integer not null # early_arrival_passes :integer default(0), not null # email :string(255) not null # late_departure_passes :integer default(0), not null # name :string(255) not null -# created_at :datetime -# updated_at :datetime -# event_id :integer +# created_at :datetime not null +# updated_at :datetime not null +# event_id :bigint # stripe_charge_id :string not null # +# Indexes +# +# index_eald_payments_on_event_id (event_id) +# class EaldPayment < ApplicationRecord include PaymentsHelper diff --git a/app/models/event.rb b/app/models/event.rb index dda1e6c6..133aedf3 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -4,7 +4,7 @@ # # Table name: events # -# id :integer not null, primary key +# id :bigint not null, primary key # adult_ticket_price :decimal(8, 2) # allow_donations :boolean default(FALSE), not null # allow_financial_assistance :boolean default(FALSE), not null @@ -17,8 +17,8 @@ # max_cabin_requests :integer # max_cabins_per_request :integer # max_kid_tickets_per_request :integer -# name :string(255) -# photo :string(255) +# name :string +# photo :string # require_mailing_address :boolean default(FALSE), not null # slug :text # start_time :datetime @@ -26,8 +26,8 @@ # ticket_sales_end_time :datetime # ticket_sales_start_time :datetime # tickets_require_approval :boolean default(TRUE), not null -# created_at :datetime -# updated_at :datetime +# created_at :datetime not null +# updated_at :datetime not null # class Event < ApplicationRecord has_many :event_admins diff --git a/app/models/event_admin.rb b/app/models/event_admin.rb index 75ba8ab1..4cef97f4 100644 --- a/app/models/event_admin.rb +++ b/app/models/event_admin.rb @@ -4,11 +4,11 @@ # # Table name: event_admins # -# id :integer not null, primary key -# created_at :datetime -# updated_at :datetime -# event_id :integer -# user_id :integer +# id :bigint not null, primary key +# created_at :datetime not null +# updated_at :datetime not null +# event_id :bigint +# user_id :bigint # # Indexes # diff --git a/app/models/job.rb b/app/models/job.rb index 5386e08a..26f3557f 100644 --- a/app/models/job.rb +++ b/app/models/job.rb @@ -4,12 +4,16 @@ # # Table name: jobs # -# id :integer not null, primary key +# id :bigint not null, primary key # description :string(512) not null # name :string(100) not null -# created_at :datetime -# updated_at :datetime -# event_id :integer not null +# created_at :datetime not null +# updated_at :datetime not null +# event_id :bigint not null +# +# Indexes +# +# index_jobs_on_event_id (event_id) # class Job < ApplicationRecord belongs_to :event diff --git a/app/models/payment.rb b/app/models/payment.rb index b7a0b148..d3fa7cbe 100644 --- a/app/models/payment.rb +++ b/app/models/payment.rb @@ -4,11 +4,11 @@ # # Table name: payments # -# id :integer not null, primary key -# explanation :string(255) +# id :bigint not null, primary key +# explanation :string # status :string(1) default("N"), not null -# created_at :datetime -# updated_at :datetime +# created_at :datetime not null +# updated_at :datetime not null # stripe_charge_id :string(255) # stripe_payment_id :string # ticket_request_id :integer not null diff --git a/app/models/price_rule.rb b/app/models/price_rule.rb index 8dbf7229..f5ba5445 100644 --- a/app/models/price_rule.rb +++ b/app/models/price_rule.rb @@ -4,13 +4,13 @@ # # Table name: price_rules # -# id :integer not null, primary key +# id :bigint not null, primary key # price :decimal(8, 2) # trigger_value :integer -# type :string(255) -# created_at :datetime -# updated_at :datetime -# event_id :integer +# type :string +# created_at :datetime not null +# updated_at :datetime not null +# event_id :bigint # # Indexes # diff --git a/app/models/price_rule/kids_equal_to.rb b/app/models/price_rule/kids_equal_to.rb index d24df0f1..ba9306b4 100644 --- a/app/models/price_rule/kids_equal_to.rb +++ b/app/models/price_rule/kids_equal_to.rb @@ -4,13 +4,13 @@ # # Table name: price_rules # -# id :integer not null, primary key +# id :bigint not null, primary key # price :decimal(8, 2) # trigger_value :integer -# type :string(255) -# created_at :datetime -# updated_at :datetime -# event_id :integer +# type :string +# created_at :datetime not null +# updated_at :datetime not null +# event_id :bigint # # Indexes # diff --git a/app/models/shift.rb b/app/models/shift.rb index 0d6677b4..867e45e4 100644 --- a/app/models/shift.rb +++ b/app/models/shift.rb @@ -4,12 +4,17 @@ # # Table name: shifts # -# id :integer not null, primary key +# id :bigint not null, primary key # name :string(70) -# created_at :datetime -# updated_at :datetime -# time_slot_id :integer not null -# user_id :integer not null +# created_at :datetime not null +# updated_at :datetime not null +# time_slot_id :bigint not null +# user_id :bigint not null +# +# Indexes +# +# index_shifts_on_time_slot_id (time_slot_id) +# index_shifts_on_user_id (user_id) # class Shift < ApplicationRecord belongs_to :time_slot diff --git a/app/models/site_admin.rb b/app/models/site_admin.rb index a083383d..186995d2 100644 --- a/app/models/site_admin.rb +++ b/app/models/site_admin.rb @@ -4,9 +4,9 @@ # # Table name: site_admins # -# id :integer not null, primary key -# created_at :datetime -# updated_at :datetime +# id :bigint not null, primary key +# created_at :datetime not null +# updated_at :datetime not null # user_id :integer not null # class SiteAdmin < ApplicationRecord diff --git a/app/models/ticket_request.rb b/app/models/ticket_request.rb index 4662cd20..73d7a870 100644 --- a/app/models/ticket_request.rb +++ b/app/models/ticket_request.rb @@ -4,7 +4,7 @@ # # Table name: ticket_requests # -# id :integer not null, primary key +# id :bigint not null, primary key # address_line1 :string(200) # address_line2 :string(200) # admin_notes :string(512) @@ -15,6 +15,7 @@ # car_camping_explanation :string(200) # city :string(50) # country_code :string(4) +# deleted_at :datetime # donation :decimal(8, 2) default(0.0) # early_arrival_passes :integer default(0), not null # guests :text @@ -23,27 +24,99 @@ # needs_assistance :boolean default(FALSE), not null # notes :string(500) # previous_contribution :string(250) -# role :string(255) default("volunteer"), not null +# role :string default("volunteer"), not null # role_explanation :string(200) # special_price :decimal(8, 2) # state :string(50) # status :string(1) not null # zip_code :string(32) -# created_at :datetime -# updated_at :datetime +# created_at :datetime not null +# updated_at :datetime not null # event_id :integer not null # user_id :integer not null # +# Indexes +# +# index_ticket_requests_on_deleted_at (deleted_at) WHERE (deleted_at IS NULL) +# class TicketRequest < ApplicationRecord + # Class methods + class << self + # @description + # This method returns a two-dimensional array. The first row is the header row, + # and then for each ticket request we return the primary user with the ticket request info, + # followed by one row per guest. + def for_csv(event) + table = [] + + event.ticket_requests.active.each do |ticket_request| + # Main Ticket Request User Row + row = [] + + row << ticket_request.user.first + row << ticket_request.user.last + row << ticket_request.user.email + + csv_columns.each do |column| + row << ticket_request.attributes[column] + end + + table << row + + ticket_request.guests.each do |guest| + first, last, = guest.split(/\s+/) + email = guest.gsub(/.*.*/, '') + table << [last, first, email] + end + Rails.logger.debug table + end + + table + end + + def csv_header + ['Last', 'First', 'Email', *csv_columns.map(&:titleize)] + end + + def csv_columns + %w[ + address_line1 + address_line2 + city + state + zip_code + country_code + adults + kids + special_price + donation + needs_assistance + status + notes + role + role_explanation + previous_contribution + car_camping + car_camping_explanation + admin_notes + early_arrival_passes + late_departure_passes + ] + end + end + + # Deletions of TicketRequests are logical, not physical + acts_as_paranoid + include ActiveModel::Validations include ActiveModel::Validations::Callbacks STATUSES = [ - STATUS_PENDING = 'P', + STATUS_PENDING = 'P', STATUS_AWAITING_PAYMENT = 'A', - STATUS_DECLINED = 'D', - STATUS_COMPLETED = 'C', - STATUS_REFUNDED = 'R' + STATUS_DECLINED = 'D', + STATUS_COMPLETED = 'C', + STATUS_REFUNDED = 'R' ].freeze STATUS_NAMES = { @@ -103,30 +176,30 @@ class TicketRequest < ApplicationRecord presence: { if: -> { event.try(:require_mailing_address) } } validates :adults, presence: true, - numericality: { only_integer: true, greater_than: 0 } + numericality: { only_integer: true, greater_than: 0 } validates :early_arrival_passes, presence: true, - numericality: { only_integer: true, greater_than_or_equal_to: 0 } + numericality: { only_integer: true, greater_than_or_equal_to: 0 } validates :late_departure_passes, presence: true, - numericality: { only_integer: true, greater_than_or_equal_to: 0 } + numericality: { only_integer: true, greater_than_or_equal_to: 0 } validates :kids, allow_nil: true, - numericality: { only_integer: true, greater_than_or_equal_to: 0 } + numericality: { only_integer: true, greater_than_or_equal_to: 0 } validates :cabins, allow_nil: true, - numericality: { only_integer: true, greater_than_or_equal_to: 0 } + numericality: { only_integer: true, greater_than_or_equal_to: 0 } validates :role, presence: true, inclusion: { in: ROLES.keys } validates :role_explanation, presence: { if: -> { role == ROLE_OTHER } }, - length: { maximum: 200 } + length: { maximum: 200 } validates :notes, length: { maximum: 500 } validates :guests, length: { maximum: 8 } validates :special_price, allow_nil: true, - numericality: { greater_than_or_equal_to: 0 } + numericality: { greater_than_or_equal_to: 0 } validates :donation, numericality: { greater_than_or_equal_to: 0 } @@ -139,6 +212,8 @@ class TicketRequest < ApplicationRecord scope :declined, -> { where(status: STATUS_DECLINED) } scope :not_declined, -> { where.not(status: STATUS_DECLINED) } + scope :active, -> { where(status: [STATUS_COMPLETED, STATUS_AWAITING_PAYMENT]) } + def can_view?(user) self.user == user || event.admin?(user) end @@ -254,6 +329,13 @@ def guests_specified Array(guests).size end + def guest_list + [].tap do |guest_list| + guest_list << user.name_and_email + guests.each { |guest| guest_list << guest } + end.compact + end + def all_guests_specified? guests_specified >= guest_count end @@ -262,8 +344,6 @@ def country_name ISO3166::Country[country_code] end - private - def set_defaults self.status = nil if status.blank? self.status ||= if event diff --git a/app/models/time_slot.rb b/app/models/time_slot.rb index d039b600..a1b22068 100644 --- a/app/models/time_slot.rb +++ b/app/models/time_slot.rb @@ -4,13 +4,17 @@ # # Table name: time_slots # -# id :integer not null, primary key +# id :bigint not null, primary key # end_time :datetime not null # slots :integer not null # start_time :datetime not null -# created_at :datetime -# updated_at :datetime -# job_id :integer not null +# created_at :datetime not null +# updated_at :datetime not null +# job_id :bigint not null +# +# Indexes +# +# index_time_slots_on_job_id (job_id) # class TimeSlot < ApplicationRecord belongs_to :job diff --git a/app/models/user.rb b/app/models/user.rb index 3bd3c3b6..6d5d64ac 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -4,30 +4,30 @@ # # Table name: users # -# id :integer not null, primary key +# id :bigint not null, primary key # authentication_token :string(64) # confirmation_sent_at :datetime -# confirmation_token :string(255) +# confirmation_token :string # confirmed_at :datetime # current_sign_in_at :datetime -# current_sign_in_ip :string(255) -# email :string(255) not null -# encrypted_password :string(255) not null +# current_sign_in_ip :string +# email :string not null +# encrypted_password :string not null # failed_attempts :integer default(0) # first :text # last :text # last_sign_in_at :datetime -# last_sign_in_ip :string(255) +# last_sign_in_ip :string # locked_at :datetime # name :string(70) not null # remember_created_at :datetime # reset_password_sent_at :datetime -# reset_password_token :string(255) +# reset_password_token :string # sign_in_count :integer default(0) -# unconfirmed_email :string(255) -# unlock_token :string(255) -# created_at :datetime -# updated_at :datetime +# unconfirmed_email :string +# unlock_token :string +# created_at :datetime not null +# updated_at :datetime not null # # Indexes # diff --git a/db/migrate/20240516225937_add_deleted_at_to_ticket_requests.rb b/db/migrate/20240516225937_add_deleted_at_to_ticket_requests.rb new file mode 100644 index 00000000..c4dced7a --- /dev/null +++ b/db/migrate/20240516225937_add_deleted_at_to_ticket_requests.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class AddDeletedAtToTicketRequests < ActiveRecord::Migration[7.1] + def change + add_column :ticket_requests, :deleted_at, :datetime + add_index :ticket_requests, :deleted_at, where: 'deleted_at is NULL' + end +end diff --git a/db/structure.sql b/db/structure.sql index 1592ce32..d8f3573f 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -30,16 +30,16 @@ CREATE TABLE public.ar_internal_metadata ( -- CREATE TABLE public.eald_payments ( - id integer NOT NULL, - event_id integer, + id bigint NOT NULL, + event_id bigint, stripe_charge_id character varying NOT NULL, amount_charged_cents integer NOT NULL, name character varying(255) NOT NULL, email character varying(255) NOT NULL, early_arrival_passes integer DEFAULT 0 NOT NULL, late_departure_passes integer DEFAULT 0 NOT NULL, - created_at timestamp without time zone, - updated_at timestamp without time zone + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL ); @@ -67,11 +67,11 @@ ALTER SEQUENCE public.eald_payments_id_seq OWNED BY public.eald_payments.id; -- CREATE TABLE public.event_admins ( - id integer NOT NULL, - event_id integer, - user_id integer, - created_at timestamp without time zone, - updated_at timestamp without time zone + id bigint NOT NULL, + event_id bigint, + user_id bigint, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL ); @@ -99,12 +99,12 @@ ALTER SEQUENCE public.event_admins_id_seq OWNED BY public.event_admins.id; -- CREATE TABLE public.events ( - id integer NOT NULL, - name character varying(255), + id bigint NOT NULL, + name character varying, start_time timestamp without time zone, end_time timestamp without time zone, - created_at timestamp without time zone, - updated_at timestamp without time zone, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL, adult_ticket_price numeric(8,2), kid_ticket_price numeric(8,2), cabin_price numeric(8,2), @@ -112,7 +112,7 @@ CREATE TABLE public.events ( max_kid_tickets_per_request integer, max_cabins_per_request integer, max_cabin_requests integer, - photo character varying(255), + photo character varying, tickets_require_approval boolean DEFAULT true NOT NULL, require_mailing_address boolean DEFAULT false NOT NULL, allow_financial_assistance boolean DEFAULT false NOT NULL, @@ -120,8 +120,8 @@ CREATE TABLE public.events ( ticket_sales_start_time timestamp without time zone, ticket_sales_end_time timestamp without time zone, ticket_requests_end_time timestamp without time zone, - early_arrival_price numeric(8,2) DEFAULT 0, - late_departure_price numeric(8,2) DEFAULT 0, + early_arrival_price numeric(8,2) DEFAULT 0.0, + late_departure_price numeric(8,2) DEFAULT 0.0, slug text ); @@ -150,12 +150,12 @@ ALTER SEQUENCE public.events_id_seq OWNED BY public.events.id; -- CREATE TABLE public.jobs ( - id integer NOT NULL, - event_id integer NOT NULL, + id bigint NOT NULL, + event_id bigint NOT NULL, name character varying(100) NOT NULL, description character varying(512) NOT NULL, - created_at timestamp without time zone, - updated_at timestamp without time zone + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL ); @@ -183,13 +183,13 @@ ALTER SEQUENCE public.jobs_id_seq OWNED BY public.jobs.id; -- CREATE TABLE public.payments ( - id integer NOT NULL, + id bigint NOT NULL, ticket_request_id integer NOT NULL, stripe_charge_id character varying(255), status character varying(1) DEFAULT 'N'::character varying NOT NULL, - created_at timestamp without time zone, - updated_at timestamp without time zone, - explanation character varying(255), + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL, + explanation character varying, stripe_payment_id character varying ); @@ -218,13 +218,13 @@ ALTER SEQUENCE public.payments_id_seq OWNED BY public.payments.id; -- CREATE TABLE public.price_rules ( - id integer NOT NULL, - type character varying(255), - event_id integer, + id bigint NOT NULL, + type character varying, + event_id bigint, price numeric(8,2), trigger_value integer, - created_at timestamp without time zone, - updated_at timestamp without time zone + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL ); @@ -252,7 +252,7 @@ ALTER SEQUENCE public.price_rules_id_seq OWNED BY public.price_rules.id; -- CREATE TABLE public.schema_migrations ( - version character varying(255) NOT NULL + version character varying NOT NULL ); @@ -261,12 +261,12 @@ CREATE TABLE public.schema_migrations ( -- CREATE TABLE public.shifts ( - id integer NOT NULL, - time_slot_id integer NOT NULL, - user_id integer NOT NULL, + id bigint NOT NULL, + time_slot_id bigint NOT NULL, + user_id bigint NOT NULL, name character varying(70), - created_at timestamp without time zone, - updated_at timestamp without time zone + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL ); @@ -294,10 +294,10 @@ ALTER SEQUENCE public.shifts_id_seq OWNED BY public.shifts.id; -- CREATE TABLE public.site_admins ( - id integer NOT NULL, + id bigint NOT NULL, user_id integer NOT NULL, - created_at timestamp without time zone, - updated_at timestamp without time zone + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL ); @@ -325,20 +325,20 @@ ALTER SEQUENCE public.site_admins_id_seq OWNED BY public.site_admins.id; -- CREATE TABLE public.ticket_requests ( - id integer NOT NULL, + id bigint NOT NULL, adults integer DEFAULT 1 NOT NULL, kids integer DEFAULT 0 NOT NULL, cabins integer DEFAULT 0 NOT NULL, needs_assistance boolean DEFAULT false NOT NULL, notes character varying(500), status character varying(1) NOT NULL, - created_at timestamp without time zone, - updated_at timestamp without time zone, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL, user_id integer NOT NULL, special_price numeric(8,2), event_id integer NOT NULL, - donation numeric(8,2) DEFAULT 0, - role character varying(255) DEFAULT 'volunteer'::character varying NOT NULL, + donation numeric(8,2) DEFAULT 0.0, + role character varying DEFAULT 'volunteer'::character varying NOT NULL, role_explanation character varying(200), previous_contribution character varying(250), address_line1 character varying(200), @@ -353,7 +353,8 @@ CREATE TABLE public.ticket_requests ( agrees_to_terms boolean, early_arrival_passes integer DEFAULT 0 NOT NULL, late_departure_passes integer DEFAULT 0 NOT NULL, - guests text + guests text, + deleted_at timestamp(6) without time zone ); @@ -381,13 +382,13 @@ ALTER SEQUENCE public.ticket_requests_id_seq OWNED BY public.ticket_requests.id; -- CREATE TABLE public.time_slots ( - id integer NOT NULL, - job_id integer NOT NULL, + id bigint NOT NULL, + job_id bigint NOT NULL, start_time timestamp without time zone NOT NULL, end_time timestamp without time zone NOT NULL, slots integer NOT NULL, - created_at timestamp without time zone, - updated_at timestamp without time zone + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL ); @@ -415,26 +416,26 @@ ALTER SEQUENCE public.time_slots_id_seq OWNED BY public.time_slots.id; -- CREATE TABLE public.users ( - id integer NOT NULL, - email character varying(255) NOT NULL, - encrypted_password character varying(255) NOT NULL, - reset_password_token character varying(255), + id bigint NOT NULL, + email character varying NOT NULL, + encrypted_password character varying NOT NULL, + reset_password_token character varying, reset_password_sent_at timestamp without time zone, remember_created_at timestamp without time zone, sign_in_count integer DEFAULT 0, current_sign_in_at timestamp without time zone, last_sign_in_at timestamp without time zone, - current_sign_in_ip character varying(255), - last_sign_in_ip character varying(255), - confirmation_token character varying(255), + current_sign_in_ip character varying, + last_sign_in_ip character varying, + confirmation_token character varying, confirmed_at timestamp without time zone, confirmation_sent_at timestamp without time zone, - unconfirmed_email character varying(255), + unconfirmed_email character varying, failed_attempts integer DEFAULT 0, - unlock_token character varying(255), + unlock_token character varying, locked_at timestamp without time zone, - created_at timestamp without time zone, - updated_at timestamp without time zone, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL, name character varying(70) NOT NULL, authentication_token character varying(64), first text, @@ -594,6 +595,14 @@ ALTER TABLE ONLY public.price_rules ADD CONSTRAINT price_rules_pkey PRIMARY KEY (id); +-- +-- Name: schema_migrations schema_migrations_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.schema_migrations + ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (version); + + -- -- Name: shifts shifts_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -634,6 +643,13 @@ ALTER TABLE ONLY public.users ADD CONSTRAINT users_pkey PRIMARY KEY (id); +-- +-- Name: index_eald_payments_on_event_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_eald_payments_on_event_id ON public.eald_payments USING btree (event_id); + + -- -- Name: index_event_admins_on_event_id_and_user_id; Type: INDEX; Schema: public; Owner: - -- @@ -648,6 +664,13 @@ CREATE UNIQUE INDEX index_event_admins_on_event_id_and_user_id ON public.event_a CREATE INDEX index_event_admins_on_user_id ON public.event_admins USING btree (user_id); +-- +-- Name: index_jobs_on_event_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_jobs_on_event_id ON public.jobs USING btree (event_id); + + -- -- Name: index_payments_on_stripe_payment_id; Type: INDEX; Schema: public; Owner: - -- @@ -662,6 +685,34 @@ CREATE INDEX index_payments_on_stripe_payment_id ON public.payments USING btree CREATE INDEX index_price_rules_on_event_id ON public.price_rules USING btree (event_id); +-- +-- Name: index_shifts_on_time_slot_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_shifts_on_time_slot_id ON public.shifts USING btree (time_slot_id); + + +-- +-- Name: index_shifts_on_user_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_shifts_on_user_id ON public.shifts USING btree (user_id); + + +-- +-- Name: index_ticket_requests_on_deleted_at; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_ticket_requests_on_deleted_at ON public.ticket_requests USING btree (deleted_at) WHERE (deleted_at IS NULL); + + +-- +-- Name: index_time_slots_on_job_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_time_slots_on_job_id ON public.time_slots USING btree (job_id); + + -- -- Name: index_users_on_confirmation_token; Type: INDEX; Schema: public; Owner: - -- @@ -690,13 +741,6 @@ CREATE UNIQUE INDEX index_users_on_reset_password_token ON public.users USING bt CREATE UNIQUE INDEX index_users_on_unlock_token ON public.users USING btree (unlock_token); --- --- Name: unique_schema_migrations; Type: INDEX; Schema: public; Owner: - --- - -CREATE UNIQUE INDEX unique_schema_migrations ON public.schema_migrations USING btree (version); - - -- -- PostgreSQL database dump complete -- @@ -704,6 +748,7 @@ CREATE UNIQUE INDEX unique_schema_migrations ON public.schema_migrations USING b SET search_path TO "$user", public; INSERT INTO "schema_migrations" (version) VALUES +('20240516225937'), ('20240513035005'), ('20240509025037'), ('20240423054549'), diff --git a/spec/controllers/ticket_requests_controller_spec.rb b/spec/controllers/ticket_requests_controller_spec.rb index 6bf554de..85355ca9 100644 --- a/spec/controllers/ticket_requests_controller_spec.rb +++ b/spec/controllers/ticket_requests_controller_spec.rb @@ -6,7 +6,6 @@ describe TicketRequestsController, type: :controller do let(:user) { create(:user) } let(:event) { create(:event) } - let(:viewer) { nil } before { sign_in viewer if viewer } @@ -250,5 +249,78 @@ end end end + + describe 'DELETE #destroy' do + let(:ticket_request) { create(:ticket_request, event:, user:) } + let(:ticket_request_id) { ticket_request.id } + let(:ticket_request_from_db) { TicketRequest.where(id: ticket_request_id).first } + + describe 'before destroy' do + subject { ticket_request_from_db } + + it { is_expected.to eql(ticket_request) } + end + + describe 'after DELETE #destroy' do + subject { delete_request_proc[ticket_request] } + + let(:delete_request_proc) do + lambda do |ticket_request| + delete :destroy, params: { event_id: ticket_request.event.to_param, + id: ticket_request.to_param } + end + end + + context 'attempt to delete when not allowed' do + context 'when viewer not signed in' do + it 'has no logged in user' do + expect(controller.current_user).to be_nil + end + + it { succeeds } + end + + context 'when viewer is not a ticket request owner' do + let(:viewer) { create(:user) } + + it 'logged in user is not the same user as the ticket_request owner' do + expect(controller.current_user).not_to be_nil + expect(controller.current_user).to eql(viewer) + expect(controller.current_user).not_to eql(ticket_request.user) + end + + it { is_expected.to have_http_status(:redirect) } + end + end + + shared_examples_for 'successful deletion of the ticket request' do + before { sign_in(viewer) } + + subject { delete_request_proc[ticket_request] } + + it { is_expected.to have_http_status(:ok) } + + it 'should no longer have the TicketRequest' + end + + context 'when viewer is the event admin' do + let(:viewer) { create(:event_admin, event:).user } + + it_behaves_like 'successful deletion of the ticket request' + end + + context 'when viewer is a site admin' do + let(:viewer) { create(:user, :site_admin) } + + it_behaves_like 'successful deletion of the ticket request' + end + + context 'when viewer is ticket request owner' do + let(:viewer) { ticket_request.user } + + it_behaves_like 'successful deletion of the ticket request' + end + end + end end # rubocop: enable RSpec diff --git a/spec/factories/ticket_request.rb b/spec/factories/ticket_request.rb index fbbb5be3..6cf449fa 100644 --- a/spec/factories/ticket_request.rb +++ b/spec/factories/ticket_request.rb @@ -8,7 +8,12 @@ needs_assistance { [true, false].sample } notes { Faker::Lorem.paragraph } agrees_to_terms { true } - guests { [Faker::Name.name, Faker::Name.name, Faker::Name.name] } + guests do + [ + "#{Faker::Name.first_name} #{Faker::Name.last_name} <#{Faker::Internet.email}>", + "#{Faker::Name.first_name} #{Faker::Name.last_name} <#{Faker::Internet.email}>" + ] + end user event diff --git a/spec/factories/user.rb b/spec/factories/user.rb index 353426aa..e761652a 100644 --- a/spec/factories/user.rb +++ b/spec/factories/user.rb @@ -7,6 +7,7 @@ name { "#{first} #{last}" } email { Faker::Internet.email } password { Faker::Internet.password(min_length: 8) } + confirmed_at { Time.current } trait :site_admin do site_admin diff --git a/spec/models/ticket_request_spec.rb b/spec/models/ticket_request_spec.rb index 895da6d0..40c59c3b 100644 --- a/spec/models/ticket_request_spec.rb +++ b/spec/models/ticket_request_spec.rb @@ -378,7 +378,7 @@ let(:guests) do [ 'Carl Cox ', - 'John Digweed ', 'Matt Levy '] } + + before do + # These are completed + (number_of_active_requests - 2).times do |_index| + event.ticket_requests.create!(user: create(:user), agrees_to_terms: true, status: TicketRequest::STATUS_COMPLETED, guests: guest_list) + end + + # These are still waiting on the payment + (number_of_active_requests - 3).times do + event.ticket_requests.create!(user: create(:user), agrees_to_terms: true, status: TicketRequest::STATUS_AWAITING_PAYMENT, guests: guest_list) + end + + # These should not be included in the CSV, as they have not been approved + number_of_inactive_requests.times do + event.ticket_requests.create!(user: create(:user), agrees_to_terms: true, status: TicketRequest::STATUS_PENDING, guests: guest_list) + end + end + + describe 'event with active requests' do + subject { described_class.for_csv(event) } + + it 'has the right number of total ticket requests' do + expect(event.ticket_requests.size).to eq(number_of_active_requests + number_of_inactive_requests) + end + + it 'has the right number of active ticket requests' do + expect(event.ticket_requests.active.size).to eq(number_of_active_requests) + end + + it 'each request should have two guests' do + event.ticket_requests.each do |tr| + expect(tr.guests.size).to eq(2) end end + + it { is_expected.to be_a(Array) } + + it { is_expected.not_to eq [] } + + # number of active requests with two guests each + its(:size) { is_expected.to eq(number_of_active_requests * 3) } + end + + describe '.csv_columns' do + subject { described_class.csv_header } + + it { is_expected.to start_with %w[Last First Email] } end end end