@rmdes/indiekit-endpoint-microsub 1.0.0-beta.10 → 1.0.0-beta.12

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
@@ -78,6 +78,10 @@ export default class MicrosubEndpoint {
78
78
  "/channels/:uid/settings",
79
79
  readerController.updateSettings,
80
80
  );
81
+ readerRouter.post(
82
+ "/channels/:uid/delete",
83
+ readerController.deleteChannel,
84
+ );
81
85
  readerRouter.get("/channels/:uid/feeds", readerController.feeds);
82
86
  readerRouter.post("/channels/:uid/feeds", readerController.addFeed);
83
87
  readerRouter.post(
@@ -10,6 +10,7 @@ import {
10
10
  getChannel,
11
11
  createChannel,
12
12
  updateChannelSettings,
13
+ deleteChannel,
13
14
  } from "../storage/channels.js";
14
15
  import {
15
16
  getFeedsForChannel,
@@ -171,6 +172,32 @@ export async function updateSettings(request, response) {
171
172
  response.redirect(`${request.baseUrl}/channels/${uid}`);
172
173
  }
173
174
 
175
+ /**
176
+ * Delete channel
177
+ * @param {object} request - Express request
178
+ * @param {object} response - Express response
179
+ * @returns {Promise<void>}
180
+ */
181
+ export async function deleteChannelAction(request, response) {
182
+ const { application } = request.app.locals;
183
+ const userId = request.session?.userId;
184
+ const { uid } = request.params;
185
+
186
+ // Don't allow deleting notifications channel
187
+ if (uid === "notifications") {
188
+ return response.redirect(`${request.baseUrl}/channels`);
189
+ }
190
+
191
+ const channelDocument = await getChannel(application, uid, userId);
192
+ if (!channelDocument) {
193
+ return response.status(404).render("404");
194
+ }
195
+
196
+ await deleteChannel(application, uid, userId);
197
+
198
+ response.redirect(`${request.baseUrl}/channels`);
199
+ }
200
+
174
201
  /**
175
202
  * View feeds for a channel
176
203
  * @param {object} request - Express request
@@ -394,6 +421,7 @@ export const readerController = {
394
421
  channel,
395
422
  settings,
396
423
  updateSettings,
424
+ deleteChannel: deleteChannelAction,
397
425
  feeds,
398
426
  addFeed,
399
427
  removeFeed,
@@ -5,6 +5,8 @@
5
5
 
6
6
  import { ObjectId } from "mongodb";
7
7
 
8
+ import { deleteFeedsForChannel } from "./feeds.js";
9
+ import { deleteItemsForChannel } from "./items.js";
8
10
  import { generateChannelUid } from "../utils/jf2.js";
9
11
 
10
12
  /**
@@ -184,7 +186,7 @@ export async function updateChannel(application, uid, updates, userId) {
184
186
  }
185
187
 
186
188
  /**
187
- * Delete a channel
189
+ * Delete a channel and all its feeds and items
188
190
  * @param {object} application - Indiekit application
189
191
  * @param {string} uid - Channel UID
190
192
  * @param {string} [userId] - User ID
@@ -200,7 +202,20 @@ export async function deleteChannel(application, uid, userId) {
200
202
  return false;
201
203
  }
202
204
 
203
- const result = await collection.deleteOne(query);
205
+ // Find the channel first to get its ObjectId
206
+ const channel = await collection.findOne(query);
207
+ if (!channel) {
208
+ return false;
209
+ }
210
+
211
+ // Cascade delete: items first, then feeds, then channel
212
+ const itemsDeleted = await deleteItemsForChannel(application, channel._id);
213
+ const feedsDeleted = await deleteFeedsForChannel(application, channel._id);
214
+ console.info(
215
+ `[Microsub] Deleted channel ${uid}: ${feedsDeleted} feeds, ${itemsDeleted} items`,
216
+ );
217
+
218
+ const result = await collection.deleteOne({ _id: channel._id });
204
219
  return result.deletedCount > 0;
205
220
  }
206
221
 
@@ -5,6 +5,8 @@
5
5
 
6
6
  import { ObjectId } from "mongodb";
7
7
 
8
+ import { deleteItemsForFeed } from "./items.js";
9
+
8
10
  /**
9
11
  * Get feeds collection from application
10
12
  * @param {object} application - Indiekit application
@@ -122,7 +124,7 @@ export async function updateFeed(application, id, updates) {
122
124
  }
123
125
 
124
126
  /**
125
- * Delete a feed subscription
127
+ * Delete a feed subscription and all its items
126
128
  * @param {object} application - Indiekit application
127
129
  * @param {ObjectId|string} channelId - Channel ObjectId
128
130
  * @param {string} url - Feed URL
@@ -133,7 +135,18 @@ export async function deleteFeed(application, channelId, url) {
133
135
  const objectId =
134
136
  typeof channelId === "string" ? new ObjectId(channelId) : channelId;
135
137
 
136
- const result = await collection.deleteOne({ channelId: objectId, url });
138
+ // Find the feed first to get its ID for cascade delete
139
+ const feed = await collection.findOne({ channelId: objectId, url });
140
+ if (!feed) {
141
+ return false;
142
+ }
143
+
144
+ // Delete all items from this feed
145
+ const itemsDeleted = await deleteItemsForFeed(application, feed._id);
146
+ console.info(`[Microsub] Deleted ${itemsDeleted} items from feed ${url}`);
147
+
148
+ // Delete the feed itself
149
+ const result = await collection.deleteOne({ _id: feed._id });
137
150
  return result.deletedCount > 0;
138
151
  }
139
152
 
package/locales/en.json CHANGED
@@ -55,6 +55,10 @@
55
55
  "excludeRegex": "Exclude pattern",
56
56
  "excludeRegexHelp": "Regular expression to filter out matching content",
57
57
  "save": "Save settings",
58
+ "dangerZone": "Danger zone",
59
+ "deleteWarning": "Deleting this channel will permanently remove all feeds and items. This action cannot be undone.",
60
+ "deleteConfirm": "Are you sure you want to delete this channel and all its content?",
61
+ "delete": "Delete channel",
58
62
  "types": {
59
63
  "like": "Likes",
60
64
  "repost": "Reposts",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rmdes/indiekit-endpoint-microsub",
3
- "version": "1.0.0-beta.10",
3
+ "version": "1.0.0-beta.12",
4
4
  "description": "Microsub endpoint for Indiekit. Enables subscribing to feeds and reading content using the Microsub protocol.",
5
5
  "keywords": [
6
6
  "indiekit",
@@ -55,5 +55,19 @@
55
55
  </a>
56
56
  </div>
57
57
  </form>
58
+
59
+ {% if channel.uid !== "notifications" %}
60
+ <hr class="divider">
61
+ <div class="danger-zone">
62
+ <h3>{{ __("microsub.settings.dangerZone") }}</h3>
63
+ <p class="hint">{{ __("microsub.settings.deleteWarning") }}</p>
64
+ <form method="post" action="{{ baseUrl }}/channels/{{ channel.uid }}/delete" onsubmit="return confirm('{{ __("microsub.settings.deleteConfirm") }}');">
65
+ {{ button({
66
+ text: __("microsub.settings.delete"),
67
+ classes: "button--danger"
68
+ }) }}
69
+ </form>
70
+ </div>
71
+ {% endif %}
58
72
  </div>
59
73
  {% endblock %}