@rmdes/indiekit-endpoint-microsub 1.0.0-beta.11 → 1.0.0-beta.13

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(
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import { deleteItemsByAuthorUrl } from "../storage/items.js";
7
+ import { getUserId } from "../utils/auth.js";
7
8
  import { validateUrl } from "../utils/validation.js";
8
9
 
9
10
  /**
@@ -23,7 +24,7 @@ function getCollection(application) {
23
24
  */
24
25
  export async function list(request, response) {
25
26
  const { application } = request.app.locals;
26
- const userId = request.session?.userId;
27
+ const userId = getUserId(request);
27
28
 
28
29
  const collection = getCollection(application);
29
30
  const blocked = await collection.find({ userId }).toArray();
@@ -40,7 +41,7 @@ export async function list(request, response) {
40
41
  */
41
42
  export async function block(request, response) {
42
43
  const { application } = request.app.locals;
43
- const userId = request.session?.userId;
44
+ const userId = getUserId(request);
44
45
  const { url } = request.body;
45
46
 
46
47
  validateUrl(url);
@@ -71,7 +72,7 @@ export async function block(request, response) {
71
72
  */
72
73
  export async function unblock(request, response) {
73
74
  const { application } = request.app.locals;
74
- const userId = request.session?.userId;
75
+ const userId = getUserId(request);
75
76
  const { url } = request.body;
76
77
 
77
78
  validateUrl(url);
@@ -13,6 +13,7 @@ import {
13
13
  deleteChannel,
14
14
  reorderChannels,
15
15
  } from "../storage/channels.js";
16
+ import { getUserId } from "../utils/auth.js";
16
17
  import {
17
18
  validateChannel,
18
19
  validateChannelName,
@@ -27,7 +28,7 @@ import {
27
28
  */
28
29
  export async function list(request, response) {
29
30
  const { application } = request.app.locals;
30
- const userId = request.session?.userId;
31
+ const userId = getUserId(request);
31
32
 
32
33
  const channels = await getChannels(application, userId);
33
34
 
@@ -42,7 +43,7 @@ export async function list(request, response) {
42
43
  */
43
44
  export async function action(request, response) {
44
45
  const { application } = request.app.locals;
45
- const userId = request.session?.userId;
46
+ const userId = getUserId(request);
46
47
  const { method, name, uid } = request.body;
47
48
 
48
49
  // Delete channel
@@ -113,7 +114,7 @@ export async function action(request, response) {
113
114
  */
114
115
  export async function get(request, response) {
115
116
  const { application } = request.app.locals;
116
- const userId = request.session?.userId;
117
+ const userId = getUserId(request);
117
118
  const { uid } = request.params;
118
119
 
119
120
  validateChannel(uid);
@@ -9,6 +9,7 @@ import {
9
9
  sendEvent,
10
10
  subscribeClient,
11
11
  } from "../realtime/broker.js";
12
+ import { getUserId } from "../utils/auth.js";
12
13
 
13
14
  /**
14
15
  * SSE stream endpoint
@@ -18,7 +19,7 @@ import {
18
19
  */
19
20
  export async function stream(request, response) {
20
21
  const { application } = request.app.locals;
21
- const userId = request.session?.userId;
22
+ const userId = getUserId(request);
22
23
 
23
24
  // Set SSE headers
24
25
  response.setHeader("Content-Type", "text/event-stream");
@@ -13,6 +13,7 @@ import {
13
13
  getFeedsForChannel,
14
14
  } from "../storage/feeds.js";
15
15
  import { createFeedResponse } from "../utils/jf2.js";
16
+ import { getUserId } from "../utils/auth.js";
16
17
  import { validateChannel, validateUrl } from "../utils/validation.js";
17
18
 
18
19
  /**
@@ -23,7 +24,7 @@ import { validateChannel, validateUrl } from "../utils/validation.js";
23
24
  */
24
25
  export async function list(request, response) {
25
26
  const { application } = request.app.locals;
26
- const userId = request.session?.userId;
27
+ const userId = getUserId(request);
27
28
  const { channel } = request.query;
28
29
 
29
30
  validateChannel(channel);
@@ -47,7 +48,7 @@ export async function list(request, response) {
47
48
  */
48
49
  export async function follow(request, response) {
49
50
  const { application } = request.app.locals;
50
- const userId = request.session?.userId;
51
+ const userId = getUserId(request);
51
52
  const { channel, url } = request.body;
52
53
 
53
54
  validateChannel(channel);
@@ -84,7 +85,7 @@ export async function follow(request, response) {
84
85
  */
85
86
  export async function unfollow(request, response) {
86
87
  const { application } = request.app.locals;
87
- const userId = request.session?.userId;
88
+ const userId = getUserId(request);
88
89
  const { channel, url } = request.body;
89
90
 
90
91
  validateChannel(channel);
@@ -5,6 +5,7 @@
5
5
 
6
6
  import { IndiekitError } from "@indiekit/error";
7
7
 
8
+ import { getUserId } from "../utils/auth.js";
8
9
  import { validateChannel, validateUrl } from "../utils/validation.js";
9
10
 
10
11
  /**
@@ -24,7 +25,7 @@ function getCollection(application) {
24
25
  */
25
26
  export async function list(request, response) {
26
27
  const { application } = request.app.locals;
27
- const userId = request.session?.userId;
28
+ const userId = getUserId(request);
28
29
  const { channel } = request.query;
29
30
 
30
31
  // Channel can be "global" or a specific channel UID
@@ -58,7 +59,7 @@ export async function list(request, response) {
58
59
  */
59
60
  export async function mute(request, response) {
60
61
  const { application } = request.app.locals;
61
- const userId = request.session?.userId;
62
+ const userId = getUserId(request);
62
63
  const { channel, url } = request.body;
63
64
 
64
65
  validateUrl(url);
@@ -99,7 +100,7 @@ export async function mute(request, response) {
99
100
  */
100
101
  export async function unmute(request, response) {
101
102
  const { application } = request.app.locals;
102
- const userId = request.session?.userId;
103
+ const userId = getUserId(request);
103
104
  const { channel, url } = request.body;
104
105
 
105
106
  validateUrl(url);
@@ -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,
@@ -17,6 +18,7 @@ import {
17
18
  deleteFeed,
18
19
  } from "../storage/feeds.js";
19
20
  import { getTimelineItems, getItemById } from "../storage/items.js";
21
+ import { getUserId } from "../utils/auth.js";
20
22
  import {
21
23
  validateChannelName,
22
24
  validateExcludeTypes,
@@ -39,7 +41,7 @@ export async function index(request, response) {
39
41
  */
40
42
  export async function channels(request, response) {
41
43
  const { application } = request.app.locals;
42
- const userId = request.session?.userId;
44
+ const userId = getUserId(request);
43
45
 
44
46
  const channelList = await getChannels(application, userId);
45
47
 
@@ -69,7 +71,7 @@ export async function newChannel(request, response) {
69
71
  */
70
72
  export async function createChannelAction(request, response) {
71
73
  const { application } = request.app.locals;
72
- const userId = request.session?.userId;
74
+ const userId = getUserId(request);
73
75
  const { name } = request.body;
74
76
 
75
77
  validateChannelName(name);
@@ -87,7 +89,7 @@ export async function createChannelAction(request, response) {
87
89
  */
88
90
  export async function channel(request, response) {
89
91
  const { application } = request.app.locals;
90
- const userId = request.session?.userId;
92
+ const userId = getUserId(request);
91
93
  const { uid } = request.params;
92
94
  const { before, after } = request.query;
93
95
 
@@ -119,7 +121,7 @@ export async function channel(request, response) {
119
121
  */
120
122
  export async function settings(request, response) {
121
123
  const { application } = request.app.locals;
122
- const userId = request.session?.userId;
124
+ const userId = getUserId(request);
123
125
  const { uid } = request.params;
124
126
 
125
127
  const channelDocument = await getChannel(application, uid, userId);
@@ -144,7 +146,7 @@ export async function settings(request, response) {
144
146
  */
145
147
  export async function updateSettings(request, response) {
146
148
  const { application } = request.app.locals;
147
- const userId = request.session?.userId;
149
+ const userId = getUserId(request);
148
150
  const { uid } = request.params;
149
151
  const { excludeTypes, excludeRegex } = request.body;
150
152
 
@@ -171,6 +173,32 @@ export async function updateSettings(request, response) {
171
173
  response.redirect(`${request.baseUrl}/channels/${uid}`);
172
174
  }
173
175
 
176
+ /**
177
+ * Delete channel
178
+ * @param {object} request - Express request
179
+ * @param {object} response - Express response
180
+ * @returns {Promise<void>}
181
+ */
182
+ export async function deleteChannelAction(request, response) {
183
+ const { application } = request.app.locals;
184
+ const userId = getUserId(request);
185
+ const { uid } = request.params;
186
+
187
+ // Don't allow deleting notifications channel
188
+ if (uid === "notifications") {
189
+ return response.redirect(`${request.baseUrl}/channels`);
190
+ }
191
+
192
+ const channelDocument = await getChannel(application, uid, userId);
193
+ if (!channelDocument) {
194
+ return response.status(404).render("404");
195
+ }
196
+
197
+ await deleteChannel(application, uid, userId);
198
+
199
+ response.redirect(`${request.baseUrl}/channels`);
200
+ }
201
+
174
202
  /**
175
203
  * View feeds for a channel
176
204
  * @param {object} request - Express request
@@ -179,7 +207,7 @@ export async function updateSettings(request, response) {
179
207
  */
180
208
  export async function feeds(request, response) {
181
209
  const { application } = request.app.locals;
182
- const userId = request.session?.userId;
210
+ const userId = getUserId(request);
183
211
  const { uid } = request.params;
184
212
 
185
213
  const channelDocument = await getChannel(application, uid, userId);
@@ -205,7 +233,7 @@ export async function feeds(request, response) {
205
233
  */
206
234
  export async function addFeed(request, response) {
207
235
  const { application } = request.app.locals;
208
- const userId = request.session?.userId;
236
+ const userId = getUserId(request);
209
237
  const { uid } = request.params;
210
238
  const { url } = request.body;
211
239
 
@@ -238,7 +266,7 @@ export async function addFeed(request, response) {
238
266
  */
239
267
  export async function removeFeed(request, response) {
240
268
  const { application } = request.app.locals;
241
- const userId = request.session?.userId;
269
+ const userId = getUserId(request);
242
270
  const { uid } = request.params;
243
271
  const { url } = request.body;
244
272
 
@@ -260,7 +288,7 @@ export async function removeFeed(request, response) {
260
288
  */
261
289
  export async function item(request, response) {
262
290
  const { application } = request.app.locals;
263
- const userId = request.session?.userId;
291
+ const userId = getUserId(request);
264
292
  const { id } = request.params;
265
293
 
266
294
  const itemDocument = await getItemById(application, id, userId);
@@ -311,7 +339,7 @@ export async function submitCompose(request, response) {
311
339
  */
312
340
  export async function searchPage(request, response) {
313
341
  const { application } = request.app.locals;
314
- const userId = request.session?.userId;
342
+ const userId = getUserId(request);
315
343
 
316
344
  const channelList = await getChannels(application, userId);
317
345
 
@@ -330,7 +358,7 @@ export async function searchPage(request, response) {
330
358
  */
331
359
  export async function searchFeeds(request, response) {
332
360
  const { application } = request.app.locals;
333
- const userId = request.session?.userId;
361
+ const userId = getUserId(request);
334
362
  const { query } = request.body;
335
363
 
336
364
  const channelList = await getChannels(application, userId);
@@ -362,7 +390,7 @@ export async function searchFeeds(request, response) {
362
390
  */
363
391
  export async function subscribe(request, response) {
364
392
  const { application } = request.app.locals;
365
- const userId = request.session?.userId;
393
+ const userId = getUserId(request);
366
394
  const { url, channel: channelUid } = request.body;
367
395
 
368
396
  const channelDocument = await getChannel(application, channelUid, userId);
@@ -394,6 +422,7 @@ export const readerController = {
394
422
  channel,
395
423
  settings,
396
424
  updateSettings,
425
+ deleteChannel: deleteChannelAction,
397
426
  feeds,
398
427
  addFeed,
399
428
  removeFeed,
@@ -8,6 +8,7 @@ import { IndiekitError } from "@indiekit/error";
8
8
  import { discoverFeeds } from "../feeds/hfeed.js";
9
9
  import { searchWithFallback } from "../search/query.js";
10
10
  import { getChannel } from "../storage/channels.js";
11
+ import { getUserId } from "../utils/auth.js";
11
12
  import { validateChannel, validateUrl } from "../utils/validation.js";
12
13
 
13
14
  /**
@@ -77,7 +78,7 @@ export async function discover(request, response) {
77
78
  */
78
79
  export async function search(request, response) {
79
80
  const { application } = request.app.locals;
80
- const userId = request.session?.userId;
81
+ const userId = getUserId(request);
81
82
  const { query, channel } = request.body;
82
83
 
83
84
  if (!query) {
@@ -12,6 +12,7 @@ import {
12
12
  markItemsUnread,
13
13
  removeItems,
14
14
  } from "../storage/items.js";
15
+ import { getUserId } from "../utils/auth.js";
15
16
  import {
16
17
  validateChannel,
17
18
  validateEntries,
@@ -26,7 +27,7 @@ import {
26
27
  */
27
28
  export async function get(request, response) {
28
29
  const { application } = request.app.locals;
29
- const userId = request.session?.userId;
30
+ const userId = getUserId(request);
30
31
  const { channel, before, after, limit } = request.query;
31
32
 
32
33
  validateChannel(channel);
@@ -57,7 +58,7 @@ export async function get(request, response) {
57
58
  */
58
59
  export async function action(request, response) {
59
60
  const { application } = request.app.locals;
60
- const userId = request.session?.userId;
61
+ const userId = getUserId(request);
61
62
  const { method, channel } = request.body;
62
63
 
63
64
  validateChannel(channel);
@@ -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
 
@@ -195,7 +195,7 @@ export async function getItemsByUids(application, uids, userId) {
195
195
  * Mark items as read
196
196
  * @param {object} application - Indiekit application
197
197
  * @param {ObjectId|string} channelId - Channel ObjectId
198
- * @param {Array} entryIds - Array of entry IDs to mark as read
198
+ * @param {Array} entryIds - Array of entry IDs to mark as read (can be ObjectId, uid, or URL)
199
199
  * @param {string} userId - User ID
200
200
  * @returns {Promise<number>} Number of items updated
201
201
  */
@@ -204,6 +204,12 @@ export async function markItemsRead(application, channelId, entryIds, userId) {
204
204
  const channelObjectId =
205
205
  typeof channelId === "string" ? new ObjectId(channelId) : channelId;
206
206
 
207
+ console.info(
208
+ `[Microsub] markItemsRead called for channel ${channelId}, entries:`,
209
+ entryIds,
210
+ `userId: ${userId}`,
211
+ );
212
+
207
213
  // Handle "last-read-entry" special value
208
214
  if (entryIds.includes("last-read-entry")) {
209
215
  // Mark all items in channel as read
@@ -211,26 +217,39 @@ export async function markItemsRead(application, channelId, entryIds, userId) {
211
217
  { channelId: channelObjectId },
212
218
  { $addToSet: { readBy: userId } },
213
219
  );
220
+ console.info(
221
+ `[Microsub] Marked all items as read: ${result.modifiedCount} updated`,
222
+ );
214
223
  return result.modifiedCount;
215
224
  }
216
225
 
217
226
  // Convert string IDs to ObjectIds where possible
218
- const objectIds = entryIds.map((id) => {
219
- try {
220
- return new ObjectId(id);
221
- } catch {
222
- return id;
223
- }
224
- });
227
+ const objectIds = entryIds
228
+ .map((id) => {
229
+ try {
230
+ return new ObjectId(id);
231
+ } catch {
232
+ return undefined;
233
+ }
234
+ })
235
+ .filter(Boolean);
225
236
 
237
+ // Build query to match by _id, uid, or url (Microsub spec uses URLs as entry identifiers)
226
238
  const result = await collection.updateMany(
227
239
  {
228
240
  channelId: channelObjectId,
229
- $or: [{ _id: { $in: objectIds } }, { uid: { $in: entryIds } }],
241
+ $or: [
242
+ ...(objectIds.length > 0 ? [{ _id: { $in: objectIds } }] : []),
243
+ { uid: { $in: entryIds } },
244
+ { url: { $in: entryIds } },
245
+ ],
230
246
  },
231
247
  { $addToSet: { readBy: userId } },
232
248
  );
233
249
 
250
+ console.info(
251
+ `[Microsub] markItemsRead result: ${result.modifiedCount} items updated`,
252
+ );
234
253
  return result.modifiedCount;
235
254
  }
236
255
 
@@ -238,7 +257,7 @@ export async function markItemsRead(application, channelId, entryIds, userId) {
238
257
  * Mark items as unread
239
258
  * @param {object} application - Indiekit application
240
259
  * @param {ObjectId|string} channelId - Channel ObjectId
241
- * @param {Array} entryIds - Array of entry IDs to mark as unread
260
+ * @param {Array} entryIds - Array of entry IDs to mark as unread (can be ObjectId, uid, or URL)
242
261
  * @param {string} userId - User ID
243
262
  * @returns {Promise<number>} Number of items updated
244
263
  */
@@ -252,18 +271,26 @@ export async function markItemsUnread(
252
271
  const channelObjectId =
253
272
  typeof channelId === "string" ? new ObjectId(channelId) : channelId;
254
273
 
255
- const objectIds = entryIds.map((id) => {
256
- try {
257
- return new ObjectId(id);
258
- } catch {
259
- return id;
260
- }
261
- });
274
+ // Convert string IDs to ObjectIds where possible
275
+ const objectIds = entryIds
276
+ .map((id) => {
277
+ try {
278
+ return new ObjectId(id);
279
+ } catch {
280
+ return undefined;
281
+ }
282
+ })
283
+ .filter(Boolean);
262
284
 
285
+ // Match by _id, uid, or url
263
286
  const result = await collection.updateMany(
264
287
  {
265
288
  channelId: channelObjectId,
266
- $or: [{ _id: { $in: objectIds } }, { uid: { $in: entryIds } }],
289
+ $or: [
290
+ ...(objectIds.length > 0 ? [{ _id: { $in: objectIds } }] : []),
291
+ { uid: { $in: entryIds } },
292
+ { url: { $in: entryIds } },
293
+ ],
267
294
  },
268
295
  { $pull: { readBy: userId } },
269
296
  );
@@ -275,7 +302,7 @@ export async function markItemsUnread(
275
302
  * Remove items from channel
276
303
  * @param {object} application - Indiekit application
277
304
  * @param {ObjectId|string} channelId - Channel ObjectId
278
- * @param {Array} entryIds - Array of entry IDs to remove
305
+ * @param {Array} entryIds - Array of entry IDs to remove (can be ObjectId, uid, or URL)
279
306
  * @returns {Promise<number>} Number of items removed
280
307
  */
281
308
  export async function removeItems(application, channelId, entryIds) {
@@ -283,17 +310,25 @@ export async function removeItems(application, channelId, entryIds) {
283
310
  const channelObjectId =
284
311
  typeof channelId === "string" ? new ObjectId(channelId) : channelId;
285
312
 
286
- const objectIds = entryIds.map((id) => {
287
- try {
288
- return new ObjectId(id);
289
- } catch {
290
- return id;
291
- }
292
- });
313
+ // Convert string IDs to ObjectIds where possible
314
+ const objectIds = entryIds
315
+ .map((id) => {
316
+ try {
317
+ return new ObjectId(id);
318
+ } catch {
319
+ return undefined;
320
+ }
321
+ })
322
+ .filter(Boolean);
293
323
 
324
+ // Match by _id, uid, or url
294
325
  const result = await collection.deleteMany({
295
326
  channelId: channelObjectId,
296
- $or: [{ _id: { $in: objectIds } }, { uid: { $in: entryIds } }],
327
+ $or: [
328
+ ...(objectIds.length > 0 ? [{ _id: { $in: objectIds } }] : []),
329
+ { uid: { $in: entryIds } },
330
+ { url: { $in: entryIds } },
331
+ ],
297
332
  });
298
333
 
299
334
  return result.deletedCount;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Authentication utilities for Microsub
3
+ * @module utils/auth
4
+ */
5
+
6
+ /**
7
+ * Get the user ID from request context
8
+ *
9
+ * In Indiekit, the userId can come from:
10
+ * 1. request.session.userId (if explicitly set)
11
+ * 2. request.session.me (from token introspection)
12
+ * 3. application.publication.me (single-user fallback)
13
+ *
14
+ * @param {object} request - Express request
15
+ * @returns {string|undefined} User ID
16
+ */
17
+ export function getUserId(request) {
18
+ // Check session for explicit userId
19
+ if (request.session?.userId) {
20
+ return request.session.userId;
21
+ }
22
+
23
+ // Check session for me URL from token introspection
24
+ if (request.session?.me) {
25
+ return request.session.me;
26
+ }
27
+
28
+ // Fall back to publication me URL (single-user mode)
29
+ const { application } = request.app.locals;
30
+ if (application?.publication?.me) {
31
+ return application.publication.me;
32
+ }
33
+
34
+ // Final fallback: use "default" as user ID for single-user instances
35
+ // This ensures read state is tracked even without explicit user identity
36
+ return "default";
37
+ }
@@ -3,6 +3,7 @@
3
3
  * @module webmention/receiver
4
4
  */
5
5
 
6
+ import { getUserId } from "../utils/auth.js";
6
7
  import { processWebmention } from "./processor.js";
7
8
 
8
9
  /**
@@ -33,7 +34,7 @@ export async function receive(request, response) {
33
34
  }
34
35
 
35
36
  const { application } = request.app.locals;
36
- const userId = request.session?.userId;
37
+ const userId = getUserId(request);
37
38
 
38
39
  // Return 202 Accepted immediately (processing asynchronously)
39
40
  response.status(202).json({
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.11",
3
+ "version": "1.0.0-beta.13",
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 %}