Browse Source

backend: refactor joystream/index.ts

Joystream Stats 4 years ago
parent
commit
a5022ed0c2
5 changed files with 386 additions and 369 deletions
  1. 1 1
      server/db/models/event.ts
  2. 160 190
      server/joystream/index.ts
  3. 38 0
      server/joystream/lib/github.ts
  4. 8 4
      server/socket.ts
  5. 179 174
      server/types.ts

+ 1 - 1
server/db/models/event.ts

@@ -9,7 +9,7 @@ const Event = db.define('event', {
   },
   section: DataTypes.STRING,
   method: DataTypes.STRING,
-  data: DataTypes.STRING,
+  data: DataTypes.TEXT,
 })
 
 export default Event

+ 160 - 190
server/joystream/index.ts

@@ -12,21 +12,11 @@ import {
   Proposal,
   Thread,
 } from '../db/models'
-const models: { [key: string]: any } = {
-  channel: Channel,
-  proposal: Proposal,
-  category: Category,
-  thread: Thread,
-  post: Post,
-  block: Block,
-  council: Council,
-  member: Member,
-  era: Era,
-}
-
 import * as get from './lib/getters'
+//import {fetchReports} from './lib/github'
 import axios from 'axios'
 import moment from 'moment'
+import chalk from 'chalk'
 
 import { VoteKind } from '@joystream/types/proposals'
 import { EventRecord } from '@polkadot/types/interfaces'
@@ -50,50 +40,44 @@ import { AccountId, Moment, ActiveEraInfo } from '@polkadot/types/interfaces'
 import Option from '@polkadot/types/codec/Option'
 import { Vec } from '@polkadot/types'
 
-// queuing
+const DELAY = 0 // ms
 let lastUpdate = 0
-const queue: any[] = []
-let inProgress = false
-const enqueue = (fn: any) => {
-  queue.push(fn)
-  processNext()
-}
-const processNext = async () => {
-  if (inProgress) return
-  inProgress = true
-  const task = queue.pop()
-  if (task) await task()
+let queuedAll = false
+let processing = false
+let queue: any[] = []
+let fetching = ''
 
-  inProgress = false
-  //processNext()
-  //return queue.length
-}
+const getBlockHash = (api: Api, blockId: number) =>
+  api.rpc.chain.getBlockHash(blockId)
 
