Files
indiekit-endpoint-activitypub/lib/controllers/migrate.js
Ricardo de00d3a16c fix: payload too large error and add migration logging
- Add express.urlencoded({ limit: '5mb' }) to migration POST route
  to handle large CSV files (default 100KB was too small)
- Add per-handle progress logging to console for monitoring imports
- Log failed handles with reasons (WebFinger failure, no AP link, etc.)
- Show failed handles in the UI result notification
- Use error notification type when all imports fail
2026-02-19 10:14:23 +01:00

127 lines
3.9 KiB
JavaScript

/**
* Migration controller — handles Mastodon account migration UI.
*
* GET: shows the 3-step migration page
* POST: processes alias update or CSV file import
*/
import {
parseMastodonFollowingCsv,
parseMastodonFollowersList,
bulkImportFollowing,
bulkImportFollowers,
} from "../migration.js";
export function migrateGetController(mountPath, pluginOptions) {
return async (request, response, next) => {
try {
response.render("activitypub-migrate", {
title: response.locals.__("activitypub.migrate.title"),
mountPath,
currentAlias: pluginOptions.alsoKnownAs || "",
result: null,
});
} catch (error) {
next(error);
}
};
}
export function migratePostController(mountPath, pluginOptions) {
return async (request, response, next) => {
try {
const { application } = request.app.locals;
const action = request.body.action;
let result = null;
if (action === "alias") {
// Update alsoKnownAs on the actor config
const aliasUrl = request.body.aliasUrl?.trim();
if (aliasUrl) {
pluginOptions.alsoKnownAs = aliasUrl;
result = {
type: "success",
text: response.locals.__("activitypub.migrate.aliasSuccess"),
};
}
}
if (action === "import") {
const followingCollection =
application?.collections?.get("ap_following");
const followersCollection =
application?.collections?.get("ap_followers");
const importFollowing = request.body.importTypes?.includes("following");
const importFollowers = request.body.importTypes?.includes("followers");
// Read file content (submitted as text via client-side FileReader)
const fileContent = request.body.csvContent?.trim();
if (!fileContent) {
result = {
type: "error",
text: response.locals.__("activitypub.migrate.errorNoFile"),
};
} else {
let followingResult = { imported: 0, failed: 0, errors: [] };
let followersResult = { imported: 0, failed: 0, errors: [] };
if (importFollowing && followingCollection) {
const handles = parseMastodonFollowingCsv(fileContent);
console.log(`[ActivityPub] Migration: parsed ${handles.length} following handles from CSV`);
followingResult = await bulkImportFollowing(
handles,
followingCollection,
);
}
if (importFollowers && followersCollection) {
const entries = parseMastodonFollowersList(fileContent);
console.log(`[ActivityPub] Migration: parsed ${entries.length} follower entries from CSV`);
followersResult = await bulkImportFollowers(
entries,
followersCollection,
);
}
const totalFailed =
followingResult.failed + followersResult.failed;
const allErrors = [
...followingResult.errors,
...followersResult.errors,
];
let text = response.locals
.__("activitypub.migrate.success")
.replace("%d", followingResult.imported)
.replace("%d", followersResult.imported)
.replace("%d", totalFailed);
if (allErrors.length > 0) {
text += " " + response.locals
.__("activitypub.migrate.failedList")
.replace("%s", allErrors.join(", "));
}
result = {
type: totalFailed > 0 && followingResult.imported + followersResult.imported === 0
? "error"
: "success",
text,
};
}
}
response.render("activitypub-migrate", {
title: response.locals.__("activitypub.migrate.title"),
mountPath,
currentAlias: pluginOptions.alsoKnownAs || "",
result,
});
} catch (error) {
next(error);
}
};
}