OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-booking / lib / routes / providers.js

108 lines · javascript ref:
/**
 * Provider routes — CRUD for provider profiles.
 */

import { json, readJson, notFound } from '../response.js'
import { requireAuth } from './auth-helpers.js'

export function registerProviderRoutes (router, server) {
  // Get provider by slug (public)
  router.get('/api/providers/:slug', async (req, res) => {
    const { slug } = req.params
    const providerIdEntry = await server.db.get(`provider-by-slug:${slug}`)
    if (!providerIdEntry) return notFound(res, 'Provider not found')

    const provider = await server.db.get(`provider:${providerIdEntry.value}`)
    if (!provider) return notFound(res, 'Provider not found')

    json(res, provider.value)
  })

  // List all providers (public)
  router.get('/api/providers', async (req, res) => {
    const providers = []
    for await (const entry of server.db.createReadStream({
      gte: 'provider:',
      lte: 'provider:~'
    })) {
      if (entry.value.active !== false) {
        providers.push(entry.value)
      }
    }
    json(res, providers)
  })

  // Create provider (any authenticated user)
  router.post('/api/providers', async (req, res) => {
    const auth = await requireAuth(req, server, 'write')
    const body = await readJson(req)

    const { displayName, slug, bio, timezone, specialties, socialLinks } = body

    if (!displayName || !slug) {
      json(res, { error: 'displayName and slug required' }, 400)
      return
    }

    // Check slug uniqueness
    const existing = await server.db.get(`provider-by-slug:${slug}`)
    if (existing) {
      json(res, { error: 'Slug already taken' }, 409)
      return
    }

    const provider = {
      id: server.generateId('prov'),
      slug,
      displayName,
      bio: bio || '',
      timezone: timezone || 'America/New_York',
      specialties: specialties || [],
      socialLinks: socialLinks || {},
      photo: null,
      accentColor: '#c4a265',
      active: true,
      userId: auth.userId,
      createdAt: Date.now()
    }

    const batch = server.db.batch()
    await batch.put(`provider:${provider.id}`, provider)
    await batch.put(`provider-by-slug:${slug}`, provider.id)
    await batch.put(`provider-by-user:${auth.userId}`, provider.id)
    await batch.flush()

    json(res, provider, 201)
  })

  // Update provider (owner only)
  router.put('/api/providers/:slug', async (req, res) => {
    const auth = await requireAuth(req, server, 'write')
    const { slug } = req.params

    const providerIdEntry = await server.db.get(`provider-by-slug:${slug}`)
    if (!providerIdEntry) return notFound(res, 'Provider not found')

    const entry = await server.db.get(`provider:${providerIdEntry.value}`)
    if (!entry) return notFound(res, 'Provider not found')

    const provider = entry.value

    // Check ownership
    if (provider.userId !== auth.userId && !auth.permissions.admin) {
      json(res, { error: 'Not your provider' }, 403)
      return
    }

    const body = await readJson(req)
    const updatable = ['displayName', 'bio', 'timezone', 'specialties', 'socialLinks', 'photo', 'accentColor']
    for (const key of updatable) {
      if (body[key] !== undefined) provider[key] = body[key]
    }
    provider.updatedAt = Date.now()

    await server.db.put(`provider:${provider.id}`, provider)
    json(res, provider)
  })
}