Skip to content
18 changes: 18 additions & 0 deletions src/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// @ts-check

import { ApiPromise, WsProvider } from '@polkadot/api';
import { registerJoystreamTypes } from '@joystream/types';

export default async function create_api () {
// Initialise the provider to connect to the local node
const provider = new WsProvider('ws://127.0.0.1:9944');

// register types before creating the api
registerJoystreamTypes();

// Create the API and wait until ready
let api = await ApiPromise.create({ provider });
await api.isReady;

return api;
}
162 changes: 162 additions & 0 deletions src/export_forum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import create_api from './api';
import { ApiPromise } from '@polkadot/api';
import { PostId, ThreadId, BlockAndTime } from '@joystream/types/common'
import { Post, CategoryId, Category,
Thread, OptionModerationAction, VecPostTextChange,
OptionChildPositionInParentCategory, ModerationAction
} from '@joystream/types/forum';
import { Codec, CodecArg } from '@polkadot/types/types';
import { Text, bool as Bool, u32, Option, u64 } from '@polkadot/types';

// Note: Codec.toHex() re-encodes the value, based on how the type
// was registered. It does NOT produce the same value read from storage
// unless it was correctly defined with exact match.
// Also toJSON() behaves similarly., and special case for types that are registered Vec<u8> vs Text
// `Vec<u8>` produces a json array of numbers (byte array), `Text` produces a json string

main()

async function main () {
const api = await create_api();

const categories = await get_all_categories(api);
const posts = await get_all_posts(api);
const threads = await get_all_threads(api);

let forum_data = {
categories: categories.map(category => category.toHex()),
posts: posts.map(post => post.toHex()),
threads: threads.map(thread => thread.toHex()),
};

console.log(JSON.stringify(forum_data));

api.disconnect();
}

// Fetches a value from map directly from storage and through the query api.
// It ensures the value actually exists in the map
async function get_forum_checked_storage<T extends Codec>(api: ApiPromise, map: string, id: CodecArg) : Promise<T> {
const key = api.query.forum[map].key(id);
const raw_value = await api.rpc.state.getStorage(key) as unknown as Option<T>;

if (raw_value.isNone) {
console.error(`Error: value does not exits: ${map} key: ${id}`);
process.exit(-1);
} else {
return (await api.query.forum[map](id) as T)
}
}

async function get_all_posts(api: ApiPromise) {
let first = 1;
let next = (await api.query.forum.nextPostId() as PostId).toNumber();

let posts = [];

for (let id = first; id < next; id++ ) {
let post = await get_forum_checked_storage<Post>(api, 'postById', id) as Post;

// Transformation to a value that makes sense in a new chain.
post = new Post({
id: post.id,
thread_id: post.thread_id,
nr_in_thread: post.nr_in_thread,
current_text: new Text(post.current_text),
moderation: moderationActionAtBlockOne(post.moderation),
// No reason to preserve change history
text_change_history: new VecPostTextChange(),
author_id: post.author_id,
created_at: new BlockAndTime({
// old block number on a new chain doesn't make any sense
block: new u32(1),
time: new u64(post.created_at.momentDate.valueOf())
}),
});

posts.push(post)
}

return posts;
}

async function get_all_categories(api: ApiPromise) {
let first = 1;
let next = (await api.query.forum.nextCategoryId() as CategoryId).toNumber();

let categories = [];

for (let id = first; id < next; id++ ) {
let category = await get_forum_checked_storage<Category>(api, 'categoryById', id) as Category;

category = new Category({
id: new CategoryId(category.id),
title: new Text(category.title),
description: new Text(category.description),
created_at: new BlockAndTime({
// old block number on a new chain doesn't make any sense
block: new u32(1),
time: new u64(category.created_at.momentDate.valueOf())
}),
deleted: new Bool(category.deleted),
archived: new Bool(category.archived),
num_direct_subcategories: new u32(category.num_direct_subcategories),
num_direct_unmoderated_threads: new u32(category.num_direct_unmoderated_threads),
num_direct_moderated_threads: new u32(category.num_direct_moderated_threads),
position_in_parent_category: new OptionChildPositionInParentCategory(category.position_in_parent_category),
moderator_id: category.moderator_id,
});

categories.push(category)
}

return categories;
}

async function get_all_threads(api: ApiPromise) {
let first = 1;
let next = (await api.query.forum.nextThreadId() as ThreadId).toNumber();

let threads = [];

for (let id = first; id < next; id++ ) {
let thread = await get_forum_checked_storage<Thread>(api, 'threadById', id) as Thread;

thread = new Thread({
id: new ThreadId(thread.id),
title: new Text(thread.title),
category_id: new CategoryId(thread.category_id),
nr_in_category: new u32(thread.nr_in_category),
moderation: moderationActionAtBlockOne(thread.moderation),
num_unmoderated_posts: new u32(thread.num_unmoderated_posts),
num_moderated_posts: new u32(thread.num_moderated_posts),
created_at: new BlockAndTime({
// old block number on a new chain doesn't make any sense
block: new u32(1),
time: new u64(thread.created_at.momentDate.valueOf())
}),
author_id: thread.author_id,
});

threads.push(thread);
}

return threads;
}

function moderationActionAtBlockOne(
action: ModerationAction | undefined) : OptionModerationAction {

if(!action) {
return new OptionModerationAction();
} else {
return new OptionModerationAction(new ModerationAction({
moderated_at: new BlockAndTime({
block: new u32(1),
time: new u64(action.moderated_at.momentDate.valueOf()),
}),
moderator_id: action.moderator_id,
rationale: new Text(action.rationale)
}));
}
}
50 changes: 50 additions & 0 deletions src/export_members.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import create_api from './api'
import { ApiPromise } from '@polkadot/api'
import { MemberId, Profile } from '@joystream/types/members'
import { Option, u32, u64 } from '@polkadot/types/'
import { BlockAndTime } from '@joystream/types/common'

import { GenesisMember } from './genesis_member'

main()

async function main () {
const api = await create_api();

const members = await get_all_members(api);

console.log(JSON.stringify(members));

api.disconnect();
}

async function get_all_members(api: ApiPromise) : Promise<GenesisMember[]> {
const first = 0
const next = (await api.query.members.membersCreated() as MemberId).toNumber();

let members = [];

for (let id = first; id < next; id++ ) {
const profile = await api.query.members.memberProfile(id) as Option<any>;

if (profile.isSome) {
const p = profile.unwrap() as Profile;
members.push(new GenesisMember({
member_id: new MemberId(id),
root_account: p.root_account,
controller_account: p.controller_account,
handle: p.handle,
avatar_uri: p.avatar_uri,
about: p.about,
registered_at_time: fixedTimestamp(p.registered_at_time)
}));
}
}

return members;
}

function fixedTimestamp(time: u64) {
const blockAndTime = new BlockAndTime({ block: new u32(1), time })
return new u64(blockAndTime.momentDate.valueOf())
}
Loading