For anyone interrested: I managed do it by writing directly to the DB (using typescripts mysql2 package). In case anyone attempts a similar thing someday, this is the essence of it:
import * as mysql from 'mysql2';
//type in which the jsondata of the old forum is present
type Category = { name: string, threads: Thread[] }
type Thread = { name: string, posts: Post[] }
type Post = { userName: string, time: string, content: string }
const connection = mysql.createConnection({
host: "MY-SERVERS-IP",
user: "simon",
password: "MY-DB-PASSWORD",
database: "flarum_db"
});
import f0 from "./data/forumData-0";
import f1 from "./data/forumData-1";
//...
const categories: Category[] = [f0. f1, /*....*/];
const oldNamesToNewUserId = new Map<string, number>([
['OldUserName 1', 0],
['OldUserName 2', 1]
//...,
]);
const oldCategoryNameToNewTagId = new Map<string, number>([
["Old Forum Category 1", 2],
["Old Forum Category 2", 3],
//...
]);
async function transferData(): Promise<void> {
for (const category of categories) {
const tagId = oldCategoryNameToNewTagId.get(category.name);
for (const thread of category.threads) {
// create discussion
const title = thread.name;
const user_id = oldNamesToNewUserId.get(thread.posts[0].userName) ?? 17; const last_posted_user_id = oldNamesToNewUserId.get(thread.posts[thread.posts.length - 1].userName) ?? 17;
//parseOldTimestamp just converts the time string i got from the old forum to this format: 2025-03-08 19:04:13
const created_at = parseOldTimestamp(thread.posts[0].time);
const last_posted_at = parseOldTimestamp(thread.posts[thread.posts.length - 1].time);
const comment_count = thread.posts.length;
const first_post_id = null; // set after creating posts
const slug = getSlug(thread.name);
const discussionResponse = await queryAsync(`
INSERT INTO flrm_discussions
(title, user_id, last_posted_user_id, created_at, last_posted_at, comment_count, first_post_id, slug)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`, [title, user_id, last_posted_user_id, created_at, last_posted_at, comment_count, first_post_id, slug]);
const post_discussion_id = (discussionResponse as mysql.ResultSetHeader).insertId;
//set tag
await queryAsync(`
INSERT INTO flrm_discussion_tag
(discussion_id, tag_id, created_at)
VALUES (?, ?, ?)
`, [post_discussion_id, tagId, created_at]);
// create posts
for (let i = 0; i < thread.posts.length; i++) {
const post = thread.posts[i];
const p_created_at = parseOldTimestamp(post.time);
const p_user_id = oldNamesToNewUserId.get(post.userName);
const p_content = `<t>${post.content}</t>`;
const postResponse = await queryAsync(`
INSERT INTO flrm_posts
(discussion_id, number, created_at, user_id, type, content, is_private, is_approved)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`, [post_discussion_id, i + 1, p_created_at, p_user_id, 'comment', p_content, 0, 1]);
// connect first post to discussion
if (i == 0) {
await queryAsync(`
UPDATE flrm_discussions
SET first_post_id = ?
WHERE id = ?;`,
[(postResponse as mysql.ResultSetHeader).insertId, post_discussion_id])
}
}
}
}
}
export async function connectAsync() {
await new Promise<void>((resolve, reject) => connection.connect(err => (err ? reject(err) : resolve())));
}
function queryAsync(sql: string, params: any[] = []) {
return new Promise((resolve, reject) => {
connection.query(sql, params, (error, results) => {
if (error) {
reject(error);
} else {
resolve(results);
}
});
});
}
function getSlug(str: string) {
return str
.toLowerCase()
.replace("ä", "ae")
.replace("ö", "oe")
.replace("ü", "ue")
.trim()
.replace(/[^\w\s-]/g, '')
.replace(/[\s_-]+/g, '-')
.replace(/^-+|-+$/g, '');
}
async function main() {
try {
await connectAsync();
await transferData();
} catch (err) {
console.error('Error:', err);
} finally {
connection.end(); // Ensure connection is closed
}
}
main();
definitely not the most beautiful code ever written but it just needed to run once and now I can throw it away