FEATURE: permanently delete a private message (#79)

FEATURE: permanently delete a private message (#79)

Previously we added a future to set a time for permanent deletion for encrypted messages.

This feature allows user to permanently delete post even if the timer was not set. When the user clicks the delete button, we inform that message will be permanently deleted and create a timer for 1 minute. It allows the user to rollback if clicked by mistake.

diff --git a/app/controllers/encrypted_post_timers_controller.rb b/app/controllers/encrypted_post_timers_controller.rb
new file mode 100644
index 0000000..b2fc6b7
--- /dev/null
+++ b/app/controllers/encrypted_post_timers_controller.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+class DiscourseEncrypt::EncryptedPostTimersController < ApplicationController
+  def create
+    delete_at = 1.minutes.from_now
+    Array.wrap(params[:post_id]).each do |post_id|
+      create_for_post(post_id, delete_at)
+    end
+    render json: { delete_at: delete_at }
+  end
+
+  def destroy
+    post = Post.with_deleted.find(params[:post_id])
+    encrypted_post_timer = EncryptedPostTimer.find_by(post: post)
+    return unless encrypted_post_timer
+    if post.is_first_post?
+      topic = Topic.with_deleted.find(post.topic_id)
+      guardian.ensure_can_recover_topic!(topic)
+    else
+      guardian.ensure_can_recover_post!(post)
+    end
+    encrypted_post_timer.destroy!
+  end
+
+  private
+
+  def create_for_post(post_id, delete_at)
+    post = Post.find(post_id)
+    return unless post.is_encrypted?
+    guardian.ensure_can_delete!(post.is_first_post? ? post.topic : post)
+
+    EncryptedPostTimer.create!(post: post, delete_at: delete_at)
+  end
+end
diff --git a/app/jobs/scheduled/encrypted_post_timer_evaluator.rb b/app/jobs/scheduled/encrypted_post_timer_evaluator.rb
index 99425c4..148e0b2 100644
--- a/app/jobs/scheduled/encrypted_post_timer_evaluator.rb
+++ b/app/jobs/scheduled/encrypted_post_timer_evaluator.rb
@@ -8,7 +8,9 @@ module Jobs
       EncryptedPostTimer.pending.find_each do |encrypted_post_timer|
         ActiveRecord::Base.transaction do
           encrypted_post_timer.touch(:destroyed_at)
-          next if !encrypted_post_timer.post&.persisted?
+          next if posts_to_delete(encrypted_post_timer).blank?
+          next unless @topic
+          @topic.update_columns(deleted_at: nil)
           posts_to_delete(encrypted_post_timer).each do |post|
             next if !post&.persisted?
             PostDestroyer.new(post.user, post, permanent: true).destroy
@@ -18,7 +20,11 @@ module Jobs
     end
 
     def posts_to_delete(encrypted_post_timer)
-      encrypted_post_timer.post.is_first_post? ? encrypted_post_timer.post.topic.posts.with_deleted.order(created_at: :desc) : [encrypted_post_timer.post]
+      @post ||= Post.with_deleted.find_by(id: encrypted_post_timer.post_id)
+      return [] unless @post
+      @topic ||= Topic.with_deleted.find_by(id: @post.topic_id)
+      @posts_to_delete ||= @post&.is_first_post? ? @topic.posts.with_deleted.order(created_at: :desc) : [@post]
+      @posts_to_delete.compact
     end
   end
 end
diff --git a/assets/javascripts/discourse/initializers/hook-topic-controller.js.es6 b/assets/javascripts/discourse/initializers/hook-topic-controller.js.es6
new file mode 100644
index 0000000..6c6a6a4
--- /dev/null
+++ b/assets/javascripts/discourse/initializers/hook-topic-controller.js.es6
@@ -0,0 +1,139 @@
+import I18n from "I18n";
+import { withPluginApi } from "discourse/lib/plugin-api";
+import { ajax } from "discourse/lib/ajax";
+import Post from "discourse/models/post";
+
+export default {
+  name: "hook-topic-controller",
+
+  initialize() {
+    withPluginApi("0.8.32", (api) => {
+      api.modifyClass("controller:topic", {
+        permanentDeleteConfirmation(callback) {
+          bootbox.confirm(
+            I18n.t("encrypt.post.delete.confirm"),
+            I18n.t("encrypt.post.delete.no_value"),
+            I18n.t("encrypt.post.delete.yes_value"),
+            (result) => {
+              if (result) {
+                callback();
+              } else {
+                return;
+              }
+            }
+          );
+        },
+
+        createTimer(post_id) {
+          return ajax("/encrypt/encrypted_post_timers", {
+            type: "POST",
+            data: { post_id },
+          });
+        },
+
+        deleteTimer(post_id) {
+          return ajax("/encrypt/encrypted_post_timers", {
+            type: "DELETE",
+            data: { post_id },
+          });
+        },
+
+        deleteTopic() {
+          // TODO: https://github.com/emberjs/ember.js/issues/15291
+          let { _super } = this;
+          if (this.model.encrypted_title) {
+            this.permanentDeleteConfirmation(() => {
+              return this.createTimer(
+                this.model.postStream.posts[0].id
+              ).then(() => this.model.destroy(this.currentUser));
+            });
+          } else {
+            return _super.call(this, ...arguments);
+          }
+        },
+
+        actions: {
+          deletePost(post) {
+            // TODO: https://github.com/emberjs/ember.js/issues/15291
+            let { _super } = this;
+
+            if (post.encrypted_raw && post.get("post_number") !== 1) {
+              this.permanentDeleteConfirmation(() => {
+                return this.createTimer(post.id).then((result) => {
+                  post.setProperties({ delete_at: result.delete_at });
+                  return _super.call(this, ...arguments);
+                });
+              });
+            } else {
+              return _super.call(this, ...arguments);
+            }
+          },
+
+          deleteSelected() {
+            // TODO: https://github.com/emberjs/ember.js/issues/15291
+            let { _super } = this;
+
+            const user = this.currentUser;
+
+            if (this.selectedAllPosts) {
+              this.send("toggleMultiSelect");
+              return this.deleteTopic();
+            }
+
+            if (this.selectedPosts[0].encrypted_raw) {
+              this.permanentDeleteConfirmation(() => {
+                return this.createTimer(this.selectedPostIds).then((result) => {
+                  Post.deleteMany(this.selectedPostIds);
+                  this.get("model.postStream.posts").forEach((p) => {
+                    this.postSelected(p) &&
+                      p.setDeletedState(user) &&
+                      p.setProperties({
+                        delete_at: result.delete_at,
+                        deleted_at: new Date(),
+                        deleted_by: user,
+                      });
+                  });
+                  this.send("toggleMultiSelect");
+                });
+              });
+            } else {
+              return _super.call(this, ...arguments);
+            }
+          },
+
+          recoverTopic() {
+            // TODO: https://github.com/emberjs/ember.js/issues/15291
+            let { _super } = this;
+
+            if (this.model.encrypted_title) {
+              return this.deleteTimer(this.model.postStream.posts[0].id).then(
+                () => {
+                  this.model.postStream.posts[0].setProperties({
+                    delete_at: false,
+                  });
+                  return _super.call(this, ...arguments);
+                }
+              );
+            } else {
+              return _super.call(this, ...arguments);
+            }
+          },
+
+          recoverPost(post) {
+            // TODO: https://github.com/emberjs/ember.js/issues/15291
+            let { _super } = this;
+
+            if (post.encrypted_raw) {
+              return this.deleteTimer(post.id).then(() => {
+                post.setProperties({ delete_at: false });
+                return _super.call(this, ...arguments);
+              });
+            } else {
+              return _super.call(this, ...arguments);
+            }
+          },
+        },
+      });
+    });
+  },
+};
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 89345eb..edc05e4 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -32,6 +32,11 @@ en:
         checked: "This message will be end-to-end encrypted."
         unchecked: "Click the lock symbol to encrypt this message."
 
+      post:
+        delete:

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

GitHub sha: 3a1a2749

This commit appears in #79 which was approved by udan11. It was merged by lis2.