Преглед на файлове

backend: migrate server/joystream to typescript

Joystream Stats преди 4 години
родител
ревизия
55ec1dc7bc
променени са 8 файла, в които са добавени 744 реда и са изтрити 183 реда
  1. 1 0
      package.json
  2. 3 0
      server/config.json
  3. 434 101
      server/joystream/index.ts
  4. 10 46
      server/joystream/lib/announcements.ts
  5. 6 6
      server/joystream/lib/getters.ts
  6. 2 2
      server/joystream/lib/util.ts
  7. 21 28
      server/socket.ts
  8. 267 0
      server/types.ts

+ 1 - 0
package.json

@@ -4,6 +4,7 @@
   "private": true,
   "dependencies": {
     "@joystream/types": "^0.14.0",
+    "axios": "^0.21.1",
     "bootstrap": "^4.3.1",
     "chalk": "^4.1.0",
     "concurrently": "^4.1.1",

+ 3 - 0
server/config.json

@@ -0,0 +1,3 @@
+{
+    "domain": "https://testnet.joystream.org",
+}

+ 434 - 101
server/joystream/index.ts

@@ -1,104 +1,437 @@
+const { Block } = require('../db/models')
 
-const addBlock = async (api, io, header, status) => {
-          const id = Number(header.number)
-        const exists = await Block.findByPk(id)
-        if (exists) return
-        const timestamp = (await api.query.timestamp.now()).toNumber()
-                                     const last = await Block.findByPk(id - 1)
-                                               
-						 const blocktime = last ? timestamp - last.timestamp : 6000
-                                               const author = 	header.author?.toString()
- 			                                         
-        const block = await Block.create({          id,
-          timestamp,
-          blocktime,
-          author
-        })
-        console.log(
-          '[Joystream] block',
-          block.id,
-          block.blocktime,
-          block.author
-        )
-        io.emit('block', block)
-
-						 update() 
-}
-
-const processEvents = (blockHash) => {
-          const blockEvents = api.query.system.events.at(
-            blockHash
-          ) as Vec<EventRecord>
-          let transfers = blockEvents.filter((event) => {
-            return event.section == 'balances' && event.method == 'Transfer'
-          })
-          let validatorRewards = blockEvents.filter((event) => {
-            return event.section == 'staking' && event.method == 'Reward'
-          })
-}
-
-
-const update = () => {
-const id = header.number.toNumber();
-        if (blocks.find((b) => b.id === id)) return;
-        const timestamp = (await api.query.timestamp.now()).toNumber();
-        const duration = timestamp - lastBlock.timestamp;
-        const block: Block = { id, timestamp, duration };
-        blocks = blocks.concat(block);
-        this.setState({ blocks, loading: false });
-        this.save("block", id);
-        this.save("now", timestamp);
-
-        const proposalCount = await get.proposalCount(api);
-        if (proposalCount > this.state.proposalCount) {
-          this.fetchProposal(api, proposalCount);
-          this.setState({ proposalCount });
-        }
-
-        const currentChannel = await get.currentChannelId(api);
-        if (currentChannel > lastChannel)
-          lastChannel = await this.fetchChannels(api, currentChannel);
-
-        const currentCategory = await get.currentCategoryId(api);
-        if (currentCategory > lastCategory)
-          lastCategory = await this.fetchCategories(api, currentCategory);
-
-        const currentPost = await get.currentPostId(api);
-        if (currentPost > lastPost)
-          lastPost = await this.fetchPosts(api, currentPost);
-
-        const currentThread = await get.currentThreadId(api);
-        if (currentThread > lastThread)
-          lastThread = await this.fetchThreads(api, currentThread);
-
-        const postCount = await api.query.proposalsDiscussion.postCount();
-        this.setState({ proposalComments: Number(postCount) });
-
-        lastBlock = block;
-
-        // validators
-        const currentEra = Number(await api.query.staking.currentEra());
-        if (currentEra > era) {
-          era = currentEra;
-          this.fetchStakes(api, era, this.state.validators);
-          this.save("era", era);
-          this.fetchLastReward(api, era - 1);
-        } else if (this.state.lastReward === 0)
-          this.fetchLastReward(api, currentEra);
-
-        this.fetchEraRewardPoints(api, Number(era));
-
-        // check election stage
-        if (id < termEndsAt || id < stageEndsAt) return;
-        const json = stage.toJSON();
-        const key = Object.keys(json)[0];
-        stageEndsAt = json[key];
-        //console.log(id, stageEndsAt, json, key);
-
-        termEndsAt = Number((await api.query.council.termEndsAt()).toJSON());
-        round = Number((await api.query.councilElection.round()).toJSON());
-        stage = await api.query.councilElection.stage();
-        councilElection = { termEndsAt, stage: stage.toJSON(), round };
-        this.setState({ councilElection });
+const get = require('./lib/getters')
+const axios = require('axios')
+const moment = require('moment')
 
+import { VoteKind } from '@joystream/types/proposals'
+import {
+  Api,
+  Handles,
+  IState,
+  Member,
+  Category,
+  Channel,
+  Post,
+  Seat,
+  Thread,
+  ProposalDetail,
+  Status,
+} from '../types'
+
+const addBlock = async (
+  api: Api,
+  io: any,
+  header: { number: number; author: string },
+  status: Status
+): Promise<Status> => {
+  const id = +header.number
+  const exists = await Block.findByPk(id)
+  if (exists) return status
+
+  const timestamp = (await api.query.timestamp.now()).toNumber()
+
+  const last = await Block.findByPk(id - 1)
+
+  const blocktime = last ? timestamp - last.timestamp : 6000
+  const author = header.author?.toString()
+
+  const block = await Block.create({ id, timestamp, blocktime, author })
+  console.log('[Joystream] block', block.id, block.blocktime, block.author)
+  io.emit('block', block)
+  updateAll(api, io, status)
+  return status
+}
+
+const processEvents = (api: Api, blockHash) => {
+  const blockEvents = api.query.system.events.at(blockHash)
+  // TODO as Vec<EventRecord>
+  let transfers = blockEvents.filter((event) => {
+    return event.section == 'balances' && event.method == 'Transfer'
+  })
+  let validatorRewards = blockEvents.filter((event) => {
+    return event.section == 'staking' && event.method == 'Reward'
+  })
+}
+
+// from frontend
+const updateAll = async (api: Api, io: any, status: any) => {
+  const proposalCount = await get.proposalCount(api)
+  if (proposalCount > status.proposalCount) {
+    fetchProposal(api, proposalCount)
+    status.proposalCount = proposalCount
+  }
+
+  const currentChannel = await get.currentChannelId(api)
+  if (currentChannel > status.lastChannel)
+    status.lastChannel = await fetchChannels(api, currentChannel)
+
+  const currentCategory = await get.currentCategoryId(api)
+  if (currentCategory > status.lastCategory)
+    status.lastCategory = await fetchCategories(api, currentCategory)
+
+  const currentPost = await get.currentPostId(api)
+  if (currentPost > status.lastPost)
+    status.lastPost = await fetchPosts(api, currentPost)
+
+  const currentThread = await get.currentThreadId(api)
+  if (currentThread > status.lastThread)
+    status.lastThread = await fetchThreads(api, currentThread)
+
+  const postCount = await api.query.proposalsDiscussion.postCount()
+  // TODO save proposalComments: Number(postCount)
+
+  const currentEra = Number(await api.query.staking.currentEra())
+  if (currentEra > status.era) {
+    status.era = currentEra
+    fetchStakes(api, status.era, status.validators)
+    fetchLastReward(api, status.era - 1)
+  } else if (status.lastReward === 0) fetchLastReward(api, currentEra)
+
+  fetchEraRewardPoints(api, Number(status.era))
+}
+
+const fetchLastReward = async (api: Api, era: number) => {
+  const lastReward = Number(await api.query.staking.erasValidatorReward(era))
+  console.debug(`last reward`, era, lastReward)
+  if (lastReward > 0) {
+  } // TODO save lastReward
+  else fetchLastReward(api, era - 1)
+}
+
+const fetchTokenomics = async () => {
+  console.debug(`Updating tokenomics`)
+  const { data } = await axios.get('https://status.joystream.org/status')
+  if (!data) return
+  // TODO save 'tokenomics', data
+}
+
+const fetchChannels = async (api: Api, lastId: number) => {
+  const channels = [] // TOOD await Channel.findAll()
+  for (let id = lastId; id > 0; id--) {
+    if (channels.find((c) => c.id === id)) continue
+    console.debug(`Fetching channel ${id}`)
+    const data = await api.query.contentWorkingGroup.channelById(id)
+
+    const handle = String(data.handle)
+    const title = String(data.title)
+    const description = String(data.description)
+    const avatar = String(data.avatar)
+    const banner = String(data.banner)
+    const content = String(data.content)
+    const ownerId = Number(data.owner)
+    const accountId = String(data.role_account)
+    const publicationStatus =
+      data.publication_status === 'Public' ? true : false
+    const curation = String(data.curation_status)
+    const createdAt = data.created
+    const principal = Number(data.principal_id)
+
+    const channel = {
+      id,
+      handle,
+      title,
+      description,
+      avatar,
+      banner,
+      content,
+      ownerId,
+      accountId,
+      publicationStatus,
+      curation,
+      createdAt,
+      principal,
+    }
+    // TODO Channel.create(channel)
+  }
+  return lastId
+}
+
+const fetchCategories = async (api: Api, lastId: number) => {
+  const categories = [] // TODO await Category.findAll()
+  for (let id = lastId; id > 0; id--) {
+    if (categories.find((c) => c.id === id)) continue
+    console.debug(`fetching category ${id}`)
+    const data = await api.query.forum.categoryById(id)
+
+    const threadId = Number(data.thread_id)
+    const title = String(data.title)
+    const description = String(data.description)
+    const createdAt = Number(data.created_at.block)
+    const deleted = data.deleted
+    const archived = data.archived
+    const subcategories = Number(data.num_direct_subcategories)
+    const moderatedThreads = Number(data.num_direct_moderated_threads)
+    const unmoderatedThreads = Number(data.num_direct_unmoderated_threads)
+    const position = Number(data.position_in_parent_category)
+    const moderatorId = String(data.moderator_id)
+
+    const category = {
+      id,
+      threadId,
+      title,
+      description,
+      createdAt,
+      deleted,
+      archived,
+      subcategories,
+      moderatedThreads,
+      unmoderatedThreads,
+      position,
+      moderatorId,
+    }
+    //TODO Category.create(
+  }
+  return lastId
+}
+
+const fetchPosts = async (api: Api, lastId: number) => {
+  const posts = [] // TODO Post.findAll()
+  for (let id = lastId; id > 0; id--) {
+    if (posts.find((p) => p.id === id)) continue
+    console.debug(`fetching post ${id}`)
+    const data = await api.query.forum.postById(id)
+
+    const threadId = Number(data.thread_id)
+    const text = data.current_text
+    //const moderation = data.moderation;
+    //const history = data.text_change_history;
+    //const createdAt = moment(data.created_at);
+    const createdAt = data.created_at
+    const authorId = String(data.author_id)
+
+    // TODO Post.create({ id, threadId, text, authorId, createdAt })
+  }
+  return lastId
+}
+
+const fetchThreads = async (api: Api, lastId: number) => {
+  const threads = [] //TODO Thread.findAll()
+  for (let id = lastId; id > 0; id--) {
+    if (threads.find((t) => t.id === id)) continue
+    console.debug(`fetching thread ${id}`)
+    const data = await api.query.forum.threadById(id)
+
+    const title = String(data.title)
+    const categoryId = Number(data.category_id)
+    const nrInCategory = Number(data.nr_in_category)
+    const moderation = data.moderation
+    const createdAt = String(data.created_at.block)
+    const authorId = String(data.author_id)
+
+    const thread = {
+      id,
+      title,
+      categoryId,
+      nrInCategory,
+      moderation,
+      createdAt,
+      authorId,
+    }
+    // TODO Thread.create(
+  }
+  return lastId
+}
+
+const fetchCouncils = async (api: Api, status: any) => {
+  let councils = [] // await Council.findAll()
+  const cycle = 201600
+
+  for (let round = 0; round < status.round; round++) {
+    const block = 57601 + round * cycle
+    if (councils[round] || block > status.block) continue
+
+    console.debug(`Fetching council at block ${block}`)
+    const blockHash = await api.rpc.chain.getBlockHash(block)
+    if (!blockHash) continue
+
+    // TODO Council.create(await api.query.council.activeCouncil.at(blockHash))
+  }
+}
+
+const fetchProposals = async (api: Api) => {
+  const proposalCount = await get.proposalCount(api)
+  for (let i = proposalCount; i > 0; i--) fetchProposal(api, i)
 }
+
+const fetchProposal = async (api: Api, id: number) => {
+  const exists = null // TODO await Proposa.findByPk(id)
+
+  if (exists && exists.stage === 'Finalized')
+    if (exists.votesByAccount && exists.votesByAccount.length) return
+    else return fetchVotesPerProposal(api, exists)
+
+  console.debug(`Fetching proposal ${id}`)
+  const proposal = await get.proposalDetail(api, id)
+  //TODO Proposal.create(proposal)
+  fetchVotesPerProposal(api, proposal)
+}
+
+const fetchVotesPerProposal = async (api: Api, proposal: ProposalDetail) => {
+  const { votesByAccount } = proposal
+  const proposals = [] // TODO await Proposal.findAll()
+  const councils = [] // TODO await Council.findAll()
+
+  if (votesByAccount && votesByAccount.length) return
+
+  console.debug(`Fetching proposal votes (${proposal.id})`)
+  let members = []
+  councils.map((seats) =>
+    seats.forEach(async (seat: Seat) => {
+      if (members.find((member) => member.account === seat.member)) return
+      const member = null // TODO await Member.findOne({ account: seat.member })
+      member && members.push(member)
+    })
+  )
+
+  const { id } = proposal
+  proposal.votesByAccount = await Promise.all(
+    members.map(async (member) => {
+      const vote = await fetchVoteByProposalByVoter(api, id, member.id)
+      return { vote, handle: member.handle }
+    })
+  )
+  // TODO save proposal.votesByAccount
+}
+
+const fetchVoteByProposalByVoter = async (
+  api: Api,
+  proposalId: number,
+  voterId: number
+): Promise<string> => {
+  console.debug(`Fetching vote by ${voterId} for proposal ${proposalId}`)
+  const vote: VoteKind = await api.query.proposalsEngine.voteExistsByProposalByVoter(
+    proposalId,
+    voterId
+  )
+  const hasVoted: number = (
+    await api.query.proposalsEngine.voteExistsByProposalByVoter.size(
+      proposalId,
+      voterId
+    )
+  ).toNumber()
+
+  return hasVoted ? String(vote) : ''
+}
+
+// nominators, validators
+
+const fetchNominators = async (api: Api) => {
+  const nominatorEntries = await api.query.staking.nominators.entries()
+  const nominators = nominatorEntries.map((n: any) => String(n[0].toHuman()))
+  // TODO save nominators
+}
+
+const fetchValidators = async (api: Api) => {
+  // session.disabledValidators: Vec<u32>
+  // TODO check online: imOnline.keys
+  //  imOnline.authoredBlocks: 2
+  // TODO session.currentIndex: 17,081
+  const stashes = await api.derive.staking.stashes()
+  // TODO save stashes
+
+  const validatorEntries = await api.query.session.validators()
+  const validators = await validatorEntries.map((v: any) => String(v))
+  // TODO save validators
+}
+
+const fetchStakes = async (api: Api, era: number, validators: string[]) => {
+  // TODO staking.bondedEras: Vec<(EraIndex,SessionIndex)>
+  console.debug(`fetching stakes`)
+  const stashes = [] // TODO Stash.findAll()
+  if (!stashes) return
+  stashes.forEach(async (validator: string) => {
+    try {
+      const prefs = await api.query.staking.erasValidatorPrefs(era, validator)
+      const commission = Number(prefs.commission) / 10000000
+
+      const data = await api.query.staking.erasStakers(era, validator)
+      let { total, own, others } = data.toJSON()
+      //let { stakes = {} } = [] // TODO fetchStakes()
+      // { total, own, others, commission }
+      // TODO save stakes
+    } catch (e) {
+      console.warn(`Failed to fetch stakes for ${validator} in era ${era}`, e)
+    }
+  })
+}
+
+const fetchEraRewardPoints = async (api: Api, era: number) => {
+  const data = await api.query.staking.erasRewardPoints(era)
+  // TODO save rewardPoints
+}
+
+// accounts
+const fetchMembers = async (api: Api, lastId: number) => {
+  for (let id = lastId; id > 0; id--) {
+    fetchMember(api, id)
+  }
+}
+
+const fetchMemberByAccount = async (
+  api: Api,
+  account: string
+): Promise<Member> => {
+  const exists = null // TODO await Member.findOne({account}
+  if (exists) return exists
+
+  const id = await get.memberIdByAccount(api, account)
+  if (!id)
+    return { id: -1, handle: `unknown`, account, about: ``, registeredAt: 0 }
+  // TODO return member
+}
+
+const fetchMember = async (api: Api, id: number): Promise<Member> => {
+  const exists = null // TODO await Member.findOne({id}
+  if (exists) return exists
+
+  console.debug(`Fetching member ${id}`)
+  const membership = await get.membership(api, id)
+
+  const handle = String(membership.handle)
+  const account = String(membership.root_account)
+  const about = String(membership.about)
+  const registeredAt = Number(membership.registered_at_block)
+  const member = null // TODO await Member.create({ id, handle, account, registeredAt, about })
+  return member
+}
+
+const fetchReports = () => {
+  const domain = `https://raw.githubusercontent.com/Joystream/community-repo/master/council-reports`
+  const apiBase = `https://api.github.com/repos/joystream/community-repo/contents/council-reports`
+
+  const urls: { [key: string]: string } = {
+    alexandria: `${apiBase}/alexandria-testnet`,
+    archive: `${apiBase}/archived-reports`,
+    template: `${domain}/templates/council_report_template_v1.md`,
+  }
+
+  ;['alexandria', 'archive'].map((folder) => fetchGithubDir(urls[folder]))
+
+  fetchGithubFile(urls.template)
+}
+
+const fetchGithubFile = async (url: string): Promise<string> => {
+  const { data } = await axios.get(url)
+  return data
+}
+
+const fetchGithubDir = async (url: string) => {
+  const { data } = await axios.get(url)
+  data.forEach(
+    async (o: {
+      name: string
+      type: string
+      url: string
+      download_url: string
+    }) => {
+      const match = o.name.match(/^(.+)\.md$/)
+      const name = match ? match[1] : o.name
+      if (o.type === 'file') {
+        // TODO save await fetchGithubFile(o.download_url)
+      } else fetchGithubDir(o.url)
+    }
+  )
+}
+
+module.exports = { addBlock }

+ 10 - 46
server/joystream/lib/announcements.ts

@@ -1,4 +1,4 @@
-import { Api, Council, ProposalDetail, Proposals, Summary } from "../types";
+import { Api, Council, ProposalDetail, Proposals, Summary } from "../../types";
 import { BlockNumber } from "@polkadot/types/interfaces";
 import { Channel } from "@joystream/types/augment";
 import { Category, Thread, Post } from "@joystream/types/forum";
@@ -9,8 +9,8 @@ import {
   memberHandleByAccount,
   proposalDetail,
 } from "./getters";
-import { domain } from "../config";
-import moment from "moment";
+
+const { domain } = require( "../../config")
 
 const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
 
@@ -48,42 +48,6 @@ export const channels = async (
   return current;
 };
 
-// announce council change
-export const council = async (
-  api: Api,
-  council: Council,
-  currentBlock: number,
-  sendMessage: (msg: string) => void
-): Promise<Council> => {
-  const round: number = await api.query.councilElection.round();
-  const stage: any = await api.query.councilElection.stage();
-  let stageString = Object.keys(JSON.parse(JSON.stringify(stage)))[0];
-  let msg = "";
-
-  if (!stage || stage.toJSON() === null) {
-    stageString = "elected";
-    const councilEnd: BlockNumber = await api.query.council.termEndsAt();
-    const termDuration: BlockNumber = await api.query.councilElection.newTermDuration();
-    const block = councilEnd.toNumber() - termDuration.toNumber();
-    const remainingBlocks: number = councilEnd.toNumber() - currentBlock;
-    const endDate = moment()
-      // .add(remainingBlocks * 6, "s")
-      .format("DD/MM/YYYY");
-    msg = `<a href="${domain}/#/council/members">Council ${round}</a> elected at block ${block} until block ${councilEnd}. Next election: ${endDate} (${remainingBlocks} blocks)`;
-  } else {
-    if (stageString === "Announcing") {
-      msg = `Announcing election for round ${round} started.<a href="${domain}/#/council/applicants">Apply now!</a>`;
-    } else if (stageString === "Voting") {
-      msg = `Voting stage for council election started. <a href="${domain}/#/council/applicants">Vote now!</a>`;
-    } else if (stageString === "Revealing") {
-      msg = `Revealing stage for council election started. <a href="${domain}/#/council/votes">Don't forget to reveal your vote!</a>`;
-    } else console.log(`[council] unrecognized stage: ${stageString}`);
-  }
-
-  if (round !== council.round && stageString !== council.last) sendMessage(msg);
-  return { round, last: stageString };
-};
-
 // forum
 // announce latest categories
 export const categories = async (
@@ -116,16 +80,16 @@ export const posts = async (
     const post: Post = await query("current_text", () =>
       api.query.forum.postById(id)
     );
-    const replyId: number = post.nr_in_thread.toNumber();
+    const replyId: number = +post.nr_in_thread
     const message: string = post.current_text;
     const excerpt: string = message.substring(0, 100);
-    const threadId: number = post.thread_id.toNumber();
+    const threadId: number = +post.thread_id
     const thread: Thread = await query("title", () =>
       api.query.forum.threadById(threadId)
     );
     const threadTitle: string = thread.title;
     const category: Category = await query("title", () =>
-      categoryById(api, thread.category_id.toNumber())
+      categoryById(api, +thread.category_id)
     );
     const handle = await memberHandleByAccount(api, post.author_id.toJSON());
     const msg = `<b><a href="${domain}/#/members/${handle}">${handle}</a> posted <a href="${domain}/#/forum/threads/${threadId}?replyIdx=${replyId}">${threadTitle}</a> in <a href="${domain}/#/forum/categories/${category.id}">${category.title}</a>:</b>\n\r<i>${excerpt}</i> <a href="${domain}/#/forum/threads/${threadId}?replyIdx=${replyId}">more</a>`;
@@ -152,7 +116,7 @@ export const threads = async (
     const { title, author_id } = thread;
     const handle: string = await memberHandleByAccount(api, author_id.toJSON());
     const category: Category = await query("title", () =>
-      categoryById(api, thread.category_id.toNumber())
+      categoryById(api, +thread.category_id)
     );
     const msg = `Thread ${id}: <a href="${domain}/#/forum/threads/${id}">"${title}"</a> by <a href="${domain}/#/members/${handle}">${handle}</a> in category "<a href="${domain}/#/forum/categories/${category.id}">${category.title}</a>" `;
     messages.push(msg);
@@ -173,7 +137,7 @@ export const proposals = async (
   for (let id: number = +last + 1; id <= current; id++) {
     const proposal: ProposalDetail = await proposalDetail(api, id);
     const { createdAt, message, parameters } = proposal;
-    const votingEndsAt = createdAt + parameters.votingPeriod.toNumber();
+    const votingEndsAt = createdAt + +parameters.votingPeriod
     const msg = `Proposal ${id} <b>created</b> at block ${createdAt}.\r\n${message}\r\nYou can vote until block ${votingEndsAt}.`;
     sendMessage(msg);
     active.push(id);
@@ -185,7 +149,7 @@ export const proposals = async (
     if (stage === "Finalized") {
       let label: string = result;
       if (result === "Approved") {
-        const executed = parameters.gracePeriod.toNumber() > 0 ? false : true;
+        const executed = +parameters.gracePeriod > 0 ? false : true;
         label = executed ? "Executed" : "Finalized";
         if (!executed) executing.push(id);
       }
@@ -200,7 +164,7 @@ export const proposals = async (
     const { exec, finalizedAt, message, parameters } = proposal;
     const execStatus = exec ? Object.keys(exec)[0] : "";
     const label = execStatus === "Executed" ? "has been" : "failed to be";
-    const block = +finalizedAt + parameters.gracePeriod.toNumber();
+    const block = +finalizedAt + +parameters.gracePeriod;
     const msg = `Proposal ${id} <b>${label} executed</b> at block ${block}.\r\n${message}`;
     sendMessage(msg);
     executing = executing.filter((e) => e !== id);

+ 6 - 6
server/joystream/lib/getters.ts

@@ -3,7 +3,7 @@ import fetch from "node-fetch";
 
 //types
 
-import { Api, ProposalArray, ProposalDetail } from "../types";
+import { Api, ProposalArray, ProposalDetail } from "../../types";
 import {
   ChannelId,
   PostId,
@@ -19,7 +19,7 @@ import { AccountId } from "@polkadot/types/interfaces";
 
 export const currentChannelId = async (api: Api): Promise<number> => {
   const id: ChannelId = await api.query.contentWorkingGroup.nextChannelId();
-  return id.toNumber() - 1;
+  return Number(id) - 1;
 };
 
 // members
@@ -64,17 +64,17 @@ export const categoryById = async (api: Api, id: number): Promise<Category> => {
 
 export const currentPostId = async (api: Api): Promise<number> => {
   const postId: PostId = await api.query.forum.nextPostId();
-  return postId.toNumber() - 1;
+  return Number(postId) - 1;
 };
 
 export const currentThreadId = async (api: Api): Promise<number> => {
   const threadId: ThreadId = await api.query.forum.nextThreadId();
-  return threadId.toNumber() - 1;
+  return Number(threadId) - 1;
 };
 
 export const currentCategoryId = async (api: Api): Promise<number> => {
   const categoryId: CategoryId = await api.query.forum.nextCategoryId();
-  return categoryId.toNumber() - 1;
+  return Number(categoryId) - 1;
 };
 
 // proposals
@@ -139,7 +139,7 @@ export const proposalDetail = async (
   const type: string = await getProposalType(api, id);
   const args: string[] = [String(id), title, type, stage, result, author];
   const message: string = formatProposalMessage(args);
-  const createdAt: number = proposal.createdAt.toNumber();
+  const createdAt: number = Number(proposal.createdAt)
 
   return {
     id,

+ 2 - 2
server/joystream/lib/util.ts

@@ -1,5 +1,5 @@
-import { Options, Proposals } from "../types";
-import moment from "moment";
+import { Options, Proposals } from "../../types"
+const  moment  = require("moment")
 
 export const parseArgs = (args: string[]): Options => {
   const inArgs = (term: string): boolean => {

+ 21 - 28
server/socket.ts

@@ -1,31 +1,21 @@
+import { Api, Status } from './types'
+const { types } = require('@joystream/types')
+
 const { Block } = require('./db/models')
-//const chalk = require('chalk')
 
-const { types } = require('@joystream/types')
 const { ApiPromise, WsProvider } = require('@polkadot/api')
-const wsLocation = [
-  'wss://localhost:9933',
-  'wss://rome-rpc-endpoint.joystream.org:9944',
-]
+// TODO allow alternative backends
+const wsLocation = 'ws://localhost:9944'
+// 'wss://rome-rpc-endpoint.joystream.org:9944'
 
-// TODO migrate lib: const { addBlock } = require('./joystream')
+const { addBlock } = require('./joystream')
 
 module.exports = (io) => {
-  const initializeSocket = async () => {
-    console.debug(`[Joystream] Connecting to ${wsLocation}`)
-    try {
-      const provider = new WsProvider(wsLocation)
-      const api = await ApiPromise.create({ provider, types })
-      await api.isReady
-      console.debug(`[Joystream] Connected.`)
-      let status = {}
-      api.derive.chain.subscribeNewHeads(
-        async (h) => {}
-        //(status = (await addBlock(api, h, io, status)) || status)
-      )
-    } catch (e) {
-      return console.log(`[Joystream] Connection failed`)
-    }
+  const handleUpstream = async (api: Api) => {
+    let status: Status = {}
+    api.derive.chain.subscribeNewHeads(
+      async (header) => (status = await addBlock(api, io, header, status))
+    )
   }
 
   io.on('connection', async (socket) => {
@@ -41,14 +31,17 @@ module.exports = (io) => {
     })
   })
 
-  const connectUpstream = () => {
+  const connectUpstream = async () => {
     try {
-      initializeSocket()
+      console.debug(`[Joystream] Connecting to ${wsLocation}`)
+      const provider = new WsProvider(wsLocation)
+      const api = await ApiPromise.create({ provider, types })
+      await api.isReady
+      console.debug(`[Joystream] Connected.`)
+      handleUpstream(api)
     } catch (e) {
-      console.log(`[Joystream] upstream connection failed`, e)
-      //setTimeout(connectUpstream, 1000)
+      //console.log(`[Joystream] upstream connection failed`, e)
     }
   }
-  //connectUpstream()
-  initializeSocket()
+  connectUpstream()
 }

+ 267 - 0
server/types.ts

@@ -0,0 +1,267 @@
+import { ApiPromise } from "@polkadot/api";
+import { MemberId } from "@joystream/types/members";
+import {
+  ProposalParameters,
+  ProposalStatus,
+  VotingResults,
+} from "@joystream/types/proposals";
+import { AccountId, Nominations } from "@polkadot/types/interfaces";
+import { Option } from "@polkadot/types/codec";
+import { StorageKey } from "@polkadot/types/primitive";
+
+export interface Api {
+  query: any;
+  rpc: any;
+  derive: any;
+}
+
+export interface IState {
+  //gethandle: (account: AccountId | string)  => string;
+  connecting: boolean;
+  now: number;
+  block: number;
+  blocks: Block[];
+  nominators: string[];
+  validators: string[];
+  stashes: string[];
+  loading: boolean;
+  councils: Seat[][];
+  councilElection?: { stage: any; round: number; termEndsAt: number };
+  channels: Channel[];
+  categories: Category[];
+  proposals: ProposalDetail[];
+  posts: Post[];
+  threads: Thread[];
+  domain: string;
+  proposalCount: number;
+  proposalPosts: any[];
+  handles: Handles;
+  members: Member[];
+  tokenomics?: Tokenomics;
+  reports: { [key: string]: string };
+  [key: string]: any;
+  stars: { [key: string]: boolean };
+  stakes?: { [key: string]: Stakes };
+  rewardPoints?: RewardPoints;
+  lastReward: number;
+}
+
+export interface RewardPoints {
+  total: number;
+  individual: { [account: string]: number };
+}
+
+export interface Stake {
+  who: string;
+  value: number;
+}
+
+export interface Stakes {
+  total: number;
+  own: number;
+  others: Stake[];
+  commission: number;
+}
+
+export interface Seat {
+  member: string;
+  handle?: string;
+  id?: number;
+  stake: number;
+  backers: Backer[];
+}
+
+export interface Backer {
+  member: string;
+  stake: number;
+}
+
+export interface Council {
+  round: number;
+  last: string;
+}
+
+export interface Options {
+  verbose: number;
+  channel: boolean;
+  council: boolean;
+  forum: boolean;
+  proposals: boolean;
+}
+
+export interface ProposalDetail {
+  createdAt: number;
+  finalizedAt: number;
+  message: string;
+  parameters: ProposalParameters;
+  stage: any;
+  result: string;
+  exec: any;
+  id: number;
+  title: string;
+  description: any;
+  votes: VotingResults;
+  type: string;
+  votesByAccount?: Vote[];
+  author: string;
+  authorId: number;
+}
+
+export interface Vote {
+  vote: string;
+  handle: string;
+}
+
+export type ProposalArray = number[];
+
+export interface ProposalPost {
+  threadId: number;
+  text: string;
+  id: number;
+}
+
+export interface Proposals {
+  current: number;
+  last: number;
+  active: ProposalArray;
+  executing: ProposalArray;
+}
+
+export interface Channel {
+  id: number;
+  handle: string;
+  title: string;
+  description: string;
+  avatar: string;
+  banner: string;
+  content: string;
+  ownerId: number;
+  accountId: string;
+  publicationStatus: boolean;
+  curation: string;
+  createdAt: string;
+  principal: number;
+}
+
+export interface Category {
+  id: number;
+  threadId: number;
+  title: string;
+  description: string;
+  createdAt: number;
+  deleted: boolean;
+  archived: boolean;
+  subcategories: number;
+  unmoderatedThreads: number;
+  moderatedThreads: number;
+  position: number;
+  moderatorId: string;
+}
+
+export interface Post {
+  id: number;
+  text: string;
+  threadId: number;
+  authorId: string;
+  createdAt: { block: number; time: number };
+}
+
+export interface Thread {
+  id: number;
+  title: string;
+  categoryId: number;
+  nrInCategory: number;
+  moderation: string;
+  createdAt: string;
+  authorId: string;
+}
+
+export interface Member {
+  account: string;
+  handle: string;
+  id: number;
+  registeredAt: number;
+  about: string;
+}
+
+export interface Block {
+  id: number;
+  timestamp: number;
+  duration: number;
+}
+
+export interface Summary {
+  blocks: Block[];
+  validators: number[];
+  nominators: number[];
+}
+
+export type NominatorsEntries = [StorageKey, Option<Nominations>][];
+
+export interface ProviderStatus {
+  [propName: string]: boolean;
+}
+
+export interface Handles {
+  [key: string]: string;
+}
+
+export interface Tokenomics {
+  price: string;
+  totalIssuance: string;
+  validators: { total_stake: string };
+  burns: Burn[];
+  exchanges: Exchange[];
+  extecutedBurnsAmount: number;
+}
+
+export interface Burn {
+  amount: number;
+  blockHeight: number;
+  date: string; // "2020-09-21T11:07:54.000Z"
+  logTime: string; //"2020-09-21T11:08:54.091Z"
+}
+
+export interface Exchange {
+  amount: number;
+  amountUSD: number;
+  blockHeight: number;
+  date: string; // "2020-09-21T11:07:48.000Z"
+  logTime: string; // "2020-09-21T11:08:48.552Z"
+  price: number; // 0.000053676219442924057
+  recipient: string; //"5D5PhZQNJzcJXVBxwJxZcsutjKPqUPydrvpu6HeiBfMaeKQu"
+  sender: string; // "5DACzSg65taZ2NRktUtzBjhLZr8H5T8rwNoZUng9gQV6ayqT"
+  senderMemo: string; //"4Testing1337SendToBurnerAddressHopingItWorksOfc5D5PhZQNJzcJXVBxwJxZcsutjKPqUPydrvpu6HeiBfMaeKQu"
+  status: string; // FINALIZED | PENDING
+  xmrAddress: string; //"No address found"
+}
+
+export interface Event {
+  text: string;
+  date: number;
+  category: {
+    tag: string;
+    color: string;
+  };
+  link: {
+    url: string;
+    text: string;
+  };
+}
+
+export interface CalendarItem {
+  id: number;
+  group: number;
+  title: string;
+  start_time: number;
+  end_time: number;
+}
+
+export interface CalendarGroup {
+  id: number;
+  title: string;
+}
+
+export interface Status {
+  
+}