Skip to content

Commit

Permalink
Support event registration imports
Browse files Browse the repository at this point in the history
  • Loading branch information
angusmcleod committed Oct 4, 2024
1 parent 10a83de commit 68d202e
Show file tree
Hide file tree
Showing 17 changed files with 285 additions and 19 deletions.
5 changes: 5 additions & 0 deletions app/models/discourse_events/event.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ class Event < ActiveRecord::Base
class_name: "DiscourseEvents::EventTopic"
has_many :series_topics, through: :series_events, source: :topic

has_many :registrations,
foreign_key: "event_id",
class_name: "DiscourseEvents::EventRegistration",
dependent: :destroy

PAST_SERIES_EVENTS_SQL = (<<~SQL)
DISTINCT ON (series_id) *
FROM discourse_events_events
Expand Down
37 changes: 37 additions & 0 deletions app/models/discourse_events/event_registration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# frozen_string_literal: true

module DiscourseEvents
class EventRegistration < ActiveRecord::Base
self.table_name = "discourse_events_event_registrations"

belongs_to :user
belongs_to :event

enum status: { confirmed: 0, declined: 1, tentative: 2 }
end
end

# == Schema Information
#
# Table name: discourse_events_event_registrations
#
# id :bigint not null, primary key
# event_id :bigint not null
# user_id :bigint
# email :string not null
# uid :string
# name :string
# status :integer
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# idx_events_event_registration_emails (email) UNIQUE
# index_discourse_events_event_registrations_on_event_id (event_id)
# index_discourse_events_event_registrations_on_user_id (user_id)
#
# Foreign Keys
#
# fk_rails_... (event_id => discourse_events_events.id)
#
1 change: 1 addition & 0 deletions config/locales/server.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,4 @@ en:
import_finished: "Finished importing from %{provider_type}. Retrieved %{events_count} events, created %{created_count} events and updated %{updated_count} events."
sync_finished: "Finished syncing %{provider_type} with %{category_name}. Created %{created_count} topics and updated %{updated_count} topics for the %{client_name} plugin."
sync_client_not_ready: "Failed to sync %{provider_type} with %{category_name}. %{client_name} plugin is not ready."
sync_failed_to_create_registration_user: "Failed to create user for registration: %{email}"
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# frozen_string_literal: true
class CreateDiscourseEventsEventRegistrations < ActiveRecord::Migration[7.1]
def change
create_table :discourse_events_event_registrations do |t|
t.references :event, index: true, foreign_key: { to_table: :discourse_events_events }, null: false
t.references :user
t.string :email, null: false
t.string :uid
t.string :name
t.integer :status

t.timestamps
end

add_index :discourse_events_event_registrations,
%i[email],
unique: true,
name: :idx_events_event_registration_emails
end
end
23 changes: 21 additions & 2 deletions lib/discourse_events/import_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def import(opts = {})
data[:status] = "published" if data[:status].blank?
data[:series_id] = imported_event.metadata.series_id
data[:occurrence_id] = imported_event.metadata.occurrence_id
data[:registrations] = imported_event.associated_data.registrations

imported_events[imported_event.metadata.uid] = data
end
Expand All @@ -50,17 +51,35 @@ def import(opts = {})
event_source = source.event_sources.find_by(uid: uid)

if event_source
event_source.event.update!(data)
event_source.event.update!(data.except(:registrations))

updated_event_uids << event_source.uid
else
ActiveRecord::Base.transaction do
event = Event.create!(data)
event = Event.create!(data.except(:registrations))
event_source = EventSource.create!(uid: uid, event_id: event.id, source_id: source.id)
end

created_event_uids << event_source.uid
end

if event_source.event.id && data[:registrations].present?
registrations =
data[:registrations].map do |registration|
result = {
event_id: event_source.event.id,
email: registration[:email],
uid: registration[:uid],
name: registration[:name],
}
if registration[:status] &&
EventRegistration.statuses.keys.include?(registration[:status])
result[:status] = EventRegistration.statuses[registration[:status]]
end
result
end
EventRegistration.upsert_all(registrations, unique_by: %i[email])
end
end
end

