fix: write like posts to GitHub store via postTemplate + store.createFile

Previously sync only inserted into MongoDB, causing "file not found" errors
when Indiekit tried to read the post. Now generates markdown via
publication.postTemplate() and writes to GitHub via publication.store.createFile(),
matching the micropub endpoint's create flow. Reset also deletes store files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
svemagie
2026-03-18 22:38:23 +01:00
parent 56f859e70d
commit ae0b879401
2 changed files with 90 additions and 20 deletions

View File

@@ -236,8 +236,36 @@ export const likesController = {
const postsCollection = request.app.locals.application.collections?.get("posts");
const publication = request.app.locals.publication;
let deletedPosts = 0;
if (postsCollection) {
// Delete files from the store (GitHub) before removing from MongoDB
if (publication?.store) {
const likePosts = await postsCollection
.find({
"properties.post-type": "like",
"properties.youtube-video-id": { $exists: true },
})
.toArray();
for (const post of likePosts) {
try {
const message = publication.storeMessageTemplate
? publication.storeMessageTemplate({
action: "delete",
result: "deleted",
fileType: "post",
postType: "like",
})
: `Delete like post ${post.path}`;
await publication.store.deleteFile(post.path, { message });
} catch (err) {
console.error(`[YouTube] Failed to delete ${post.path} from store:`, err.message);
}
}
}
const result = await postsCollection.deleteMany({
"properties.post-type": "like",
"properties.youtube-video-id": { $exists: true },

View File

@@ -61,6 +61,28 @@ async function snapshotExistingLikes(db, client, accessToken, maxPages) {
return count;
}
/**
* Prepare template properties by stripping internal mp-* and post-type keys,
* matching what Indiekit's micropub endpoint does before calling postTemplate.
* @param {object} properties
* @returns {object}
*/
function getTemplateProperties(properties) {
const templateProperties = structuredClone(properties);
const preserveMpProperties = ["mp-syndicate-to"];
for (const key in templateProperties) {
if (key.startsWith("mp-") && !preserveMpProperties.includes(key)) {
delete templateProperties[key];
}
if (key === "post-type") {
delete templateProperties[key];
}
}
return templateProperties;
}
/**
* Sync liked videos into the Indiekit posts collection.
*
@@ -71,7 +93,7 @@ async function snapshotExistingLikes(db, client, accessToken, maxPages) {
* @param {object} opts
* @param {import("mongodb").Db} opts.db
* @param {object} opts.youtubeConfig - endpoint options
* @param {object} opts.publication - Indiekit publication config
* @param {object} opts.publication - Indiekit publication (with store, postTemplate, storeMessageTemplate)
* @param {import("mongodb").Collection} [opts.postsCollection]
* @param {number} [opts.maxPages=3] - max pages to fetch (50 likes/page)
* @returns {Promise<{synced: number, skipped: number, total: number, baselined?: number, error?: string}>}
@@ -163,9 +185,7 @@ export async function syncLikes({ db, youtubeConfig, publication, postsCollectio
? likePostType.post.url.replace("{slug}", slug)
: `${publicationUrl}/likes/${slug}/`;
const postDoc = {
path: postPath,
properties: {
const postProperties = {
"post-type": "like",
"mp-slug": slug,
"like-of": videoUrl,
@@ -181,9 +201,31 @@ export async function syncLikes({ db, youtubeConfig, publication, postsCollectio
"youtube-video-id": videoId,
"youtube-channel": video.channelTitle,
"youtube-thumbnail": video.thumbnail || "",
},
};
// Write markdown file to the store (e.g. GitHub)
if (publication?.postTemplate && publication?.store) {
try {
const templateProperties = getTemplateProperties(postProperties);
const content = await publication.postTemplate(templateProperties);
const message = publication.storeMessageTemplate
? publication.storeMessageTemplate({
action: "create",
result: "created",
fileType: "post",
postType: "like",
})
: `Create like post for ${videoId}`;
await publication.store.createFile(postPath, content, { message });
} catch (storeError) {
console.error(`[YouTube] Failed to write ${postPath} to store:`, storeError.message);
// Continue — still insert into MongoDB so it isn't retried
}
}
// Insert into MongoDB posts collection
const postDoc = { path: postPath, properties: postProperties };
if (postsCollection) {
await postsCollection.insertOne(postDoc);
}
@@ -233,7 +275,7 @@ export function startLikesSync(Indiekit, options) {
if (!db) return;
const postsCollection = Indiekit.config?.application?.collections?.get("posts");
const publication = Indiekit.config?.publication;
const publication = Indiekit.publication || Indiekit.config?.publication;
try {
const result = await syncLikes({