From e7c482bf3a992e6c29c8737949c69a455f9222a8 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Wed, 13 Jan 2021 16:59:04 -0600 Subject: [PATCH] Add support for 'event_discount.*' events --- app/controllers/event_discounts_controller.rb | 32 +-- app/models/event_discount.rb | 60 ++++- app/models/ticket_level.rb | 8 +- .../Nonprofit/Event/EventDiscount.ts | 12 +- spec/models/event_discount_spec.rb | 232 ++++++++++++++++++ spec/models/ticket_level_spec.rb | 43 ---- 6 files changed, 315 insertions(+), 72 deletions(-) create mode 100644 spec/models/event_discount_spec.rb diff --git a/app/controllers/event_discounts_controller.rb b/app/controllers/event_discounts_controller.rb index 73bd2832..e1aaec84 100644 --- a/app/controllers/event_discounts_controller.rb +++ b/app/controllers/event_discounts_controller.rb @@ -8,14 +8,7 @@ class EventDiscountsController < ApplicationController before_action :authenticate_event_editor!, except: [:index] def create - event_discount_params[:event_id] = current_event.id - - render JsonResp.new(event_discount_params) do |_data| - requires(:code, :name).as_string - requires(:event_id, :percent).as_int - end.when_valid do |data| - { status: 200, json: { event_discount: current_event.event_discounts.create(data) } } - end + render json: { data: {event_discount: current_event.event_discounts.create(event_discount_params[:event_discount]) } } end def index @@ -23,27 +16,22 @@ class EventDiscountsController < ApplicationController end def update - discount = Hamster.to_ruby( - Psql.execute( - Qexpr.new.update(:event_discounts, event_discount_params) - .where('id=$id', id: params[:id]) - .returning('*') - ).first - ) - render json: { status: 200, data: discount } + + current_event_discount.update event_discount_params[:event_discount] + render json: { status: 200, data: current_event_discount } end def destroy - Psql.execute( - Qexpr.new.delete_from('event_discounts') - .where('event_discounts.event_id=$id', id: params['event_id']) - .where('event_discounts.id=$id', id: params['id']) - ) + current_event_discount.destroy end private + def current_event_discount + current_event.event_discounts.find(params[:id]) + end + def event_discount_params - params.required(:event_discount).permit(:code, :event_id, :name, :percent) + params.required(:event_discount).permit(:code, :name, :percent) end end diff --git a/app/models/event_discount.rb b/app/models/event_discount.rb index f611b13f..2805a2ae 100644 --- a/app/models/event_discount.rb +++ b/app/models/event_discount.rb @@ -7,19 +7,77 @@ class EventDiscount < ApplicationRecord # :event_id, # :name, # :percent + validates :name, presence: true + validates :code, presence: true + validates :event, presence: true + validates :percent, presence: true, numericality: {only_integer: true, greater_than: 0, less_than_or_equal_to: 100} + + + # we use after_create_commit because the db could be in an inconsistent state and the messages will be slightly wrong + # we use after commit on the rest for consistency + after_create_commit :publish_create + after_destroy_commit :publish_delete + after_update_commit :publish_updated belongs_to :event has_many :tickets def to_builder(*expand) Jbuilder.new do |json| - json.(self, :id, :name) + json.(self, :id, :name, :code) + json.deleted !persisted? + json.object 'event_discount' + json.discount do + json.percent percent + end + if event if expand.include? :event json.event event.to_builder else json.event event.id end + if event.nonprofit + if expand.include? :nonprofit + json.nonprofit event.nonprofit.to_builder + else + json.nonprofit event.nonprofit.id + end + else + json.nonprofit nil + end + + if expand.include? :ticket_levels + json.ticket_levels event.ticket_levels do |tl| + json.merge! tl.to_builder.attributes! + end + else + json.ticket_levels event.ticket_levels.pluck(:id) + end + end + end + end + + private + def publish_create + Houdini.event_publisher.announce(:event_discount_created, to_event('event_discount.created', :event, :nonprofit, :ticket_levels).attributes!) + end + + def publish_updated + Houdini.event_publisher.announce(:event_discount_updated, to_event('event_discount.updated', :event, :nonprofit, :ticket_levels).attributes!) + end + + def publish_delete + Houdini.event_publisher.announce(:event_discount_deleted, to_event('event_discount.deleted', :event, :nonprofit, :ticket_levels).attributes!) + end + + def to_event(event_type, *expand) + Jbuilder.new do |event| + event.id SecureRandom.uuid + event.object 'object_event' + event.type event_type + event.data do + event.object to_builder(*expand) end end end diff --git a/app/models/ticket_level.rb b/app/models/ticket_level.rb index a39bfdd8..4d9777e6 100644 --- a/app/models/ticket_level.rb +++ b/app/models/ticket_level.rb @@ -73,7 +73,7 @@ class TicketLevel < ApplicationRecord if expand.include? :event_discounts json.event_discounts event.event_discounts do |disc| - disc.to_builder + json.merge! disc.to_builder.attributes! end else json.event_discounts event.event_discounts.pluck(:id) @@ -84,18 +84,18 @@ class TicketLevel < ApplicationRecord private def publish_create - Houdini.event_publisher.announce(:ticket_level_created, to_event('ticket_level.created', :event, :nonprofit).attributes!) + Houdini.event_publisher.announce(:ticket_level_created, to_event('ticket_level.created', :event, :nonprofit, :event_discounts).attributes!) end def publish_updated # we don't run update when we've really just discarded unless deleted - Houdini.event_publisher.announce(:ticket_level_updated, to_event('ticket_level.updated', :event, :nonprofit).attributes!) + Houdini.event_publisher.announce(:ticket_level_updated, to_event('ticket_level.updated', :event, :nonprofit, :event_discounts).attributes!) end end def publish_delete - Houdini.event_publisher.announce(:ticket_level_deleted, to_event('ticket_level.deleted', :event, :nonprofit).attributes!) + Houdini.event_publisher.announce(:ticket_level_deleted, to_event('ticket_level.deleted', :event, :nonprofit, :event_discounts).attributes!) end def to_event(event_type, *expand) diff --git a/docs/event_definitions/Nonprofit/Event/EventDiscount.ts b/docs/event_definitions/Nonprofit/Event/EventDiscount.ts index cf542f27..a2d19358 100644 --- a/docs/event_definitions/Nonprofit/Event/EventDiscount.ts +++ b/docs/event_definitions/Nonprofit/Event/EventDiscount.ts @@ -1,15 +1,23 @@ // License: LGPL-3.0-or-later -import type { IdType, HoudiniObject } from '../../common'; +import type { IdType, HoudiniObject, Amount, HoudiniEvent } from '../../common'; import type Nonprofit from '..'; import type Event from '.'; import type { TicketLevel } from './TicketLevel'; + +type DiscountType = { percent: number } | { amount: Amount }; /** * Describes an EventDiscount (shell) */ export interface EventDiscount extends HoudiniObject { + code: string; + discount: DiscountType; event: IdType | Event; nonprofit: IdType | Nonprofit; object: "event_discount"; ticket_levels: IdType[] | TicketLevel[]; -} \ No newline at end of file +} + +export type EventDiscountCreated = HoudiniEvent<'event_discount.created', EventDiscount>; +export type EventDiscountlUpdated = HoudiniEvent<'event_discount.updated', EventDiscount>; +export type EventDiscountDeleted = HoudiniEvent<'event_discount.deleted', EventDiscount>; \ No newline at end of file diff --git a/spec/models/event_discount_spec.rb b/spec/models/event_discount_spec.rb new file mode 100644 index 00000000..974d3cf1 --- /dev/null +++ b/spec/models/event_discount_spec.rb @@ -0,0 +1,232 @@ +# frozen_string_literal: true + +# License: AGPL-3.0-or-later WITH WTO-AP-3.0-or-later +# Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE +require 'rails_helper' + +RSpec.describe EventDiscount, type: :model do + include_context :shared_donation_charge_context + let(:name) {"CUSTOM EVENT DISCOUNT"} + let(:percent) { 55} + let(:code) { "fewet"} + let(:event_discount) { + ticket_level + event.event_discounts.create(name: name, percent: percent, code: code) + } + + describe 'validate' do + let(:event_discount) { ed = EventDiscount.new; ed.save; ed} + let(:ed_percent_at_0) { ed = EventDiscount.new(percent: 0); ed.save; ed} + let(:ed_percent_at_101) { ed = EventDiscount.new(percent: 101); ed.save; ed} + + it('has errors on name') do + expect(event_discount.errors.details[:name].length).to be(1) + end + + it('has errors on code') do + expect(event_discount.errors.details[:code].length).to be(1) + end + + it('has errors on event') do + expect(event_discount.errors.details[:event].length).to be(1) + end + + it('has errors on percent') do + expect(event_discount.errors.details[:percent].length).to be(2) + end + + it('has errors on percents at 0') do + expect(ed_percent_at_0.errors.details[:percent].length).to be(1) + end + + it('has errors on percents at 101') do + expect(ed_percent_at_101.errors.details[:percent].length).to be(1) + end + + end + + describe 'create' do + + it 'is without error' do + expect(event_discount.errors).to be_empty + end + + it 'announces create' do + expect(Houdini.event_publisher).to receive(:announce).with(:ticket_level_created, anything).ordered + expect(Houdini.event_publisher).to receive(:announce).with(:event_discount_created, { + 'id' => kind_of(String), + 'object' => 'object_event', + 'type' => 'event_discount.created', + 'data' => { + 'object' => { + 'code' => code, + 'deleted' => false, + 'discount' => { + 'percent' => percent + }, + 'event' => { + 'id' => event.id, + 'name' => event.name, + 'object' => 'event', + 'nonprofit' => nonprofit.id + }, + 'id'=> kind_of(Numeric), + 'name' => name, + 'nonprofit'=> { + 'id' => nonprofit.id, + 'name' => nonprofit.name, + 'object' => 'nonprofit' + }, + 'object' => 'event_discount', + 'ticket_levels' => [ + { + 'id' => ticket_level.id, + 'name' => ticket_level.name, + 'deleted' => ticket_level.deleted, + 'order' => ticket_level.order, + 'limit' => ticket_level.limit, + 'object' => 'ticket_level', + 'description' => ticket_level.description, + 'amount' => { + 'value_in_cents' => ticket_level.amount, + 'currency' => 'usd' + }, + 'available_to' => 'everyone', + 'nonprofit' => nonprofit.id, + 'event' => event.id, + 'event_discounts' => [kind_of(Numeric)] + } + ] + } + } + }) + + event_discount + end + end + + describe 'update' do + it 'is without error' do + event_discount.code = 'code' + event_discount.save + expect(event_discount.errors).to be_empty + end + + it 'announces updated' do + expect(Houdini.event_publisher).to receive(:announce).with(:ticket_level_created, anything).ordered + expect(Houdini.event_publisher).to receive(:announce).with(:event_discount_created, anything).ordered + expect(Houdini.event_publisher).to receive(:announce).with(:event_discount_updated, { + 'id' => kind_of(String), + 'object' => 'object_event', + 'type' => 'event_discount.updated', + 'data' => { + 'object' => { + 'code' => 'code', + 'deleted' => false, + 'discount' => { + 'percent' => percent + }, + 'event' => { + 'id' => event.id, + 'name' => event.name, + 'object' => 'event', + 'nonprofit' => nonprofit.id + }, + 'id'=> kind_of(Numeric), + 'name' => name, + 'nonprofit'=> { + 'id' => nonprofit.id, + 'name' => nonprofit.name, + 'object' => 'nonprofit' + }, + 'object' => 'event_discount', + 'ticket_levels' => [ + { + 'id' => ticket_level.id, + 'name' => ticket_level.name, + 'deleted' => ticket_level.deleted, + 'order' => ticket_level.order, + 'limit' => ticket_level.limit, + 'object' => 'ticket_level', + 'description' => ticket_level.description, + 'amount' => { + 'value_in_cents' => ticket_level.amount, + 'currency' => 'usd' + }, + 'available_to' => 'everyone', + 'nonprofit' => nonprofit.id, + 'event' => event.id, + 'event_discounts' => [kind_of(Numeric)] + } + ] + } + } + }).ordered + + event_discount.code = 'code' + event_discount.save! + end + end + + + describe 'deleted' do + it 'is without error' do + event_discount.destroy + expect(event_discount).to_not be_persisted + end + + it 'announces deleted' do + expect(Houdini.event_publisher).to receive(:announce).with(:ticket_level_created, anything).ordered + expect(Houdini.event_publisher).to receive(:announce).with(:event_discount_created, anything).ordered + expect(Houdini.event_publisher).to receive(:announce).with(:event_discount_deleted, { + 'id' => kind_of(String), + 'object' => 'object_event', + 'type' => 'event_discount.deleted', + 'data' => { + 'object' => { + 'code' => code, + 'deleted' => true, + 'discount' => { + 'percent' => percent + }, + 'event' => { + 'id' => event.id, + 'name' => event.name, + 'object' => 'event', + 'nonprofit' => nonprofit.id + }, + 'id'=> kind_of(Numeric), + 'name' => name, + 'nonprofit'=> { + 'id' => nonprofit.id, + 'name' => nonprofit.name, + 'object' => 'nonprofit' + }, + 'object' => 'event_discount', + 'ticket_levels' => [ + { + 'id' => ticket_level.id, + 'name' => ticket_level.name, + 'deleted' => ticket_level.deleted, + 'order' => ticket_level.order, + 'limit' => ticket_level.limit, + 'object' => 'ticket_level', + 'description' => ticket_level.description, + 'amount' => { + 'value_in_cents' => ticket_level.amount, + 'currency' => 'usd' + }, + 'available_to' => 'everyone', + 'nonprofit' => nonprofit.id, + 'event' => event.id, + 'event_discounts' => [kind_of(Numeric)] + } + ] + } + } + }).ordered + + event_discount.destroy + end + end +end diff --git a/spec/models/ticket_level_spec.rb b/spec/models/ticket_level_spec.rb index c59d8272..0864edab 100644 --- a/spec/models/ticket_level_spec.rb +++ b/spec/models/ticket_level_spec.rb @@ -302,47 +302,4 @@ RSpec.describe TicketLevel, type: :model do end end end - # it 'creates' do - # expect(tag_master.errors).to be_empty - # end - - # it 'announces create' do - # expect(Houdini.event_publisher).to receive(:announce).with(:tag_master_created, { - # 'id' => kind_of(String), - # 'object' => 'event', - # 'type' => 'tag_master.created', - # 'data' => { - # 'object' => { - # 'id'=> kind_of(Numeric), - # 'deleted' => false, - # 'name' => name, - # 'nonprofit'=> nonprofit.id, - # 'object' => 'tag_master' - # } - # } - # }) - - # tag_master - # end - - # it 'announces deleted' do - # expect(Houdini.event_publisher).to receive(:announce).with(:tag_master_created, anything).ordered - # expect(Houdini.event_publisher).to receive(:announce).with(:tag_master_deleted, { - # 'id' => kind_of(String), - # 'object' => 'event', - # 'type' => 'tag_master.deleted', - # 'data' => { - # 'object' => { - # 'id'=> kind_of(Numeric), - # 'deleted' => true, - # 'name' => name, - # 'nonprofit'=> nonprofit.id, - # 'object' => 'tag_master' - # } - # } - # }).ordered - - # tag_master.discard! - - # end end