Expand Down
2 changes: 1 addition & 1 deletion lib/discourse_events/subscription_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class SubscriptionManager
omnievent_icalendar: "0.1.0.pre7",
omnievent_api: "0.1.0.pre5",
omnievent_outlook: "0.1.0.pre7",
omnievent_google: "0.1.0.pre4",
omnievent_google: "0.1.0.pre5",
},
}

Expand Down
67 changes: 61 additions & 6 deletions lib/discourse_events/syncer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,25 @@ def create_topic(event)
topic = create_client_topic(event)
raise ActiveRecord::Rollback if topic.blank?
create_event_topic(event, topic)
topic.id
topic
end

def update_topic(topic, event, add_raw: nil)
topic = update_client_topic(topic, event, add_raw: add_raw)
raise ActiveRecord::Rollback if topic.blank?
topic.id
topic
end

def connect_topic(topic, event)
return false if topic.first_post.event.present?
update_topic(topic, event, add_raw: true)
end

def update_registrations(topic, event)
ensure_registration_users(event)
update_client_registrations(topic, event)
end

def create_post(event, topic_opts = {})
topic_opts = { title: event.name }.merge(topic_opts)
topic_opts[:category] = source.category.id if source&.category_id
Expand Down Expand Up @@ -86,6 +91,10 @@ def update_client_topic(topic, event, add_raw: false)
raise NotImplementedError
end

def update_client_registrations(topic, event)
raise NotImplementedError
end

def post_raw(event, post: nil, add_raw: false)
raise NotImplementedError
end
Expand All @@ -100,7 +109,12 @@ def update_events
event
.event_topics
.where(client: client)
.each { |et| topics_updated << update_topic(et.topic, event) }
.each do |et|
topic = update_topic(et.topic, event)
next unless topic
topics_updated << topic.id
update_registrations(topic, event)
end
end
end

Expand All @@ -111,7 +125,12 @@ def create_events
topics_created = []

unsynced_events.each do |event|
ActiveRecord::Base.transaction { topics_created << create_topic(event) }
ActiveRecord::Base.transaction do
topic = create_topic(event)
next unless topic
topics_created << topic.id
update_registrations(topic, event)
end
end

topics_created
Expand All @@ -127,9 +146,13 @@ def update_series_events_topics
ActiveRecord::Base.transaction do
if topic.present?
ensure_event_topic(event, topic)
topics_updated << update_topic(topic, event)
topic = update_topic(topic, event)
topics_updated << topic.id
update_registrations(topic, event)
else
topics_created << create_topic(event)
topic = create_topic(event)
topics_created << topic.id
update_registrations(topic, event)
end
end
end
Expand Down Expand Up @@ -192,5 +215,37 @@ def log(type, message)
def one_topic_per_series
source.supports_series && SiteSetting.events_one_event_per_series
end

def ensure_registration_users(event)
event.registrations.each do |registration|
next if registration.user.present?
user = find_or_create_user(registration)
next unless user.present?
registration.update(user_id: user.id)
end
end

def find_or_create_user(registration)
user = User.find_by_email(registration.email)

unless user
begin
user =
User.create!(
email: registration.email,
username: UserNameSuggester.suggest(registration.name.presence || registration.email),
name: registration.name || User.suggest_name(registration.email),
staged: true,
)
rescue PG::UniqueViolation,
ActiveRecord::RecordNotUnique,
ActiveRecord::RecordInvalid => error
message = I18n.t("log.sync_failed_to_create_registration_user", email: registration.email)
log(:error, message)
end
end

user
end
end
end
31 changes: 31 additions & 0 deletions lib/discourse_events/syncer/discourse_calendar.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,37 @@ def post_raw(event, post: nil, add_raw: false)
def add_end_time(event)
event.end_time && event.end_time > event.start_time
end

