@rmdes/indiekit-endpoint-posts 1.0.0-beta.30 → 1.0.0-beta.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -5,6 +5,7 @@ import { ISO_6709_RE, isRequired } from "@indiekit/util";
5
5
  import express from "express";
6
6
 
7
7
  import { deleteController } from "./lib/controllers/delete.js";
8
+ import { purgeController } from "./lib/controllers/purge.js";
8
9
  import { formController } from "./lib/controllers/form.js";
9
10
  import { newController } from "./lib/controllers/new.js";
10
11
  import { postController } from "./lib/controllers/post.js";
@@ -58,6 +59,9 @@ export default class PostsEndpoint {
58
59
  router.get(["/:uid/delete", "/:uid/undelete"], deleteController.get);
59
60
  router.post(["/:uid/delete", "/:uid/undelete"], deleteController.post);
60
61
 
62
+ router.get("/:uid/purge", purgeController.get);
63
+ router.post("/:uid/purge", purgeController.post);
64
+
61
65
  return router;
62
66
  }
63
67
 
@@ -44,6 +44,14 @@ export const postController = async (request, response) => {
44
44
  text: response.locals.__("posts.undelete.action"),
45
45
  }
46
46
  : {},
47
+ scope && checkScope(scope, "delete") && properties.deleted
48
+ ? {
49
+ classes: "actions__link--warning",
50
+ href: path.join(request.baseUrl + request.path, "/purge"),
51
+ icon: "delete",
52
+ text: response.locals.__("posts.purge.action"),
53
+ }
54
+ : {},
47
55
  ],
48
56
  redirectUri: path.join(request.baseUrl, request.params.uid),
49
57
  success: request.query.success,
@@ -0,0 +1,54 @@
1
+ import { checkScope } from "@indiekit/endpoint-micropub/lib/scope.js";
2
+
3
+ import { purgePost } from "../utils.js";
4
+
5
+ export const purgeController = {
6
+ /**
7
+ * Confirm permanent deletion
8
+ * @type {import("express").RequestHandler}
9
+ */
10
+ async get(request, response) {
11
+ const { postName, postsPath, properties, scope } = response.locals;
12
+
13
+ if (scope && checkScope(scope, "delete") && properties.deleted) {
14
+ return response.render("post-purge", {
15
+ title: response.locals.__("posts.purge.title"),
16
+ parent: { text: postName },
17
+ });
18
+ }
19
+
20
+ response.redirect(postsPath);
21
+ },
22
+
23
+ /**
24
+ * Permanently delete post from database
25
+ * @type {import("express").RequestHandler}
26
+ */
27
+ async post(request, response) {
28
+ const { application } = request.app.locals;
29
+ const { properties, postsPath } = response.locals;
30
+ const uid = request.params.uid;
31
+
32
+ try {
33
+ // Only allow purging already soft-deleted posts
34
+ if (!properties.deleted) {
35
+ response.redirect(postsPath);
36
+ return;
37
+ }
38
+
39
+ await purgePost(uid, application);
40
+
41
+ const message = encodeURIComponent(
42
+ response.locals.__("posts.purge.success"),
43
+ );
44
+ response.redirect(`${request.baseUrl}?success=${message}`);
45
+ } catch (error) {
46
+ response.status(error.status || 500);
47
+ response.render("post-purge", {
48
+ title: response.locals.__("posts.purge.title"),
49
+ parent: { text: postName },
50
+ error,
51
+ });
52
+ }
53
+ },
54
+ };
package/lib/utils.js CHANGED
@@ -278,6 +278,26 @@ export const getPostTypeCounts = async (application) => {
278
278
  .toArray();
279
279
  };
280
280
 
281
+ /**
282
+ * Permanently delete a post from MongoDB
283
+ * @param {string} uid - MongoDB ObjectId string
284
+ * @param {object} application - Application object with collections
285
+ * @returns {Promise<boolean>} True if deleted, false if not found
286
+ */
287
+ export const purgePost = async (uid, application) => {
288
+ const postsCollection = application?.collections?.get("posts");
289
+ if (!postsCollection) {
290
+ return false;
291
+ }
292
+
293
+ // Match _id as string to avoid BSON version issues
294
+ const result = await postsCollection.deleteOne({
295
+ $expr: { $eq: [{ $toString: "$_id" }, uid] },
296
+ });
297
+
298
+ return result.deletedCount > 0;
299
+ };
300
+
281
301
  /**
282
302
  * Get post URL from ID
283
303
  * @param {string} id - ID
package/locales/en.json CHANGED
@@ -52,7 +52,15 @@
52
52
  "undelete": {
53
53
  "action": "Restore post",
54
54
  "title": "Are you sure you want to restore this post?",
55
- "submit": "Im sure – restore this post"
55
+ "submit": "I'm sure – restore this post"
56
+ },
57
+ "purge": {
58
+ "action": "Permanently delete",
59
+ "title": "Permanently delete this post?",
60
+ "warning": "This will remove the post from the database. This action cannot be undone.",
61
+ "submit": "I'm sure – permanently delete",
62
+ "cancel": "No – return to posts",
63
+ "success": "Post permanently deleted"
56
64
  },
57
65
  "form": {
58
66
  "advancedOptions": "Advanced options",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rmdes/indiekit-endpoint-posts",
3
- "version": "1.0.0-beta.30",
3
+ "version": "1.0.0-beta.31",
4
4
  "description": "Post management endpoint for Indiekit with syndicate form fix. View posts published by your Micropub endpoint and publish new posts to it.",
5
5
  "keywords": [
6
6
  "indiekit",
@@ -0,0 +1,21 @@
1
+ {% extends "form.njk" %}
2
+
3
+ {% block form %}
4
+ {{ heading({
5
+ text: title,
6
+ parent: parent
7
+ }) }}
8
+
9
+ {{ prose({
10
+ text: __("posts.purge.warning")
11
+ }) }}
12
+
13
+ {{ button({
14
+ classes: "button--warning",
15
+ text: __("posts.purge.submit")
16
+ }) }}
17
+
18
+ {{ prose({
19
+ text: "[" + __("posts.purge.cancel") + "](" + postsPath + ")"
20
+ }) }}
21
+ {% endblock %}