OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-social / hyper-social-web / lib / feed-store.js

87 lines · javascript ref:
import { randomBytes } from 'bare-crypto'

const SOURCES = ['mastodon', 'atproto', 'instagram', 'local']

class FeedStore {
  constructor (bee) {
    this.bee = bee
  }

  _genId () {
    return randomBytes(8).toString('hex')
  }

  _padTimestamp (ts) {
    return String(ts).padStart(16, '0')
  }

  async add (opts = {}) {
    const id = opts.id || this._genId()
    const now = Date.now()

    const item = {
      id,
      source: opts.source || 'local',
      author: opts.author || 'unknown',
      authorId: opts.authorId || opts.author || 'unknown',
      authorPublicKey: opts.authorPublicKey || null,
      text: opts.text || '',
      timestamp: opts.timestamp || now,
      type: opts.type || 'post',
      tags: opts.tags || [],
      url: opts.url || null,
      mediaUrl: opts.mediaUrl || null,
      mediaType: opts.mediaType || null,
      metadata: opts.metadata || {},
      syndicated: opts.syndicated || null,
      anproto: opts.anproto || null
    }

    const batch = this.bee.batch()
    await batch.put(`item/${id}`, item)
    await batch.put(`timeline/${this._padTimestamp(item.timestamp)}/${id}`, { id })
    await batch.put(`source/${item.source}/${this._padTimestamp(item.timestamp)}/${id}`, { id })
    await batch.put(`author/${item.authorId}/${this._padTimestamp(item.timestamp)}/${id}`, { id })
    await batch.flush()

    return item
  }

  async get (id) {
    const node = await this.bee.get(`item/${id}`)
    return node ? node.value : null
  }

  async timeline (opts = {}) {
    const { limit = 50, before, after, source } = opts
    const items = []

    const rangeOpts = {}
    if (source) {
      rangeOpts.gte = `source/${source}/`
      rangeOpts.lte = `source/${source}/~`
      if (before) rangeOpts.lt = `source/${source}/${this._padTimestamp(before)}/`
    } else {
      rangeOpts.gte = 'timeline/'
      rangeOpts.lte = 'timeline/~'
      if (before) rangeOpts.lt = `timeline/${this._padTimestamp(before)}/`
      if (after) rangeOpts.gt = `timeline/${this._padTimestamp(after)}/~`
    }

    // Read in reverse (newest first)
    rangeOpts.reverse = true

    for await (const node of this.bee.createReadStream(rangeOpts)) {
      if (!node.value) continue
      const item = await this.get(node.value.id)
      if (!item) continue
      items.push(item)
      if (items.length >= limit) break
    }

    return items
  }
}

export { FeedStore, SOURCES }