@rmdes/indiekit-endpoint-posts 1.0.0-beta.42 → 1.0.0-beta.44

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
@@ -6,6 +6,7 @@ import express from "express";
6
6
 
7
7
  import { deleteController } from "./lib/controllers/delete.js";
8
8
  import { editController } from "./lib/controllers/edit.js";
9
+ import { purgeAllController } from "./lib/controllers/purge-all.js";
9
10
  import { purgeController } from "./lib/controllers/purge.js";
10
11
  import { formController } from "./lib/controllers/form.js";
11
12
  import { newController } from "./lib/controllers/new.js";
@@ -47,6 +48,9 @@ export default class PostsEndpoint {
47
48
 
48
49
  router.get("/edit", editController.get);
49
50
 
51
+ router.get("/purge-deleted", purgeAllController.get);
52
+ router.post("/purge-deleted", purgeAllController.post);
53
+
50
54
  router.get("/new", newController.get);
51
55
  router.post("/new", validate.new, newController.post);
52
56
 
@@ -85,6 +85,15 @@ export const formController = {
85
85
  delete values.postType;
86
86
  delete values["publication-date"];
87
87
 
88
+ // Map content-warning text to summary when post is marked sensitive
89
+ // and the post type doesn't have its own summary field
90
+ if (values.sensitive && values["content-warning"]) {
91
+ if (!values.summary) {
92
+ values.summary = values["content-warning"];
93
+ }
94
+ }
95
+ delete values["content-warning"];
96
+
88
97
  // Easy MDE appends `image` value to formData for last image uploaded
89
98
  delete values.image;
90
99
 
@@ -62,6 +62,7 @@ export const postsController = async (request, response, next) => {
62
62
  item.icon = item["post-type"];
63
63
  item.locale = application.locale;
64
64
  item.photo = getPhotoUrl(publication, item);
65
+
65
66
  item.description = {
66
67
  text:
67
68
  item.summary ||
@@ -111,6 +112,14 @@ export const postsController = async (request, response, next) => {
111
112
  text: response.locals.__("posts.create.action"),
112
113
  }
113
114
  : {},
115
+ scope && checkScope(scope, "delete") && status === "deleted" && total > 0
116
+ ? {
117
+ classes: "actions__link--warning",
118
+ href: path.join(request.baseUrl, "/purge-deleted"),
119
+ icon: "delete",
120
+ text: response.locals.__("posts.purgeAll.action"),
121
+ }
122
+ : {},
114
123
  ],
115
124
  cursor,
116
125
  posts,
@@ -0,0 +1,68 @@
1
+ import { checkScope } from "@indiekit/endpoint-micropub/lib/scope.js";
2
+
3
+ import { getPosts, purgeAllDeletedPosts } from "../utils.js";
4
+
5
+ export const purgeAllController = {
6
+ /**
7
+ * Confirm purge of all deleted posts
8
+ * @type {import("express").RequestHandler}
9
+ */
10
+ async get(request, response) {
11
+ const { application } = request.app.locals;
12
+ const { scope } = request.session;
13
+
14
+ if (!scope || !checkScope(scope, "delete")) {
15
+ return response.redirect(request.baseUrl);
16
+ }
17
+
18
+ // Count deleted posts for the confirmation message
19
+ const { total } = await getPosts(application, { status: "deleted" });
20
+
21
+ if (total === 0) {
22
+ const message = encodeURIComponent(
23
+ response.locals.__("posts.purgeAll.none"),
24
+ );
25
+ return response.redirect(`${request.baseUrl}?success=${message}`);
26
+ }
27
+
28
+ response.render("post-purge-all", {
29
+ title: response.locals.__("posts.purgeAll.title"),
30
+ deletedCount: total,
31
+ postsPath: request.baseUrl,
32
+ });
33
+ },
34
+
35
+ /**
36
+ * Permanently delete all soft-deleted posts
37
+ * @type {import("express").RequestHandler}
38
+ */
39
+ async post(request, response) {
40
+ const { application } = request.app.locals;
41
+ const { scope } = request.session;
42
+
43
+ if (!scope || !checkScope(scope, "delete")) {
44
+ return response.redirect(request.baseUrl);
45
+ }
46
+
47
+ try {
48
+ const deletedCount = await purgeAllDeletedPosts(application);
49
+
50
+ const message = encodeURIComponent(
51
+ response.locals.__("posts.purgeAll.success", deletedCount),
52
+ );
53
+ response.redirect(`${request.baseUrl}?success=${message}`);
54
+ } catch (error) {
55
+ const { total } = await getPosts(application, {
56
+ status: "deleted",
57
+ });
58
+
59
+ response.status(error.status || 500);
60
+ response.render("post-purge-all", {
61
+ title: response.locals.__("posts.purgeAll.title"),
62
+ deletedCount: total,
63
+ postsPath: request.baseUrl,
64
+ error,
65
+ });
66
+ }
67
+ },
68
+ };
package/lib/utils.js CHANGED
@@ -307,6 +307,24 @@ export const purgePost = async (uid, application) => {
307
307
  return result.deletedCount > 0;
308
308
  };
309
309
 
310
+ /**
311
+ * Permanently delete all soft-deleted posts from MongoDB
312
+ * @param {object} application - Application object with collections
313
+ * @returns {Promise<number>} Number of posts deleted
314
+ */
315
+ export const purgeAllDeletedPosts = async (application) => {
316
+ const postsCollection = application?.collections?.get("posts");
317
+ if (!postsCollection) {
318
+ return 0;
319
+ }
320
+
321
+ const result = await postsCollection.deleteMany({
322
+ "properties.deleted": { $exists: true },
323
+ });
324
+
325
+ return result.deletedCount;
326
+ };
327
+
310
328
  /**
311
329
  * Query database for post data by Micropub URL
312
330
  * @param {string} url - Micropub URL (e.g. https://example.com/articles/2026/02/13/slug)
package/locales/en.json CHANGED
@@ -62,6 +62,15 @@
62
62
  "cancel": "No – return to posts",
63
63
  "success": "Post permanently deleted"
64
64
  },
65
+ "purgeAll": {
66
+ "action": "Purge all deleted",
67
+ "title": "Permanently delete all deleted posts?",
68
+ "warning": "This will permanently remove **%s deleted post(s)** from the database. This action cannot be undone.",
69
+ "submit": "I'm sure - permanently delete all",
70
+ "cancel": "No - return to posts",
71
+ "success": "Permanently deleted %s post(s)",
72
+ "none": "No deleted posts to purge"
73
+ },
65
74
  "form": {
66
75
  "advancedOptions": "Advanced options",
67
76
  "back": "Change post type",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rmdes/indiekit-endpoint-posts",
3
- "version": "1.0.0-beta.42",
3
+ "version": "1.0.0-beta.44",
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",
@@ -82,8 +82,8 @@
82
82
  label: __("posts.form.sensitive.hint"),
83
83
  value: "true",
84
84
  conditional: input({
85
- name: "summary",
86
- value: properties.summary,
85
+ name: "content-warning",
86
+ value: properties["content-warning"],
87
87
  label: __("posts.form.summary.label"),
88
88
  optional: true
89
89
  })
@@ -0,0 +1,20 @@
1
+ {% extends "form.njk" %}
2
+
3
+ {% block form %}
4
+ {{ heading({
5
+ text: title
6
+ }) }}
7
+
8
+ {{ prose({
9
+ text: __("posts.purgeAll.warning", deletedCount)
10
+ }) }}
11
+
12
+ {{ button({
13
+ classes: "button--warning",
14
+ text: __("posts.purgeAll.submit")
15
+ }) }}
16
+
17
+ {{ prose({
18
+ text: "[" + __("posts.purgeAll.cancel") + "](" + postsPath + ")"
19
+ }) }}
20
+ {% endblock %}