OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-note / lib / routes / sync.js

93 lines · javascript ref:
/**
 * Sync Routes — P2P replication control endpoints
 */

import { json, readJson } from '../response.js'
import { UnauthorizedError, ForbiddenError, NotFoundError, ValidationError } from '../errors.js'

async function requireAuth (req, server, action, notebook) {
  const { authorized, auth, reason } = await server.authManager.authorizeRequest(req, action, notebook)
  if (!auth) throw new UnauthorizedError('Authentication required')
  if (!authorized) throw new ForbiddenError(reason, action)
  return auth
}

export function registerSyncRoutes (router, server) {
  // Sync status overview
  router.get('/api/sync/status', async (req, res) => {
    await requireAuth(req, server, 'admin')
    const status = server.replicationManager.getStatus()
    json(res, status)
  })

  // Announce notebook on swarm
  router.post('/api/notebooks/:notebook/announce', async (req, res) => {
    await requireAuth(req, server, 'admin', req.params.notebook)

    const slug = req.params.notebook
    const info = await server.driveManager.getDriveInfo(slug)
    if (!info) throw new NotFoundError(`Notebook '${slug}' not found`, 'notebook')

    await server.replicationManager.announce(slug)
    json(res, { announced: true, slug, discoveryKey: info.discoveryKey })
  })

  // Stop announcing notebook
  router.post('/api/notebooks/:notebook/unannounce', async (req, res) => {
    await requireAuth(req, server, 'admin', req.params.notebook)

    const slug = req.params.notebook
    await server.replicationManager.unannounce(slug)
    json(res, { announced: false, slug })
  })

  // Add remote notebook
  router.post('/api/sync/add', async (req, res) => {
    await requireAuth(req, server, 'admin')
    const body = await readJson(req)

    if (!body.driveKey || typeof body.driveKey !== 'string') {
      throw new ValidationError('driveKey is required (64-char hex)', 'driveKey')
    }
    if (body.driveKey.length !== 64) {
      throw new ValidationError('driveKey must be 64-char hex string', 'driveKey')
    }

    const slug = body.slug || 'remote-' + body.driveKey.substring(0, 8)

    await server.driveManager.addRemoteDrive(body.driveKey, slug)
    await server.replicationManager.joinRemote(slug)

    json(res, { added: true, slug, driveKey: body.driveKey }, 201)
  })

  // Remove remote notebook
  router.delete('/api/sync/remote/:notebook', async (req, res) => {
    await requireAuth(req, server, 'admin')

    const slug = req.params.notebook
    const info = await server.driveManager.getDriveInfo(slug)
    if (!info) throw new NotFoundError(`Remote notebook '${slug}' not found`, 'notebook')

    await server.replicationManager.unannounce(slug)
    await server.driveManager.removeRemoteDrive(slug)

    json(res, { removed: true, slug })
  })

  // Drive info for a notebook (open read)
  router.get('/api/notebooks/:notebook/drive-info', async (req, res) => {
    const slug = req.params.notebook
    const info = await server.driveManager.getDriveInfo(slug)
    if (!info) throw new NotFoundError(`Notebook '${slug}' not found`, 'notebook')

    const status = server.replicationManager.getStatus()
    json(res, {
      ...info,
      announced: status.announced.includes(slug)
    })
  })
}

export default registerSyncRoutes