FEATURE: allows to set reminder
Note that reminders; as custom fields, are only editable on created event and not when creating it.
This commit is contained in:
parent
b359e9a23f
commit
da91f7417d
|
@ -36,7 +36,7 @@ module DiscoursePostEvent
|
|||
end
|
||||
|
||||
def show
|
||||
event = Event.find(params[:id])
|
||||
event = Event.includes(:reminders).find(params[:id])
|
||||
guardian.ensure_can_see!(event.post)
|
||||
serializer = EventSerializer.new(event, scope: guardian)
|
||||
render_json_dump(serializer)
|
||||
|
@ -56,6 +56,7 @@ module DiscoursePostEvent
|
|||
guardian.ensure_can_edit!(event.post)
|
||||
guardian.ensure_can_act_on_discourse_post_event!(event)
|
||||
event.update_with_params!(event_params)
|
||||
event.update_reminders!(event_reminders_params)
|
||||
serializer = EventSerializer.new(event, scope: guardian)
|
||||
render_json_dump(serializer)
|
||||
end
|
||||
|
@ -131,6 +132,10 @@ module DiscoursePostEvent
|
|||
|
||||
private
|
||||
|
||||
def event_reminders_params
|
||||
Array(params.require(:event).permit(reminders: [:id, :value, :unit])[:reminders])
|
||||
end
|
||||
|
||||
def event_params
|
||||
allowed_custom_fields = SiteSetting.discourse_post_event_allowed_custom_fields.split('|')
|
||||
|
||||
|
@ -144,7 +149,7 @@ module DiscoursePostEvent
|
|||
:status,
|
||||
:url,
|
||||
custom_fields: allowed_custom_fields,
|
||||
raw_invitees: []
|
||||
raw_invitees: [],
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module DiscoursePostEvent
|
||||
class RemindersController < DiscoursePostEventController
|
||||
def destroy
|
||||
event = Event.find_by(id: params[:post_id])
|
||||
reminder = event.reminders.find_by(id: params[:id])
|
||||
guardian.ensure_can_act_on_discourse_post_event!(event)
|
||||
Jobs.cancel_scheduled_job(:discourse_post_event_send_reminder, reminder_id: reminder.id)
|
||||
reminder.destroy!
|
||||
render json: success_json
|
||||
end
|
||||
end
|
||||
end
|
|
@ -34,34 +34,41 @@ module DiscoursePostEvent
|
|||
end
|
||||
end
|
||||
|
||||
after_commit :setup_starts_at_handler, on: [:create, :update]
|
||||
def setup_starts_at_handler
|
||||
if !transaction_include_any_action?([:create])
|
||||
after_commit :setup_handlers, on: [:create, :update]
|
||||
def setup_handlers
|
||||
starts_at_changes = saved_change_to_starts_at
|
||||
if starts_at_changes
|
||||
new_starts_at = starts_at_changes[1]
|
||||
|
||||
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)
|
||||
|
||||
if new_starts_at > Time.now
|
||||
Jobs.enqueue_at(new_starts_at, :discourse_post_event_event_started, event_id: self.id)
|
||||
|
||||
will_start_at = new_starts_at - 1.hour
|
||||
if will_start_at > Time.now
|
||||
Jobs.enqueue_at(will_start_at, :discourse_post_event_event_will_start, event_id: self.id)
|
||||
end
|
||||
end
|
||||
|
||||
self.refresh_reminders!
|
||||
end
|
||||
|
||||
if self.starts_at > Time.now
|
||||
Jobs.enqueue_at(self.starts_at, :discourse_post_event_event_started, event_id: self.id)
|
||||
ends_at_changes = saved_change_to_ends_at
|
||||
if ends_at_changes
|
||||
new_ends_at = ends_at_changes[1]
|
||||
|
||||
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)
|
||||
Jobs.cancel_scheduled_job(:discourse_post_event_event_ended, event_id: self.id)
|
||||
|
||||
if new_ends_at && new_ends_at > Time.now
|
||||
Jobs.enqueue_at(new_ends_at, :discourse_post_event_event_ended, event_id: self.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
after_commit :setup_ends_at_handler, on: [:create, :update]
|
||||
def setup_ends_at_handler
|
||||
if !transaction_include_any_action?([:create])
|
||||
Jobs.cancel_scheduled_job(:discourse_post_event_event_ended, event_id: self.id)
|
||||
end
|
||||
|
||||
if self.ends_at && self.ends_at > Time.now
|
||||
Jobs.enqueue_at(self.ends_at, :discourse_post_event_event_ended, event_id: self.id)
|
||||
end
|
||||
end
|
||||
|
||||
has_many :invitees, foreign_key: :post_id, dependent: :delete_all
|
||||
has_many :reminders, foreign_key: :post_id, dependent: :delete_all
|
||||
belongs_to :post, foreign_key: :id
|
||||
|
||||
scope :visible, -> { where(deleted_at: nil) }
|
||||
|
@ -219,14 +226,12 @@ module DiscoursePostEvent
|
|||
|
||||
if events.present?
|
||||
event_params = events.first
|
||||
|
||||
event = post.event || DiscoursePostEvent::Event.new(id: post.id)
|
||||
|
||||
params = {
|
||||
name: event_params[:name],
|
||||
starts_at: event_params[:start] || event.starts_at,
|
||||
ends_at: event_params[:end],
|
||||
url: event_params[:"url"],
|
||||
url: event_params[:url],
|
||||
status: event_params[:status].present? ? Event.statuses[event_params[:status].to_sym] : event.status,
|
||||
raw_invitees: event_params[:"allowed-groups"] ? event_params[:"allowed-groups"].split(',') : nil
|
||||
}
|
||||
|
@ -254,5 +259,20 @@ module DiscoursePostEvent
|
|||
|
||||
self.publish_update!
|
||||
end
|
||||
|
||||
def refresh_reminders!
|
||||
self.reminders.each(&:refresh!)
|
||||
end
|
||||
|
||||
def update_reminders!(reminders)
|
||||
reminders.each do |reminder|
|
||||
if reminder[:id]
|
||||
model = Reminder.find(reminder[:id])
|
||||
model.update!(value: reminder[:value], unit: reminder[:unit])
|
||||
else
|
||||
model = Reminder.create!(value: reminder[:value], unit: reminder[:unit], post_id: self.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module DiscoursePostEvent
|
||||
class Reminder < ActiveRecord::Base
|
||||
self.table_name = 'discourse_post_event_reminders'
|
||||
|
||||
belongs_to :event, foreign_key: :post_id
|
||||
|
||||
def self.means
|
||||
@means ||= Enum.new(notification: 0)
|
||||
end
|
||||
|
||||
after_commit :refresh!, on: [:create, :update]
|
||||
def refresh!
|
||||
if transaction_include_any_action?([:update])
|
||||
Jobs.cancel_scheduled_job(:discourse_post_event_send_reminder, reminder_id: self.id)
|
||||
end
|
||||
|
||||
enqueue_at = self.event.starts_at - self.value.send(self.unit)
|
||||
if enqueue_at > Time.now
|
||||
Jobs.enqueue_at(enqueue_at, :discourse_post_event_send_reminder, reminder_id: self.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -22,6 +22,7 @@ module DiscoursePostEvent
|
|||
attributes :is_public
|
||||
attributes :is_private
|
||||
attributes :is_standalone
|
||||
attributes :reminders
|
||||
|
||||
def can_act_on_discourse_post_event
|
||||
scope.can_act_on_discourse_post_event?(object)
|
||||
|
|
|
@ -1,68 +1,6 @@
|
|||
import { Result } from "discourse/adapters/rest";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import DiscoursePostEventAdapter from "./discourse-post-event-adapter";
|
||||
import { underscore } from "@ember/string";
|
||||
|
||||
export default DiscoursePostEventAdapter.extend({
|
||||
// TODO: destroy/update/create should be improved in core to allow for nested models
|
||||
destroyRecord(store, type, record) {
|
||||
return ajax(
|
||||
this.pathFor(store, type, {
|
||||
post_id: record.post_id,
|
||||
invitee_id: record.id
|
||||
}),
|
||||
{
|
||||
type: "DELETE"
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
update(store, type, id, attrs) {
|
||||
const data = {};
|
||||
const typeField = underscore(this.apiNameFor(type));
|
||||
data[typeField] = attrs;
|
||||
|
||||
return ajax(
|
||||
this.pathFor(store, type, { invitee_id: id, post_id: attrs.post_id }),
|
||||
this.getPayload("PUT", data)
|
||||
).then(function(json) {
|
||||
return new Result(json[typeField], json);
|
||||
});
|
||||
},
|
||||
|
||||
createRecord(store, type, attrs) {
|
||||
const data = {};
|
||||
const typeField = underscore(this.apiNameFor(type));
|
||||
data[typeField] = attrs;
|
||||
return ajax(
|
||||
this.pathFor(store, type, attrs),
|
||||
this.getPayload("POST", data)
|
||||
).then(function(json) {
|
||||
return new Result(json[typeField], json);
|
||||
});
|
||||
},
|
||||
|
||||
pathFor(store, type, findArgs) {
|
||||
const post_id = findArgs["post_id"];
|
||||
delete findArgs["post_id"];
|
||||
|
||||
const invitee_id = findArgs["invitee_id"];
|
||||
delete findArgs["invitee_id"];
|
||||
|
||||
let path =
|
||||
this.basePath(store, type, {}) +
|
||||
"events/" +
|
||||
post_id +
|
||||
"/" +
|
||||
underscore(store.pluralize(this.apiNameFor()));
|
||||
|
||||
if (invitee_id) {
|
||||
path += `/${invitee_id}`;
|
||||
}
|
||||
|
||||
return this.appendQueryParams(path, findArgs);
|
||||
},
|
||||
import DiscoursePostEventNestedAdapter from "./discourse-post-event-nested-adapter";
|
||||
|
||||
export default DiscoursePostEventNestedAdapter.extend({
|
||||
apiNameFor() {
|
||||
return "invitee";
|
||||
}
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
import DiscoursePostEventAdapter from "./discourse-post-event-adapter";
|
||||
import { underscore } from "@ember/string";
|
||||
import { Result } from "discourse/adapters/rest";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
|
||||
export default DiscoursePostEventAdapter.extend({
|
||||
// TODO: destroy/update/create should be improved in core to allow for nested models
|
||||
destroyRecord(store, type, record) {
|
||||
return ajax(
|
||||
this.pathFor(store, type, {
|
||||
post_id: record.post_id,
|
||||
id: record.id
|
||||
}),
|
||||
{
|
||||
type: "DELETE"
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
update(store, type, id, attrs) {
|
||||
const data = {};
|
||||
const typeField = underscore(this.apiNameFor(type));
|
||||
data[typeField] = attrs;
|
||||
|
||||
return ajax(
|
||||
this.pathFor(store, type, { id, post_id: attrs.post_id }),
|
||||
this.getPayload("PUT", data)
|
||||
).then(function(json) {
|
||||
return new Result(json[typeField], json);
|
||||
});
|
||||
},
|
||||
|
||||
createRecord(store, type, attrs) {
|
||||
const data = {};
|
||||
const typeField = underscore(this.apiNameFor(type));
|
||||
data[typeField] = attrs;
|
||||
return ajax(
|
||||
this.pathFor(store, type, attrs),
|
||||
this.getPayload("POST", data)
|
||||
).then(function(json) {
|
||||
return new Result(json[typeField], json);
|
||||
});
|
||||
},
|
||||
|
||||
pathFor(store, type, findArgs) {
|
||||
const post_id = findArgs["post_id"];
|
||||
delete findArgs["post_id"];
|
||||
|
||||
const id = findArgs["id"];
|
||||
delete findArgs["id"];
|
||||
|
||||
let path =
|
||||
this.basePath(store, type, {}) +
|
||||
"events/" +
|
||||
post_id +
|
||||
"/" +
|
||||
underscore(store.pluralize(this.apiNameFor()));
|
||||
|
||||
if (id) {
|
||||
path += `/${id}`;
|
||||
}
|
||||
|
||||
return this.appendQueryParams(path, findArgs);
|
||||
}
|
||||
});
|
|
@ -0,0 +1,7 @@
|
|||
import DiscoursePostEventNestedAdapter from "./discourse-post-event-nested-adapter";
|
||||
|
||||
export default DiscoursePostEventNestedAdapter.extend({
|
||||
apiNameFor() {
|
||||
return "reminder";
|
||||
}
|
||||
});
|
|
@ -1,19 +1,24 @@
|
|||
import { set } from "@ember/object";
|
||||
import TextLib from "discourse/lib/text";
|
||||
import Group from "discourse/models/group";
|
||||
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||
import Controller from "@ember/controller";
|
||||
import { action, computed } from "@ember/object";
|
||||
import { equal } from "@ember/object/computed";
|
||||
import { set, action, computed } from "@ember/object";
|
||||
import { equal, gte } from "@ember/object/computed";
|
||||
import { extractError } from "discourse/lib/ajax-error";
|
||||
import { Promise } from "rsvp";
|
||||
|
||||
import { buildParams, replaceRaw } from "../../lib/raw-event-helper";
|
||||
|
||||
const DEFAULT_REMINDER = { value: 15, unit: "minutes", type: "notification" };
|
||||
|
||||
export default Controller.extend(ModalFunctionality, {
|
||||
reminders: null,
|
||||
isLoadingReminders: false,
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this._dirtyCustomFields = false;
|
||||
|
||||
this.set("reminderUnits", ["minutes", "hours", "days", "weeks"]);
|
||||
},
|
||||
|
||||
modalTitle: computed("model.eventModel.isNew", {
|
||||
|
@ -39,9 +44,10 @@ export default Controller.extend(ModalFunctionality, {
|
|||
|
||||
allowsInvitees: equal("model.eventModel.status", "private"),
|
||||
|
||||
addReminderDisabled: gte("reminders.length", 5),
|
||||
|
||||
@action
|
||||
onChangeCustomField(field, event) {
|
||||
this._dirtyCustomFields = true;
|
||||
const value = event.target.value;
|
||||
set(this.model.eventModel.custom_fields, field, value);
|
||||
},
|
||||
|
@ -51,6 +57,30 @@ export default Controller.extend(ModalFunctionality, {
|
|||
this.set("model.eventModel.raw_invitees", newInvitees);
|
||||
},
|
||||
|
||||
@action
|
||||
removeReminder(reminder) {
|
||||
this.model.eventModel.reminders.removeObject(reminder);
|
||||
|
||||
if (reminder.id) {
|
||||
this.set("isLoadingReminders", true);
|
||||
|
||||
this.store
|
||||
.createRecord("discourse-post-event-reminder", {
|
||||
id: reminder.id,
|
||||
post_id: this.model.eventModel.id
|
||||
})
|
||||
.destroyRecord()
|
||||
.finally(() => this.set("isLoadingReminders", false));
|
||||
}
|
||||
},
|
||||
|
||||
@action
|
||||
addReminder() {
|
||||
this.model.eventModel.reminders.pushObject(
|
||||
Object.assign({}, DEFAULT_REMINDER)
|
||||
);
|
||||
},
|
||||
|
||||
startsAt: computed("model.eventModel.starts_at", {
|
||||
get() {
|
||||
return this.model.eventModel.starts_at
|
||||
|
@ -137,17 +167,13 @@ export default Controller.extend(ModalFunctionality, {
|
|||
updateEvent() {
|
||||
return this.store.find("post", this.model.eventModel.id).then(post => {
|
||||
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))
|
||||
);
|
||||
}
|
||||
|
||||
// custom_fields are not stored on the raw and are updated separately
|
||||
const data = this.model.eventModel.getProperties(
|
||||
"custom_fields",
|
||||
"reminders"
|
||||
);
|
||||
promises.push(this.model.eventModel.update(data));
|
||||
|
||||
const updateRawPromise = new Promise(resolve => {
|
||||
const raw = post.raw;
|
||||
|
|
|
@ -19,9 +19,7 @@ export default Controller.extend(ModalFunctionality, {
|
|||
|
||||
@action
|
||||
removeInvitee(invitee) {
|
||||
invitee
|
||||
.destroyRecord({ parent: this.model })
|
||||
.then(() => this._fetchInvitees());
|
||||
invitee.destroyRecord().then(() => this._fetchInvitees());
|
||||
},
|
||||
|
||||
_fetchInvitees(filter) {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import RestModel from "discourse/models/rest";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
|
||||
const ATTRIBUTES = {
|
||||
id: {},
|
||||
|
@ -27,6 +28,15 @@ const Event = RestModel.extend({
|
|||
this.__type = "discourse-post-event-event";
|
||||
},
|
||||
|
||||
update(data) {
|
||||
return ajax(`/discourse-post-event/events/${this.id}.json`, {
|
||||
type: "PUT",
|
||||
dataType: "json",
|
||||
contentType: "application/json",
|
||||
data: JSON.stringify({ event: data })
|
||||
});
|
||||
},
|
||||
|
||||
updateProperties() {
|
||||
const attributesKeys = Object.keys(ATTRIBUTES);
|
||||
return this.getProperties(attributesKeys);
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
import RestModel from "discourse/models/rest";
|
||||
|
||||
export default RestModel.extend({
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
|
||||
this.__type = "discourse-post-event-reminder";
|
||||
}
|
||||
});
|
|
@ -77,6 +77,48 @@
|
|||
}}
|
||||
{{/event-field}}
|
||||
|
||||
{{#if model.eventModel.reminders}}
|
||||
{{#event-field class="reminders" label="discourse_post_event.builder_modal.reminders.label"}}
|
||||
<div class="reminders-list">
|
||||
{{#each model.eventModel.reminders as |reminder|}}
|
||||
<div class="reminder-item">
|
||||
{{input
|
||||
class="reminder-value"
|
||||
value=(readonly reminder.value)
|
||||
placeholderKey="discourse_post_event.builder_modal.name.placeholder"
|
||||
input=(action (mut reminder.value) value="target.value")
|
||||
}}
|
||||
|
||||
{{combo-box
|
||||
class="reminder-unit"
|
||||
value=reminder.unit
|
||||
nameProperty=null
|
||||
valueProperty=null
|
||||
content=reminderUnits
|
||||
onChange=(action (mut reminder.unit))
|
||||
}}
|
||||
|
||||
{{d-button
|
||||
class="remove-reminder"
|
||||
icon="times"
|
||||
action=(action "removeReminder" reminder)
|
||||
disabled=isLoadingReminders
|
||||
}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
|
||||
{{d-button
|
||||
class="add-reminder"
|
||||
disabled=addReminderDisabled
|
||||
icon="plus"
|
||||
label="discourse_post_event.builder_modal.add_reminder"
|
||||
action=(action "addReminder")
|
||||
}}
|
||||
{{/event-field}}
|
||||
{{/if}}
|
||||
|
||||
{{#if model.eventModel.custom_fields}}
|
||||
{{#if allowedCustomFields.length}}
|
||||
{{#event-field label="discourse_post_event.builder_modal.custom_fields.label"}}
|
||||
<p>{{i18n "discourse_post_event.builder_modal.custom_fields.description"}}</p>
|
||||
|
@ -91,6 +133,7 @@
|
|||
{{/each}}
|
||||
{{/event-field}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</form>
|
||||
{{/conditional-loading-section}}
|
||||
{{/d-modal-body}}
|
||||
|
|
|
@ -218,6 +218,16 @@ function initializeDiscoursePostEventDecorator(api) {
|
|||
"calendar-day"
|
||||
);
|
||||
|
||||
api.replaceIcon(
|
||||
"notification.discourse_post_event.notifications.before_event_reminder",
|
||||
"calendar-day"
|
||||
);
|
||||
|
||||
api.replaceIcon(
|
||||
"notification.discourse_post_event.notifications.after_event_reminder",
|
||||
"calendar-day"
|
||||
);
|
||||
|
||||
api.modifyClass("controller:topic", {
|
||||
subscribe() {
|
||||
this._super(...arguments);
|
||||
|
|
|
@ -12,6 +12,10 @@
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.name {
|
||||
font-size: $font-down-1;
|
||||
}
|
||||
|
||||
&.from {
|
||||
margin-right: 2.65em;
|
||||
}
|
||||
|
@ -43,6 +47,16 @@
|
|||
min-height: 200px;
|
||||
|
||||
.d-date-time-input-range {
|
||||
.d-time-input {
|
||||
.selected-name {
|
||||
border: 0;
|
||||
|
||||
.name {
|
||||
font-size: $font-down-1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.to.d-date-time-input {
|
||||
.pika-single.is-bound {
|
||||
left: -2px !important;
|
||||
|
@ -90,7 +104,7 @@
|
|||
|
||||
.custom-field-label {
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.5em;
|
||||
margin: 0.5em 0 0.25em 0;
|
||||
}
|
||||
|
||||
.custom-field-input {
|
||||
|
@ -132,4 +146,33 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.event-field.reminders {
|
||||
display: flex;
|
||||
|
||||
.reminders-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 1em;
|
||||
|
||||
.reminder-item {
|
||||
display: flex;
|
||||
flex: 1 0 auto;
|
||||
padding: 0.25em 0;
|
||||
|
||||
.reminder-value {
|
||||
width: 80px;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.remove-reminder {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-reminder {
|
||||
align-self: flex-start;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -274,6 +274,8 @@ en:
|
|||
notifications:
|
||||
invite_user_notification: "%{username} has invited you to %{description}"
|
||||
invite_user_auto_notification: "%{username} has automatically set your attendance and invited you to %{description}"
|
||||
before_event_reminder: "An event is about to start %{description}"
|
||||
after_event_reminder: "An event has started %{description}"
|
||||
preview:
|
||||
more_than_one_event: You can’t have more than one event.
|
||||
edit_reason: Event updated
|
||||
|
@ -338,6 +340,10 @@ en:
|
|||
create: Create
|
||||
update: Save
|
||||
attach: Create event
|
||||
add_reminder: Add reminder
|
||||
reminders:
|
||||
label: Reminders
|
||||
description: A negative value will be sent after event started to `going` users who didn't visit the event since it started.
|
||||
url:
|
||||
label: URL
|
||||
placeholder: Optional
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateRemindersTable < ActiveRecord::Migration[6.0]
|
||||
def change
|
||||
create_table :discourse_post_event_reminders do |t|
|
||||
t.integer :post_id, null: false
|
||||
t.integer :value, null: false, default: 0
|
||||
t.integer :mean, null: false, default: 0
|
||||
t.string :unit, null: false, default: 'minutes'
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,38 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Jobs
|
||||
class DiscoursePostEventSendReminder < ::Jobs::Base
|
||||
sidekiq_options retry: false
|
||||
|
||||
def execute(args)
|
||||
raise Discourse::InvalidParameters.new(:reminder_id) if args[:reminder_id].blank?
|
||||
|
||||
reminder = DiscoursePostEvent::Reminder.includes(event: [post: [:topic], invitees: [:user]]).find(args[:reminder_id])
|
||||
event = reminder.event
|
||||
invitees = event.invitees
|
||||
|
||||
unread_notified_user_ids = Notification.where(
|
||||
read: false,
|
||||
notification_type: Notification.types[:custom],
|
||||
topic_id: event.post.topic_id,
|
||||
post_number: 1
|
||||
).pluck(:user_id)
|
||||
|
||||
invitees
|
||||
.where(status: DiscoursePostEvent::Invitee.statuses[:going])
|
||||
.where.not(user_id: unread_notified_user_ids)
|
||||
.find_each do |invitee|
|
||||
invitee.user.notifications.create!(
|
||||
notification_type: Notification.types[:custom],
|
||||
topic_id: event.post.topic_id,
|
||||
post_number: event.post.post_number,
|
||||
data: {
|
||||
topic_title: event.post.topic.title,
|
||||
display_username: invitee.user.username,
|
||||
message: 'discourse_post_event.notifications.before_event_reminder'
|
||||
}.to_json
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -83,8 +83,10 @@ after_initialize do
|
|||
"../app/controllers/discourse_post_event_controller.rb",
|
||||
"../app/controllers/discourse_post_event/invitees_controller.rb",
|
||||
"../app/controllers/discourse_post_event/events_controller.rb",
|
||||
"../app/controllers/discourse_post_event/reminders_controller.rb",
|
||||
"../app/controllers/discourse_post_event/upcoming_events_controller.rb",
|
||||
"../app/models/discourse_post_event/event.rb",
|
||||
"../app/models/discourse_post_event/reminder.rb",
|
||||
"../app/models/discourse_post_event/invitee.rb",
|
||||
"../lib/discourse_post_event/event_parser.rb",
|
||||
"../lib/discourse_post_event/event_validator.rb",
|
||||
|
@ -92,6 +94,7 @@ after_initialize do
|
|||
"../jobs/regular/discourse_post_event/event_will_start.rb",
|
||||
"../jobs/regular/discourse_post_event/event_started.rb",
|
||||
"../jobs/regular/discourse_post_event/event_ended.rb",
|
||||
"../jobs/regular/discourse_post_event/send_reminder.rb",
|
||||
"../lib/discourse_post_event/event_finder.rb",
|
||||
"../app/serializers/discourse_post_event/invitee_serializer.rb",
|
||||
"../app/serializers/discourse_post_event/event_serializer.rb"
|
||||
|
@ -108,7 +111,7 @@ after_initialize do
|
|||
get '/discourse-post-event/events/:id' => 'events#show'
|
||||
delete '/discourse-post-event/events/:id' => 'events#destroy'
|
||||
post '/discourse-post-event/events' => 'events#create'
|
||||
put '/discourse-post-event/events/:id' => 'events#update'
|
||||
put '/discourse-post-event/events/:id' => 'events#update', format: :json
|
||||
post '/discourse-post-event/events/:id/csv-bulk-invite' => 'events#csv_bulk_invite'
|
||||
post '/discourse-post-event/events/:id/bulk-invite' => 'events#bulk_invite', format: :json
|
||||
post '/discourse-post-event/events/:id/invite' => 'events#invite'
|
||||
|
@ -116,6 +119,7 @@ after_initialize do
|
|||
post '/discourse-post-event/events/:post_id/invitees' => 'invitees#create'
|
||||
get '/discourse-post-event/events/:post_id/invitees' => 'invitees#index'
|
||||
delete '/discourse-post-event/events/:post_id/invitees/:id' => 'invitees#destroy'
|
||||
delete '/discourse-post-event/events/:post_id/reminders/:id' => 'reminders#destroy'
|
||||
get '/upcoming-events' => 'upcoming_events#index'
|
||||
end
|
||||
|
||||
|
|
|
@ -4,16 +4,6 @@ require 'rails_helper'
|
|||
require 'securerandom'
|
||||
require_relative '../fabricators/event_fabricator'
|
||||
|
||||
def create_post_with_event(user, extra_raw = '')
|
||||
start = (Time.now - 10.seconds).utc.iso8601(3)
|
||||
|
||||
PostCreator.create!(
|
||||
user,
|
||||
title: "Sell a boat party ##{SecureRandom.alphanumeric}",
|
||||
raw: "[event start=\"#{start}\" #{extra_raw}]\n[/event]",
|
||||
)
|
||||
end
|
||||
|
||||
describe Post do
|
||||
Event ||= DiscoursePostEvent::Event
|
||||
Invitee ||= DiscoursePostEvent::Invitee
|
||||
|
|
|
@ -13,3 +13,13 @@ Fabricator(:event, from: 'DiscoursePostEvent::Event') do
|
|||
starts_at { |attrs| attrs[:starts_at] || 1.day.from_now.iso8601 }
|
||||
ends_at { |attrs| attrs[:ends_at] }
|
||||
end
|
||||
|
||||
def create_post_with_event(user, extra_raw = '')
|
||||
start = (Time.now - 10.seconds).utc.iso8601(3)
|
||||
|
||||
PostCreator.create!(
|
||||
user,
|
||||
title: "Sell a boat party ##{SecureRandom.alphanumeric}",
|
||||
raw: "[event start=\"#{start}\" #{extra_raw}]\n[/event]",
|
||||
)
|
||||
end
|
||||
|
|
|
@ -80,17 +80,17 @@ describe DiscoursePostEvent::Event do
|
|||
Jobs
|
||||
.expects(:cancel_scheduled_job)
|
||||
.with(:discourse_post_event_event_ended, event_id: first_post.id)
|
||||
.once
|
||||
.never
|
||||
|
||||
Jobs
|
||||
.expects(:cancel_scheduled_job)
|
||||
.with(:discourse_post_event_event_started, event_id: first_post.id)
|
||||
.once
|
||||
.at_least_once
|
||||
|
||||
Jobs
|
||||
.expects(:cancel_scheduled_job)
|
||||
.with(:discourse_post_event_event_will_start, event_id: first_post.id)
|
||||
.once
|
||||
.at_least_once
|
||||
|
||||
Event.create!(id: first_post.id, starts_at: starts_at)
|
||||
|
||||
|
@ -131,17 +131,17 @@ describe DiscoursePostEvent::Event do
|
|||
Jobs
|
||||
.expects(:cancel_scheduled_job)
|
||||
.with(:discourse_post_event_event_ended, event_id: first_post.id)
|
||||
.once
|
||||
.at_least_once
|
||||
|
||||
Jobs
|
||||
.expects(:cancel_scheduled_job)
|
||||
.with(:discourse_post_event_event_started, event_id: first_post.id)
|
||||
.once
|
||||
.at_least_once
|
||||
|
||||
Jobs
|
||||
.expects(:cancel_scheduled_job)
|
||||
.with(:discourse_post_event_event_will_start, event_id: first_post.id)
|
||||
.once
|
||||
.at_least_once
|
||||
|
||||
Event.create!(id: first_post.id, starts_at: Time.now - 1.day, ends_at: Time.now + 12.hours)
|
||||
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "rails_helper"
|
||||
require_relative '../fabricators/event_fabricator'
|
||||
|
||||
module DiscoursePostEvent
|
||||
describe RemindersController do
|
||||
let(:admin_1) { Fabricate(:user, admin: true) }
|
||||
|
||||
before do
|
||||
freeze_time
|
||||
Jobs.run_immediately!
|
||||
SiteSetting.calendar_enabled = true
|
||||
SiteSetting.discourse_post_event_enabled = true
|
||||
sign_in(admin_1)
|
||||
end
|
||||
|
||||
context 'destroying a reminder' do
|
||||
let!(:post_1) { create_post_with_event(admin_1) }
|
||||
|
||||
before do
|
||||
post_1.reload
|
||||
post_1.event.reminders.create!(value: 15, unit: 'minutes')
|
||||
end
|
||||
|
||||
context 'current user is allowed to destroy it' do
|
||||
it 'detroys the reminder' do
|
||||
reminders = post_1.event.reminders
|
||||
reminder = reminders.first
|
||||
|
||||
expect(reminders.count).to eq(1)
|
||||
Jobs.expects(:cancel_scheduled_job).with(:discourse_post_event_send_reminder, reminder_id: reminder.id).once
|
||||
|
||||
delete "/discourse-post-event/events/#{post_1.id}/reminders/#{reminder.id}.json"
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
expect(reminders.count).to eq(0)
|
||||
end
|
||||
end
|
||||
|
||||
context 'current user is not allowed to destroy it' do
|
||||
let(:lurker) { Fabricate(:user) }
|
||||
|
||||
before do
|
||||
sign_in(lurker)
|
||||
end
|
||||
|
||||
it 'it doesn’t destroy the reminder' do
|
||||
reminders = post_1.event.reminders
|
||||
expect(reminders.count).to eq(1)
|
||||
delete "/discourse-post-event/events/#{post_1.id}/reminders/#{reminders.first.id}.json"
|
||||
expect(response.status).to eq(403)
|
||||
expect(reminders.count).to eq(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue