FEATURE: adds support for custom fields on event

FEATURE: adds support for custom fields on event

Custom fields are create in the site settings of the event plugin. Once at least one custom field is created, a new form will appear in each event UI. These custom fields are passed when DIscourseEvent triggers of the plugin are called, allowing you to pass custom data of the even to other plugins.

diff --git a/README.md b/README.md
index fb13057..c4f37c5 100644
--- a/README.md
+++ b/README.md
@@ -8,5 +8,13 @@ Topic discussing the plugin itself can be found here: [https://meta.discourse.or
 
 #### Plugins
 
+##### Events
+
+- `discourse_post_event_event_will_start` this DiscourseEvent will be triggered one hour before an event starts
 - `discourse_post_event_event_started` this DiscourseEvent will be triggered when an event starts
 - `discourse_post_event_event_ended` this DiscourseEvent will be triggered when an event ends
+
+#### Custom Fields
+
+Custom fields can be set in plugin settings. Once added a new form will appear on event UI.
+These custom fields are available when a plugin event is triggered.
diff --git a/app/controllers/discourse_post_event/events_controller.rb b/app/controllers/discourse_post_event/events_controller.rb
index 9c652cc..ef2e90e 100644
--- a/app/controllers/discourse_post_event/events_controller.rb
+++ b/app/controllers/discourse_post_event/events_controller.rb
@@ -113,6 +113,8 @@ module DiscoursePostEvent
     private
 
     def event_params
+      allowed_custom_fields = SiteSetting.discourse_post_event_allowed_custom_fields.split('|')
+
       params
         .require(:event)
         .permit(
@@ -122,6 +124,7 @@ module DiscoursePostEvent
           :ends_at,
           :status,
           :url,
+          custom_fields: allowed_custom_fields,
           raw_invitees: []
         )
     end
diff --git a/app/models/discourse_post_event/event.rb b/app/models/discourse_post_event/event.rb
index f9613f4..3c2aac7 100644
--- a/app/models/discourse_post_event/event.rb
+++ b/app/models/discourse_post_event/event.rb
@@ -38,10 +38,15 @@ module DiscoursePostEvent
     def setup_starts_at_handler
       if !transaction_include_any_action?([:create])
         Jobs.cancel_scheduled_job(:discourse_post_event_event_started, event_id: self.id)
+        Jobs.cancel_scheduled_job(:discourse_post_event_event_will_start, event_id: self.id)
       end
 
       if self.starts_at > Time.now
         Jobs.enqueue_at(self.starts_at, :discourse_post_event_event_started, event_id: self.id)
+
+        if self.starts_at - 1.hour > Time.now
+          Jobs.enqueue_at(self.starts_at - 1.hour, :discourse_post_event_event_will_start, event_id: self.id)
+        end
       end
     end
 
@@ -102,6 +107,16 @@ module DiscoursePostEvent
       end
     end
 
+    validate :allowed_custom_fields
+    def allowed_custom_fields
+      allowed_custom_fields = SiteSetting.discourse_post_event_allowed_custom_fields.split('|')
+      self.custom_fields.each do |key, value|
+        if !allowed_custom_fields.include?(key)
+          errors.add(:base, I18n.t("discourse_post_event.errors.models.event.custom_field_is_invalid", field: key))
+        end
+      end
+    end
+
     def create_invitees(attrs)
       timestamp = Time.now
       attrs.map! do |attr|
@@ -219,6 +234,8 @@ module DiscoursePostEvent
     end
 
     def update_with_params!(params)
+      params[:custom_fields] = (params[:custom_fields] || {}).reject { |_, value| value.blank? }
+
       case params[:status].to_i
       when Event.statuses[:private]
         raw_invitees = Array(params[:raw_invitees])
diff --git a/app/serializers/discourse_post_event/event_serializer.rb b/app/serializers/discourse_post_event/event_serializer.rb
index 3dd9253..531e42a 100644
--- a/app/serializers/discourse_post_event/event_serializer.rb
+++ b/app/serializers/discourse_post_event/event_serializer.rb
@@ -18,6 +18,7 @@ module DiscoursePostEvent
     attributes :is_expired
     attributes :should_display_invitees
     attributes :url
+    attributes :custom_fields
 
     def can_act_on_discourse_post_event
       scope.can_act_on_discourse_post_event?(object)
diff --git a/assets/javascripts/discourse/controllers/discourse-post-event-builder.js.es6 b/assets/javascripts/discourse/controllers/discourse-post-event-builder.js.es6
index 7351f9d..12d968d 100644
--- a/assets/javascripts/discourse/controllers/discourse-post-event-builder.js.es6
+++ b/assets/javascripts/discourse/controllers/discourse-post-event-builder.js.es6
@@ -1,3 +1,4 @@
+import { set } from "@ember/object";
 import TextLib from "discourse/lib/text";
 import Group from "discourse/models/group";
 import ModalFunctionality from "discourse/mixins/modal-functionality";
@@ -5,8 +6,14 @@ import Controller from "@ember/controller";
 import { action, computed } from "@ember/object";
 import { equal } from "@ember/object/computed";
 import { extractError } from "discourse/lib/ajax-error";
+import { Promise } from "rsvp";
 
 export default Controller.extend(ModalFunctionality, {
+  init() {
+    this._super(...arguments);
+    this._dirtyCustomFields = false;
+  },
+
   modalTitle: computed("model.eventModel.isNew", {
     get() {
       return this.model.eventModel.isNew
@@ -15,6 +22,15 @@ export default Controller.extend(ModalFunctionality, {
     }
   }),
 
+  allowedCustomFields: computed(
+    "siteSettings.discourse_post_event_allowed_custom_fields",
+    function() {
+      return this.siteSettings.discourse_post_event_allowed_custom_fields
+        .split("|")
+        .filter(Boolean);
+    }
+  ),
+
   groupFinder(term) {
     return Group.findAll({ term, ignore_automatic: true });
   },
@@ -22,6 +38,13 @@ export default Controller.extend(ModalFunctionality, {
   allowsInvitees: equal("model.eventModel.status", "private"),
 
   @action
+  onChangeCustomField(field, event) {
+    this._dirtyCustomFields = true;
+    const value = event.target.value;
+    set(this.model.eventModel.custom_fields, field, value);
+  },
+
+  @action
   setRawInvitees(_, newInvitees) {
     this.set("model.eventModel.raw_invitees", newInvitees);
   },
@@ -106,25 +129,45 @@ export default Controller.extend(ModalFunctionality, {
 
   @action
   updateEvent() {
-    const eventParams = this._buildEventParams();
     return this.store.find("post", this.model.eventModel.id).then(post => {
-      const raw = post.raw;
-      const newRaw = this._replaceRawEvent(eventParams, raw);
-
-      if (newRaw) {
-        const props = {
-          raw: newRaw,
-          edit_reason: I18n.t("discourse_post_event.edit_reason")
-        };
-
-        return TextLib.cookAsync(newRaw).then(cooked => {
-          props.cooked = cooked.string;
-          return post
-            .save(props)
-            .catch(e => this.flash(extractError(e), "error"))
-            .then(result => result && this.send("closeModal"));
-        });
+      const promises = [];
+      if (this._dirtyCustomFields) {
+        // custom_fields are not stored on the raw and are updated separately
+        const customFields = this.model.eventModel.getProperties(
+          "custom_fields"
+        );
+        promises.push(
+          this.model.eventModel
+            .update(customFields)
+            .finally(() => (this._dirtyCustomFields = false))
+        );
       }
+
+      const updateRawPromise = new Promise(resolve => {
+        const raw = post.raw;
+        const eventParams = this._buildEventParams();
+        const newRaw = this._replaceRawEvent(eventParams, raw);
+
+        if (newRaw) {
+          const props = {
+            raw: newRaw,
+            edit_reason: I18n.t("discourse_post_event.edit_reason")
+          };
+
+          return TextLib.cookAsync(newRaw).then(cooked => {
+            props.cooked = cooked.string;
+            return post
+              .save(props)
+              .catch(e => this.flash(extractError(e), "error"))
+              .then(result => result && this.send("closeModal"))
+              .finally(resolve);
+          });
+        } else {
+          resolve();
+        }
+      });
+
+      return Promise.all(promises.concat(updateRawPromise));
     });
   },
 
diff --git a/assets/javascripts/discourse/templates/modal/discourse-post-event-builder.hbs b/assets/javascripts/discourse/templates/modal/discourse-post-event-builder.hbs
index 80af407..cfb8cd7 100644

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

GitHub sha: 6754bdfc