Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions scripts/fix-twins-post-topics.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#!/usr/bin/env node
/**
* Fix: migrate-twins-post.js passed a bare documentId array for the `topics`
* many-to-many relation, which Strapi's document service silently mishandled
* (orphaned link rows with no twins_post_id). Reconnects each post to its
* topics using `{ connect: [...] }`, which links correctly.
*
* Topic slugs below are recovered from the original post frontmatter
* (twins-in-the-loop git history), since they were never persisted.
*
* Usage: node scripts/fix-twins-post-topics.js
*/

const POST_TOPICS = {
'ai-and-datacenter-conversations': ['ai', 'data-centers'],
'arm-owns-the-datacenter': ['hardware', 'data-centers'],
'arms-next-chapter': ['hardware'],
'ask-me-about-the-weather': ['ai'],
'i-invented-bare-metal': ['hardware'],
'implications-of-geopolitical-decoupling': ['infrastructure'],
'impossible-to-buy-datacenter-space': ['data-centers'],
'live-coding-a-cli-in-rust': ['infrastructure'],
'nobody-cares-about-gandalf': ['infrastructure'],
'on-device-ai': ['ai', 'hardware'],
'reading-500-incident-reports': ['infrastructure'],
'reduced-deploy-times': ['infrastructure'],
'simplify-then-add-lightness': ['hardware'],
'taking-writing-lessons': ['ai'],
'zero-downtime-schema-migrations': ['infrastructure'],
};

async function fixTopics(app) {
const topicsAttribute = app.db.metadata.get('api::twins-post.twins-post').attributes.topics;
const joinTableName = topicsAttribute?.joinTable?.name;
const postJoinColumn = topicsAttribute?.joinTable?.joinColumn?.name;

if (!joinTableName || !postJoinColumn) {
throw new Error('Could not resolve twins-post topics join table metadata');
}

// Clear only the orphaned link rows left by the original migration.
await app.db.connection(joinTableName).whereNull(postJoinColumn).del();
Comment on lines +41 to +42

for (const [slug, topicSlugs] of Object.entries(POST_TOPICS)) {
const topics = await app.documents('api::topic.topic').findMany({
filters: { slug: { $in: topicSlugs } },
});

Comment thread
Copilot marked this conversation as resolved.
const topicsBySlug = new Map(topics.map((topic) => [topic.slug, topic]));
const missingSlugs = topicSlugs.filter((s) => !topicsBySlug.has(s));
if (missingSlugs.length) {
throw new Error(`Missing topic(s) for "${slug}": ${missingSlugs.join(', ')}`);
}

const topicDocumentIds = topicSlugs.map((s) => topicsBySlug.get(s).documentId);
for (const status of ['draft', 'published']) {
const post = await app.documents('api::twins-post.twins-post').findFirst({
filters: { slug },
status,
});
if (!post) {
console.warn(`No ${status} version found for "${slug}"`);
continue;
}
await app.documents('api::twins-post.twins-post').update({
documentId: post.documentId,
data: { topics: { connect: topicDocumentIds } },
status,
});
}
console.log(`Linked "${slug}" to [${topicSlugs.join(', ')}]`);
}
}

async function main() {
const { createStrapi, compileStrapi } = require('@strapi/strapi');

const appContext = await compileStrapi();
const app = await createStrapi(appContext).load();

app.log.level = 'error';

await fixTopics(app);
await app.destroy();

process.exit(0);
}

main().catch((error) => {
console.error(error);
process.exit(1);
});
68 changes: 68 additions & 0 deletions scripts/grant-twins-public-permissions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/usr/bin/env node
/**
* Grant the public role read access to the specified content types.
* Idempotent: skips any action that's already granted.
*
* Usage:
* node scripts/grant-twins-public-permissions.js api::article.article api::category.category
*/

const contentTypeUids = process.argv.slice(2);

function getActions(app) {
if (contentTypeUids.length === 0) {
throw new Error(
'Provide one or more content-type UIDs, e.g. api::article.article api::category.category'
);
}

return contentTypeUids.flatMap((uid) => {
if (!app.contentTypes[uid]) {
throw new Error(`Content type "${uid}" is not registered in this project`);
}

return [`${uid}.find`, `${uid}.findOne`];
});
}

async function grantPermissions(app) {
const publicRole = await app.query('plugin::users-permissions.role').findOne({
where: { type: 'public' },
});
if (!publicRole) {
throw new Error('Public role not found');
}

for (const action of getActions(app)) {
const existing = await app.query('plugin::users-permissions.permission').findOne({
where: { action, role: publicRole.id },
});
if (existing) {
console.log(`Skipping "${action}" (already granted)`);
continue;
}
await app.query('plugin::users-permissions.permission').create({
data: { action, role: publicRole.id },
});
console.log(`Granted "${action}"`);
}
}

async function main() {
const { createStrapi, compileStrapi } = require('@strapi/strapi');

const appContext = await compileStrapi();
const app = await createStrapi(appContext).load();

app.log.level = 'error';

await grantPermissions(app);
await app.destroy();

process.exit(0);
}

main().catch((error) => {
console.error(error);
process.exit(1);
});
53 changes: 53 additions & 0 deletions scripts/republish-twins-post.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/usr/bin/env node
/**
* Fix: migrate-twins-post.js created entries with status "published", but the
* published version ended up without its topics/blocks/cover relations
* (only the draft version got them). Republishing from the current draft
* copies its content into the published version.
*
* Usage: node scripts/republish-twins-post.js
*/

const CONTENT_TYPE_UID = 'api::twins-post.twins-post';

async function republishAll(app) {
const drafts = await app.documents(CONTENT_TYPE_UID).findMany({
status: 'draft',
});

for (const draft of drafts) {
const published = await app.documents(CONTENT_TYPE_UID).findFirst({
documentId: draft.documentId,
status: 'published',
});
Comment on lines +19 to +22

if (!published) {
console.log(`Skipping "${draft.slug}" (no published version to repair)`);
continue;
}

await app.documents(CONTENT_TYPE_UID).publish({
documentId: draft.documentId,
});
Comment on lines +29 to +31
console.log(`Republished "${draft.slug}"`);
}
}

async function main() {
const { createStrapi, compileStrapi } = require('@strapi/strapi');

const appContext = await compileStrapi();
const app = await createStrapi(appContext).load();

app.log.level = 'error';

await republishAll(app);
await app.destroy();

process.exit(0);
}

main().catch((error) => {
console.error(error);
process.exit(1);
});