-const save = async (model: any, data: any) => {
-  const Model = models[model]
-  try {
-    const exists = await Model.findByPk(data.id)
-    if (exists) return exists.update(data)
-  } catch (e) {}
-  //console.debug(`saving ${data.id}`, `queued tasks: ${queue.length}`)
-  try {
-    return Model.create(data)
-  } catch (e) {
-    console.warn(`Failed to save ${Model}`, e.message)
-  }
-}
+const getEraAtBlock = (api: Api, hash: string) =>
+  api.query.staking.activeEra.at(hash)
 
 const addBlock = async (
   api: Api,
   io: any,
   header: { number: number; author: string },
-  status: Status
+  status: Status = {
+    era: 0,
+    members: 0,
+    channels: 0,
+    categories: 0,
+    threads: 0,
+    posts: 0,
+    proposals: 0,
+    proposalPosts: 0,
+  }
 ): Promise<Status> => {
   const id = +header.number
   const last = await Block.findByPk(id - 1)
+  const exists = await Block.findByPk(id)
+  if (exists) {
+    console.error(`TODO handle fork`, String(header.author))
+    return status
+  }
   const timestamp = moment.utc(await api.query.timestamp.now()).valueOf()
   const blocktime = last ? timestamp - last.timestamp : 6000
-  const block = await save('block', { id, timestamp, blocktime })
+  const block = await Block.create({ id, timestamp, blocktime })
   io.emit('block', block)
 
   const author = header.author?.toString()
@@ -102,15 +86,18 @@ const addBlock = async (
   updateBalances(api, id)
 
   const currentEra = Number(await api.query.staking.currentEra())
-  const era = await save('era', { id: currentEra })
-  era.addBlock(block.id)
+  Era.findOrCreate({ where: { id: currentEra } }).then(() =>
+    block.setEra(currentEra)
+  )
 
   const handle = member ? member.handle : author
-  const queued = `(queued: ${queue.length})`
-  console.log(`[Joystream] block ${block.id} ${handle} ${queued}`)
+  const f = fetching !== '' ? `, fetching ${fetching}` : ''
+  const q = queue.length ? ` (${queue.length} queued${f})` : ''
+  console.log(`[Joystream] block ${block.id} ${handle}${q}`)
 
-  await processEvents(api, id)
-  return updateEra(api, io, status, currentEra)
+  processEvents(api, id)
+  //updateEra(api, io, status, currentEra)
+  return updateStatus(api, status, currentEra)
 }
 
 const addBlockRange = async (
@@ -153,7 +140,7 @@ const addBlockRange = async (
 
     const timestamp = (await api.query.timestamp.now.at(hash)) as Moment
     const date = new Date(timestamp.toNumber())
-    await save('era', {
+    await Era.create({
       id: blockEra.unwrap().index.toNumber(),
       waiting: waiting,
       actives: actives,
@@ -165,8 +152,62 @@ const addBlockRange = async (
   }
 }
 
-const getBlockHash = (api: Api, blockId: number) =>
-  api.rpc.chain.getBlockHash(blockId)
+const updateStatus = async (api: Api, old: Status, era: number) => {
+  const status = {
+    era,
+    members: (await api.query.members.nextMemberId()) - 1,
+    channels: await get.currentChannelId(api),
+
+    categories: await get.currentCategoryId(api),
+    threads: await get.currentThreadId(api),
+    posts: await get.currentPostId(api),
+
+    proposals: await get.proposalCount(api),
+    proposalPosts: await api.query.proposalsDiscussion.postCount(),
+  }
+  if (!queuedAll) fetchAll(api, status)
+  else {
+    // TODO catch if more than one are added
+    status.members > old.members && fetchMember(api, status.members)
+    status.posts > old.posts && fetchPost(api, status.posts)
+    status.proposals > old.proposals && fetchProposal(api, status.proposals)
+    status.channels > old.channels && fetchChannel(api, status.channels)
+    status.categories > old.categories && fetchCategory(api, status.categories)
+  }
+  return status
+}
+
+const fetchAll = async (api: Api, status: Status) => {
+  // trying to avoid SequelizeUniqueConstraintError
+
+  for (let id = status.members; id > 0; id--) {
+    queue.push(() => fetchMember(api, id))
+  }
+  for (let id = status.posts; id > 0; id--) {
+    queue.push(() => fetchPost(api, id))
+  }
+  for (let id = status.proposals; id > 0; id--) {
+    queue.push(() => fetchProposal(api, id))
+  }
+  for (let id = status.channels; id > 0; id--) {
+    queue.push(() => fetchChannel(api, id))
+  }
+  for (let id = status.categories; id > 0; id--) {
+    queue.push(() => fetchCategory(api, id))
+  }
+  queuedAll = true
+  processNext()
+}
+
+const processNext = async () => {
+  if (processing) return
+  processing = true
+  const task = queue.pop()
+  if (!task) return
+  const result = await task()
+  processing = false
+  setTimeout(() => processNext(), DELAY)
+}
 
 const processEvents = async (api: Api, blockId: number) => {
   const blockHash = await getBlockHash(api, blockId)
@@ -175,6 +216,7 @@ const processEvents = async (api: Api, blockId: number) => {
     let { section, method, data } = event
     Event.create({ blockId, section, method, data: JSON.stringify(data) })
   })
+  // TODO catch votes, posts, proposals?
 }
 
 const updateEra = async (api: Api, io: any, status: any, era: number) => {
@@ -188,10 +230,9 @@ const updateEra = async (api: Api, io: any, status: any, era: number) => {
   // imOnline.authoredBlocks: 2
   // session.currentIndex: 17,081
 
-  const lastReward = Number(await api.query.staking.erasValidatorReward(era))
-  console.debug(`last reward`, era, lastReward)
-  if (lastReward > 0) {
-  } // TODO save lastReward
+  //const lastReward = Number(await api.query.staking.erasValidatorReward(era))
+  //console.debug(`last reward`, era, lastReward)
+  //if (lastReward > 0) {} // TODO save lastReward
 
   const nominatorEntries = await api.query.staking.nominators.entries()
   const nominators = nominatorEntries.map((n: any) => String(n[0].toHuman()))
@@ -202,9 +243,8 @@ const updateEra = async (api: Api, io: any, status: any, era: number) => {
 
   // TODO staking.bondedEras: Vec<(EraIndex,SessionIndex)>
   const stashes = await api.derive.staking.stashes()
-  console.debug(`fetching stakes`)
-
-  if (!stashes) return
+  fetching = `stakes`
+  //if (!stashes) return
 
   for (let validator of stashes) {
     try {
@@ -217,18 +257,6 @@ const updateEra = async (api: Api, io: any, status: any, era: number) => {
       console.warn(`Failed to fetch stakes for ${validator} in era ${era}`, e)
     }
   }
-
-  return {
-    members: (await api.query.members.nextMemberId()) - 1,
-    categories: await get.currentCategoryId(api),
-    threads: await get.currentThreadId(api),
-    proposals: await get.proposalCount(api),
-    channels: await get.currentChannelId(api),
-    posts: await get.currentPostId(api),
-    proposalPosts: await api.query.proposalsDiscussion.postCount(),
-    queued: queue.length,
-    era,
-  }
 }
 
 const validatorStatus = async (api: Api, blockId: number) => {
@@ -252,6 +280,7 @@ const getAccountAtBlock = (
   account: string
 ): Promise<AccountInfo> => api.query.system.account.at(hash, account)
 
+// TODO when to cal
 const fetchAccounts = async (api: Api, blockId: number) => {
   api.query.system.account.entries().then((account: any) => {
     const address = account[0].toHuman()[0]
@@ -288,88 +317,68 @@ const fetchTokenomics = async () => {
 }
 
 const fetchChannel = async (api: Api, id: number) => {
+  if (id <= 0) return
   const exists = await Channel.findByPk(id)
   if (exists) return exists
 
-  console.debug(`Fetching channel ${id}`)
+  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 { handle, title, description, avatar, banner, content, created } = data
+  //const accountId = String(data.role_account)
   const channel = {
     id,
-    handle,
-    title,
-    description,
-    avatar,
-    banner,
-    content,
-    publicationStatus,
-    curation,
-    createdAt,
-    principal,
+    handle: String(handle),
+    title: String(title),
+    description: String(description),
+    avatar: String(avatar),
+    banner: String(banner),
+    content: String(content),
+    publicationStatus: data.publication_status === 'Public' ? true : false,
+    curation: String(data.curation_status),
+    createdAt: +created,
+    principal: Number(data.principal_id),
   }
-  const chan = await save('channel', channel)
-  const owner = await fetchMember(api, ownerId)
+  const chan = await Channel.create(channel)
+  const owner = await fetchMember(api, data.owner)
   chan.setOwner(owner)
-  if (id > 1) fetchChannel(api, id - 1)
   return chan
 }
 
 const fetchCategory = async (api: Api, id: number) => {
-  const exists = await Category.findByPk(id)
+  if (id <= 0) return
+  const exists = await Category.findByPk(+id)
   if (exists) return exists
 
-  console.debug(`fetching category ${id}`)
+  fetching = `category ${id}`
   const data = await api.query.forum.categoryById(id)
-
-  const threadId = +data.thread_id
-  const title = String(data.title)
-  const description = String(data.description)
-  const createdAt = +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 = +data.position_in_parent_category // TODO sometimes NaN
+  const { title, description, deleted, archived } = data
+  const threadId = +data.thread_id // TODO needed?
   const moderator: string = String(data.moderator_id) // account
 
   const cat = {
     id,
     title,
     description,
-    createdAt,
+    createdAt: +data.created_at.block,
     deleted,
     archived,
-    subcategories,
-    moderatedThreads,
-    unmoderatedThreads,
-    //position,
+    subcategories: Number(data.num_direct_subcategories),
+    moderatedThreads: Number(data.num_direct_moderated_threads),
+    unmoderatedThreads: Number(data.num_direct_unmoderated_threads),
+    //position:+data.position_in_parent_category // TODO sometimes NaN,
   }
-  const category = await save('category', cat)
+  const category = await Category.create(cat)
   const mod = await fetchMemberByAccount(api, moderator)
   if (mod) category.setModerator(mod.id)
-  if (id > 1) fetchCategory(api, id - 1)
   return category
 }
 
 const fetchPost = async (api: Api, id: number) => {
+  if (id <= 0) return
   const exists = await Post.findByPk(id)
   if (exists) return exists
 
-  console.debug(`fetching post ${id}`)
+  fetching = `post ${id}`
   const data = await api.query.forum.postById(id)
 
   const threadId = Number(data.thread_id)
@@ -379,32 +388,32 @@ const fetchPost = async (api: Api, id: number) => {
   const createdAt = data.created_at.block
   const author: string = String(data.author_id)
 
-  const post = await save('post', { id, text, createdAt })
+  const post = await Post.create({ id, text, createdAt })
   const thread = await fetchThread(api, threadId)
   if (thread) post.setThread(thread.id)
   const member = await fetchMemberByAccount(api, author)
   if (member) post.setAuthor(member.id)
   const mod = await fetchMemberByAccount(api, moderation)
-  if (id > 1) fetchPost(api, id - 1)
   return post
 }
 
 const fetchThread = async (api: Api, id: number) => {
+  if (id <= 0) return
   const exists = await Thread.findByPk(id)
   if (exists) return exists
 
-  console.debug(`fetching thread ${id}`)
+  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 = +data.created_at.block
+  const { title, moderation, nr_in_category } = data
   const account = String(data.author_id)
-
-  const thread = await save('thread', { id, title, nrInCategory, createdAt })
-  const category = await fetchCategory(api, categoryId)
+  const t = {
+    id,
+    title,
+    nrInCategory: +nr_in_category,
+    createdAt: +data.created_at.block,
+  }
+  const thread = await Thread.create(t)
+  const category = await fetchCategory(api, +data.category_id)
   if (category) thread.setCategory(category.id)
   const author = await fetchMemberByAccount(api, account)
   if (author) thread.setAuthor(author.id)
@@ -425,7 +434,6 @@ const fetchThread = async (api: Api, id: number) => {
     //const mod = await fetchMemberByAccount(api, moderation)
     //if (mod) thread.setModeration(mod.id)
   }
-  if (id > 1) fetchThread(api, id - 1)
   return thread
 }
 
@@ -437,31 +445,31 @@ const fetchCouncils = async (api: Api, lastBlock: number) => {
   for (let round = 0; round < round; round++) {
     const block = 57601 + round * cycle
     if (councils.find((c) => c.round === round) || block > lastBlock) continue
-    //enqueue(() => fetchCouncil(api, block))
+    fetchCouncil(api, block)
   }
 }
 
 const fetchCouncil = async (api: Api, block: number) => {
-  console.debug(`Fetching council at block ${block}`)
+  fetching = `council at block ${block}`
   const blockHash = await api.rpc.chain.getBlockHash(block)
   if (!blockHash)
     return console.error(`Error: empty blockHash fetchCouncil ${block}`)
   const council = await api.query.council.activeCouncil.at(blockHash)
-  return save('council', council)
+  return Council.create(council)
 }
 
 const fetchProposal = async (api: Api, id: number) => {
-  const exists = await Proposal.findByPk(id)
+  if (id <= 0) return
+  const exists = await Proposal.findByPk(+id)
   if (exists) return exists
 
   //if (exists && exists.stage === 'Finalized')
   //if (exists.votesByAccount && exists.votesByAccount.length) return
   //else return //TODO fetchVotesPerProposal(api, exists)
 
-  console.debug(`Fetching proposal ${id}`)
+  fetching = `proposal ${id}`
   const proposal = await get.proposalDetail(api, id)
-  if (id > 1) fetchProposal(api, id - 1)
-  return save('proposal', proposal)
+  return Proposal.create(proposal)
   //TODO fetchVotesPerProposal(api, proposal)
 }
 
@@ -471,7 +479,7 @@ const fetchVotesPerProposal = async (api: Api, proposal: ProposalDetail) => {
   const proposals = await Proposal.findAll()
   const councils = await Council.findAll()
 
-  console.debug(`Fetching proposal votes (${proposal.id})`)
+  fetching = `proposal votes (${proposal.id})`
   let members: MemberType[] = []
   councils.map((seats: Seat[]) =>
     seats.forEach(async (seat: Seat) => {
@@ -496,7 +504,7 @@ const fetchVoteByProposalByVoter = async (
   proposalId: number,
   voterId: number
 ): Promise<string> => {
-  console.debug(`Fetching vote by ${voterId} for proposal ${proposalId}`)
+  fetching = `vote by ${voterId} for proposal ${proposalId}`
   const vote: VoteKind = await api.query.proposalsEngine.voteExistsByProposalByVoter(
     proposalId,
     voterId
@@ -523,59 +531,21 @@ const fetchMemberByAccount = async (
   return id ? fetchMember(api, id) : undefined
 }
 
-const fetchMember = async (api: Api, id: number): Promise<MemberType> => {
-  try {
-    const exists = await Member.findByPk(id)
-    if (exists) return exists
-  } catch (e) {
-    console.debug(`Fetching member ${id}`)
-  }
+const fetchMember = async (
+  api: Api,
+  id: number
+): Promise<MemberType | undefined> => {
+  if (id <= 0) return
+  const exists = await Member.findByPk(+id)
+  if (exists) return exists
+
+  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 createdAt = +membership.registered_at_block
-  if (id > 1) fetchMember(api, id - 1)
-  return save('member', { id, handle, createdAt, about })
-}
-
-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') {
-        const file = await fetchGithubFile(o.download_url)
-        // TODO save file
-      } else fetchGithubDir(o.url)
-    }
-  )
+  return Member.create({ id, handle, createdAt, about })
 }
 
 module.exports = { addBlock, addBlockRange }

+ 38 - 0
server/joystream/lib/github.ts

@@ -0,0 +1,38 @@
+export 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') {
+        const file = await fetchGithubFile(o.download_url)
+        // TODO save file
+      } else fetchGithubDir(o.url)
+    }
+  )
+}

+ 8 - 4
server/socket.ts

@@ -7,11 +7,15 @@ const { addBlock } = require('./joystream')
 const chalk = require('chalk')
 
 const setupSocket = async (io: any, api: Api) => {
-  let status: Status = {}
-  let lastHeader: Header
+  let status: Status
+  let lastHeader: Header = { number: 0, timestamp: 0, author: '' }
+
   api.derive.chain.subscribeNewHeads(async (header: Header) => {
-    if (lastHeader && lastHeader.number === header.number)
-      return console.debug(`skipping seen block`)
+    const id = +header.number
+    if (id === +lastHeader.number)
+      return console.debug(
+        `[Joystream] Skipping duplicate block ${id} (TODO handleFork)`
+      )
     lastHeader = header
     status = await addBlock(api, io, header, status)
   })

+ 179 - 174
server/types.ts

@@ -1,271 +1,276 @@
-import { ApiPromise } from "@polkadot/api";
-import { MemberId } from "@joystream/types/members";
+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";
+} 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;
+  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;
+  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 };
+  total: number
+  individual: { [account: string]: number }
 }
 
 export interface Stake {
-  who: string;
-  value: number;
+  who: string
+  value: number
 }
 
 export interface Stakes {
-  total: number;
-  own: number;
-  others: Stake[];
-  commission: number;
+  total: number
+  own: number
+  others: Stake[]
+  commission: number
 }
 
 export interface Seat {
-  member: string;
-  handle?: string;
-  id?: number;
-  stake: number;
-  backers: Backer[];
+  member: string
+  handle?: string
+  id?: number
+  stake: number
+  backers: Backer[]
 }
 
 export interface Backer {
-  member: string;
-  stake: number;
+  member: string
+  stake: number
 }
 
 export interface CouncilType {
-  round: number;
-  last: string;
+  round: number
+  last: string
 }
 
-export interface CouncilModel {
-
-}
+export interface CouncilModel {}
 
 export interface Options {
-  verbose: number;
-  channel: boolean;
-  council: boolean;
-  forum: boolean;
-  proposals: boolean;
+  verbose: number
+  channel: boolean
+  council: boolean
+  forum: boolean
+  proposals: boolean
 }
 
 export interface ProposalDetail {
-  createdAt: number;
-  finalizedAt: number;
-  message: string;
-  parameters: string;
-  stage: any;
-  result: string;
-  exec: any;
-  id: number;
-  title: string;
-  description: any;
-  votes: VotingResults;
-  type: string;
-  votesByAccount?: Vote[];
-  author: string;
-  authorId: number;
+  createdAt: number
+  finalizedAt: number
+  message: string
+  parameters: string
+  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;
+  vote: string
+  handle: string
 }
 
-export type ProposalArray = number[];
+export type ProposalArray = number[]
 
 export interface ProposalPost {
-  threadId: number;
-  text: string;
-  id: number;
+  threadId: number
+  text: string
+  id: number
 }
 
 export interface Proposals {
-  current: number;
-  last: number;
-  active: ProposalArray;
-  executing: ProposalArray;
+  current: number
+  last: number
+  active: ProposalArray
+  executing: ProposalArray
 }
 
 export interface ChannelType {
-  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;
+  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 CategoryType {
-  id: number;
-  threadId: number;
-  title: string;
-  description: string;
-  createdAt: number;
-  deleted: boolean;
-  archived: boolean;
-  subcategories: number;
-  unmoderatedThreads: number;
-  moderatedThreads: number;
-  position: number;
-  moderatorId: string;
+  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 PostType {
-  id: number;
-  text: string;
-  threadId: number;
-  authorId: string;
-  createdAt: { block: number; time: number };
+  id: number
+  text: string
+  threadId: number
+  authorId: string
+  createdAt: { block: number; time: number }
 }
 
 export interface ThreadType {
-  id: number;
-  title: string;
-  categoryId: number;
-  nrInCategory: number;
-  moderation: string;
-  createdAt: string;
-  authorId: string;
+  id: number
+  title: string
+  categoryId: number
+  nrInCategory: number
+  moderation: string
+  createdAt: string
+  authorId: string
 }
 
 export interface MemberType {
-  account: string;
-  handle: string;
-  id: number;
-  registeredAt: number;
-  about: string;
+  account: string
+  handle: string
+  id: number
+  registeredAt: number
+  about: string
 }
 
 export interface Header {
-  number: number;
-  timestamp: number;
+  number: number
+  timestamp: number
   author: string
 }
 
 export interface Summary {
-  blocks: Block[];
-  validators: number[];
-  nominators: number[];
+  blocks: Block[]
+  validators: number[]
+  nominators: number[]
 }
 
-export type NominatorsEntries = [StorageKey, Option<Nominations>][];
+export type NominatorsEntries = [StorageKey, Option<Nominations>][]
 
 export interface ProviderStatus {
-  [propName: string]: boolean;
+  [propName: string]: boolean
 }
 
 export interface Handles {
-  [key: string]: string;
+  [key: string]: string
 }
 
 export interface Tokenomics {
-  price: string;
-  totalIssuance: string;
-  validators: { total_stake: string };
-  burns: Burn[];
-  exchanges: Exchange[];
-  extecutedBurnsAmount: number;
+  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"
+  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"
+  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;
+  text: string
+  date: number
   category: {
-    tag: string;
-    color: string;
-  };
+    tag: string
+    color: string
+  }
   link: {
-    url: string;
-    text: string;
-  };
+    url: string
+    text: string
+  }
 }
 
 export interface CalendarItem {
-  id: number;
-  group: number;
-  title: string;
-  start_time: number;
-  end_time: number;
+  id: number
+  group: number
+  title: string
+  start_time: number
+  end_time: number
 }
 
 export interface CalendarGroup {
-  id: number;
-  title: string;
+  id: number
+  title: string
 }
 
 export interface Status {
-  
-}
+  era: number
+  members: number
+  channels: number
+  categories: number
+  threads: number
+  posts: number
+  proposals: number
+  proposalPosts: number
+}