@rmdes/indiekit-endpoint-rss 1.2.0 → 1.2.2

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.
@@ -207,7 +207,10 @@ export async function resetPublishState(db, id) {
207
207
 
208
208
  const result = await db.collection("rssItems").updateMany(
209
209
  { feedId },
210
- { $unset: { postedAt: "", postUrl: "", postSkipped: "", postError: "", postErrorAt: "", postAttempts: "" } },
210
+ // postUrl is kept on purpose: it tells the publisher the post already
211
+ // exists, so the next attempt updates it instead of issuing a create the
212
+ // file store would silently ignore.
213
+ { $unset: { postedAt: "", postSkipped: "", postError: "", postErrorAt: "", postAttempts: "" } },
211
214
  );
212
215
 
213
216
  return { status: 200, reset: result.modifiedCount };
package/lib/publisher.js CHANGED
@@ -18,7 +18,10 @@ export function mintToken(me) {
18
18
  throw new Error("Cannot publish: SECRET is not configured");
19
19
  }
20
20
 
21
- return jwt.sign({ me, scope: "create" }, process.env.SECRET, {
21
+ // Both scopes: an item that already has a post is updated, not created, and
22
+ // checkScope treats them as distinct actions — a create-only token gets a
23
+ // 403 insufficient_scope on every republish.
24
+ return jwt.sign({ me, scope: "create update" }, process.env.SECRET, {
22
25
  expiresIn: TOKEN_TTL,
23
26
  });
24
27
  }
@@ -41,10 +44,18 @@ export async function postToMicropub(
41
44
  micropubEndpoint,
42
45
  accessToken,
43
46
  properties,
44
- { fetchImpl = fetch } = {},
47
+ { fetchImpl = fetch, replaces } = {},
45
48
  ) {
46
49
  const mf2 = jf2ToMf2({ properties: { ...properties } });
47
50
 
51
+ // A post that already exists must be updated, not created again. The file
52
+ // system store's createFile returns silently when the path exists, so a
53
+ // second create updates the database row and leaves the published file
54
+ // untouched — the two then disagree with nothing to show for it.
55
+ const body = replaces
56
+ ? { action: "update", url: replaces, replace: mf2.properties }
57
+ : mf2;
58
+
48
59
  const response = await fetchImpl(micropubEndpoint, {
49
60
  method: "POST",
50
61
  headers: {
@@ -52,7 +63,7 @@ export async function postToMicropub(
52
63
  authorization: `Bearer ${accessToken}`,
53
64
  "content-type": "application/json",
54
65
  },
55
- body: JSON.stringify(mf2),
66
+ body: JSON.stringify(body),
56
67
  });
57
68
 
58
69
  if (!response.ok) {
@@ -196,7 +207,10 @@ export async function publishPending(feed, itemsCollection, options) {
196
207
  // of the batch would be marked postSkipped and lost for good.
197
208
  const token = mintImpl(me);
198
209
  const properties = buildJf2(item, feed.publish);
199
- const postUrl = await postImpl(micropubEndpoint, token, properties);
210
+ const postUrl =
211
+ (await postImpl(micropubEndpoint, token, properties, {
212
+ replaces: item.postUrl,
213
+ })) || item.postUrl;
200
214
 
201
215
  await itemsCollection.updateOne(
202
216
  { _id: item._id },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rmdes/indiekit-endpoint-rss",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "RSS feed reader endpoint for Indiekit. Aggregates multiple feeds, caches in MongoDB, displays on frontend.",
5
5
  "keywords": [
6
6
  "indiekit",