def update_client_registrations(topic, event)
post = topic.first_post

event.registrations.each do |registration|
next unless registration.user

invitee =
DiscoursePostEvent::Invitee.find_by(user_id: registration.user.id, post_id: post.id)
status = invitee_status(registration.status)

if invitee
invitee.update_attendance!(status)
else
DiscoursePostEvent::Invitee.create_attendance!(registration.user.id, post.id, status)
end
end
end

def invitee_status(registration_status)
case registration_status
when "confirmed"
:going
when "declined"
:not_going
when "tentative"
:interested
else
DiscoursePostEvent::Invitee::UNKNOWN_ATTENDANCE
end
end
end
end

Expand Down
30 changes: 22 additions & 8 deletions lib/discourse_events/syncer/discourse_events.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,27 @@ def post_raw(event, post: nil, add_raw: false)
raw += "\n\n#{post.raw}" if post && add_raw
raw
end

def update_client_registrations(topic, event)
confirmed_user_ids = []

event.registrations.each do |registration|
next unless registration.user && registration.confirmed?
confirmed_user_ids << registration.user.id
end

if confirmed_user_ids.any?
event_going = topic.event_going || []

if !topic.event_going_max || list.length <= topic.event_going_max
confirmed_user_ids.each do |user_id|
event_going << user_id unless event_going.include?(user_id)
end
end

topic.custom_fields["event_going"] = event_going
topic.save_custom_fields(true)
end
end
end
end

# == Events Plugin Schema
#
# Table: topic_custom_fields
#
# Fields:
# event_start unix datetime
# event_end unix datetime
1 change: 1 addition & 0 deletions plugin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
require_relative "lib/discourse_events/auth/google.rb"
require_relative "app/models/discourse_events/filter.rb"
require_relative "app/models/discourse_events/event_topic.rb"
require_relative "app/models/discourse_events/event_registration.rb"
require_relative "app/models/discourse_events/event_source.rb"
require_relative "app/models/discourse_events/event.rb"
require_relative "app/models/discourse_events/log.rb"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# frozen_string_literal: true

Fabricator(:discourse_events_event_registration, from: "DiscourseEvents::EventRegistration") do
event { Fabricate(:discourse_events_event) }
email { sequence(:email) { |i| "angus#{i}@email.com" } }
end
9 changes: 8 additions & 1 deletion spec/fixtures/list_events.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,14 @@
"label": "Don Giovanni Live"
}
]
}
},
"attendees": [
{
"email": "angus@email.com",
"name": "Angus McLeod",
"status": "confirmed"
}
]
}
]
}
7 changes: 6 additions & 1 deletion spec/lib/discourse_events/import_manager_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,16 @@ def event_uids
context "with a subscription" do
before { enable_subscription(:business) }

it "imports a source" do
it "imports a events from source" do
subject.import_source(source.id)
expect(event_uids).to match_array(raw_event_uids)
end

it "imports event registrations" do
subject.import_source(source.id)
expect(DiscourseEvents::EventRegistration.exists?(email: "angus@email.com")).to eq(true)
end

it "does not create a previously sourced event" do
event = Fabricate(:discourse_events_event, start_time: "2017-09-18T16:00:00+08:00")
event_source =
Expand Down
11 changes: 11 additions & 0 deletions spec/lib/discourse_events/syncer/discourse_calendar_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,15 @@ def sync_events(opts = {})
event_dates = DiscoursePostEvent::EventDate.all
expect(event_dates.first.ends_at).to be(nil)
end

context "with event registrations" do
fab!(:event_registration1) do
Fabricate(:discourse_events_event_registration, event: event, user: user, status: "confirmed")
end

it "creates event invitees" do
post = sync_events
expect(post.event.invitees.first.user.id).to eq(user.id)
end
end
end
Loading

0 comments on commit 68d202e

Please sign in to comment.