FEATURE: allows to bulk invite into events (#54)

FEATURE: allows to bulk invite into events (#54)

diff --git a/app/controllers/discourse_post_event/events_controller.rb b/app/controllers/discourse_post_event/events_controller.rb
index cd3a4b9..9c652cc 100644
--- a/app/controllers/discourse_post_event/events_controller.rb
+++ b/app/controllers/discourse_post_event/events_controller.rb
@@ -8,10 +8,10 @@ module DiscoursePostEvent
       @events = DiscoursePostEvent::EventFinder.search(current_user, filtered_events_params)
 
       respond_to do |format|
-        format.ics {
+        format.ics do
           filename = "events-#{@events.map(&:id).join('-')}"
           response.headers['Content-Disposition'] = "attachment; filename=\"#{filename}.#{request.format.symbol}\""
-        }
+        end
 
         format.json do
           render json: ActiveModel::ArraySerializer.new(
@@ -62,25 +62,54 @@ module DiscoursePostEvent
     end
 
     def create
-      event = Event.new(event_params)
+      event = Event.new(id: event_params[:id])
       guardian.ensure_can_edit!(event.post)
       guardian.ensure_can_create_discourse_post_event!
-
-      case event_params[:status].to_i
-      when Event.statuses[:private]
-        raw_invitees = Array(event_params[:raw_invitees])
-        event.update!(raw_invitees: raw_invitees)
-        event.fill_invitees!
-        event.notify_invitees!
-      when Event.statuses[:public], Event.statuses[:standalone]
-        event.update!(event_params.merge(raw_invitees: []))
-      end
-
+      event.update_with_params!(event_params)
       event.publish_update!
       serializer = EventSerializer.new(event, scope: guardian)
       render_json_dump(serializer)
     end
 
+    def bulk_invite
+      require 'csv'
+
+      event = Event.find(params[:id])
+      guardian.ensure_can_edit!(event.post)
+      guardian.ensure_can_create_discourse_post_event!
+
+      file = params[:file] || (params[:files] || []).first
+      raise Discourse::InvalidParameters.new(:file) if file.blank?
+
+      hijack do
+        begin
+          count = 0
+          invitees = []
+
+          CSV.foreach(file.tempfile) do |row|
+            if row[0].present?
+              count += 1
+              invitees << { identifier: row[0], attendance: row[1] || 'going' }
+            end
+          end
+
+          if invitees.present?
+            Jobs.enqueue(
+              :discourse_post_event_bulk_invite,
+              event_id: event.id,
+              invitees: invitees,
+              current_user_id: current_user.id
+            )
+            render json: success_json
+          else
+            render json: failed_json.merge(errors: [I18n.t('discourse_post_event.errors.bulk_invite.error')]), status: 422
+          end
+        rescue
+          render json: failed_json.merge(errors: [I18n.t('discourse_post_event.errors.bulk_invite.error')]), status: 422
+        end
+      end
+    end
+
     private
 
     def event_params
diff --git a/app/controllers/discourse_post_event/invitees_controller.rb b/app/controllers/discourse_post_event/invitees_controller.rb
index 1335802..d4c66ce 100644
--- a/app/controllers/discourse_post_event/invitees_controller.rb
+++ b/app/controllers/discourse_post_event/invitees_controller.rb
@@ -22,19 +22,20 @@ module DiscoursePostEvent
     end
 
     def update
-      invitee = Invitee.update_attendance!(
-        params[:id],
-        invitee_params.tap { |ip| ip.require(:status) },
-        guardian
-      )
+      invitee = Invitee.find(params[:id])
+      guardian.ensure_can_act_on_invitee!(invitee)
+      invitee.update_attendance!(invitee_params[:status])
       render json: InviteeSerializer.new(invitee)
     end
 
     def create
+      event = Event.find(invitee_params[:post_id])
+      guardian.ensure_can_see!(event.post)
+
       invitee = Invitee.create_attendance!(
         current_user.id,
-        invitee_params.tap { |ip| ip.require([:status, :post_id]) },
-        guardian
+        invitee_params[:post_id],
+        invitee_params[:status]
       )
       render json: InviteeSerializer.new(invitee)
     end
diff --git a/app/models/discourse_post_event/event.rb b/app/models/discourse_post_event/event.rb
index c0e11fc..58fca7a 100644
--- a/app/models/discourse_post_event/event.rb
+++ b/app/models/discourse_post_event/event.rb
@@ -106,6 +106,18 @@ module DiscoursePostEvent
       @statuses ||= Enum.new(standalone: 0, public: 1, private: 2)
     end
 
+    def public?
+      status == Event.statuses[:public]
+    end
+
+    def standalone?
+      status == Event.statuses[:standalone]
+    end
+
+    def private?
+      status == Event.statuses[:private]
+    end
+
     def most_likely_going(limit = SiteSetting.displayed_invitees_limit)
       self.invitees
         .order([:status, :user_id])
@@ -180,7 +192,7 @@ module DiscoursePostEvent
         self.update!(params.merge(raw_invitees: raw_invitees))
         self.enforce_raw_invitees!
       when Event.statuses[:public]
-        self.update!(params.merge(raw_invitees: []))
+        self.update!(params.merge(raw_invitees: [:trust_level_0]))
       when Event.statuses[:standalone]
         self.update!(params.merge(raw_invitees: []))
         self.invitees.destroy_all
diff --git a/app/models/discourse_post_event/invitee.rb b/app/models/discourse_post_event/invitee.rb
index 684c66f..cf6162a 100644
--- a/app/models/discourse_post_event/invitee.rb
+++ b/app/models/discourse_post_event/invitee.rb
@@ -2,6 +2,8 @@
 
 module DiscoursePostEvent
   class Invitee < ActiveRecord::Base
+    UNKNOWN_ATTENDANCE = 'unknown'
+
     self.table_name = 'discourse_post_event_invitees'
 
     belongs_to :event, foreign_key: :post_id
@@ -21,30 +23,22 @@ module DiscoursePostEvent
       @statuses ||= Enum.new(going: 0, interested: 1, not_going: 2)
     end
 
-    def self.create_attendance!(user_id, params, guardian)
-      event = Event.find(params[:post_id])
-      guardian.ensure_can_see!(event.post)
+    def self.create_attendance!(user_id, post_id, status)
       invitee = Invitee.create!(
-        status: Invitee.statuses[params[:status].to_sym],
-        post_id: params[:post_id],
+        status: Invitee.statuses[status.to_sym],
+        post_id: post_id,
         user_id: user_id,
       )
       invitee.event.publish_update!
       invitee
     end
 
-    def update_attendance!(params, guardian)
-      guardian.ensure_can_act_on_invitee!(self)
-      self.update(status: Invitee.statuses[params[:status].to_sym])
+    def update_attendance!(status)
+      self.update(status: Invitee.statuses[status.to_sym])
       self.event.publish_update!
       self
     end
 
-    def self.update_attendance!(invitee_id, params, guardian)
-      invitee = Invitee.find(invitee_id)
-      invitee.update_attendance!(params, guardian)
-    end
-
     def self.extract_uniq_usernames(groups)
       User.where(
         id: GroupUser.where(
diff --git a/assets/javascripts/discourse/components/bulk-invite-sample-csv-file.js.es6 b/assets/javascripts/discourse/components/bulk-invite-sample-csv-file.js.es6
new file mode 100644
index 0000000..06c20db
--- /dev/null
+++ b/assets/javascripts/discourse/components/bulk-invite-sample-csv-file.js.es6
@@ -0,0 +1,27 @@
+import Component from "@ember/component";
+import { action } from "@ember/object";
+
+export default Component.extend({
+  @action
+  downloadSampleCsv() {
+    const sampleData = [
+      ["my_awesome_group", "going"],
+      ["lucy", "interested"],
+      ["mark", "not_going"],
+      ["sam", "unknown"]
+    ];
+
+    let csv = "";
+    sampleData.forEach(row => {
+      csv += row.join(",");
+      csv += "\n";
+    });
+
+    const btn = document.createElement("a");
+    btn.href = `data:text/csv;charset=utf-8,${encodeURI(csv)}`;
+    btn.target = "_blank";
+    btn.rel = "noopener noreferrer";
+    btn.download = "bulk-invite-sample.csv";
+    btn.click();
+  }
+});

[... diff too long, it was truncated ...]

GitHub sha: 1451a65f

This commit appears in #54 which was merged by jjaffeux.