OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-booking / lib / routes / packages.js

150 lines · javascript ref:
/**
 * Package routes — CRUD for service packages.
 */

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

export function registerPackageRoutes (router, server) {
  // List packages for a provider (public)
  router.get('/api/providers/:slug/packages', 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 providerId = providerIdEntry.value
    const packages = []
    for await (const entry of server.db.createReadStream({
      gte: `package:${providerId}:`,
      lte: `package:${providerId}:~`
    })) {
      if (entry.value.active !== false) {
        packages.push(entry.value)
      }
    }

    // Sort: discovery first, then by price
    packages.sort((a, b) => {
      if (a.type === 'discovery' && b.type !== 'discovery') return -1
      if (b.type === 'discovery' && a.type !== 'discovery') return 1
      return a.price - b.price
    })

    json(res, packages)
  })

  // Get single package (public)
  router.get('/api/packages/:id', async (req, res) => {
    const entry = await server.db.get(`package-detail:${req.params.id}`)
    if (!entry) return notFound(res, 'Package not found')
    json(res, entry.value)
  })

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

    const {
      providerId, name, type, description,
      sessionDuration, sessionCount, price,
      currency, discountFullPay, installments,
      groupCapacity, intakeFormId, contractId
    } = body

    if (!providerId || !name || !type) {
      json(res, { error: 'providerId, name, and type required' }, 400)
      return
    }

    // Verify provider ownership
    const providerEntry = await server.db.get(`provider:${providerId}`)
    if (!providerEntry) return notFound(res, 'Provider not found')
    if (providerEntry.value.userId !== auth.userId &&
        !auth.permissions.admin) {
      json(res, { error: 'Not your provider' }, 403)
      return
    }

    const pkg = {
      id: server.generateId('pkg'),
      providerId,
      name,
      type,  // single | bundle | subscription | discovery | group
      description: description || '',
      sessionDuration: sessionDuration || 60,
      sessionCount: sessionCount || 1,
      price: price || 0,  // cents
      currency: currency || 'USD',
      discountFullPay: discountFullPay || 0,
      installments: installments || 0,
      groupCapacity: groupCapacity || null,
      intakeFormId: intakeFormId || null,
      contractId: contractId || null,
      image: null,
      active: true,
      createdAt: Date.now()
    }

    const batch = server.db.batch()
    await batch.put(`package:${providerId}:${pkg.id}`, pkg)
    await batch.put(`package-detail:${pkg.id}`, pkg)
    await batch.flush()

    json(res, pkg, 201)
  })

  // Update package (provider)
  router.put('/api/packages/:id', async (req, res) => {
    const auth = await requireAuth(req, server, 'write')
    const entry = await server.db.get(`package-detail:${req.params.id}`)
    if (!entry) return notFound(res, 'Package not found')

    const pkg = entry.value

    // Verify ownership
    const providerEntry = await server.db.get(`provider:${pkg.providerId}`)
    if (providerEntry.value.userId !== auth.userId &&
        !auth.permissions.admin) {
      json(res, { error: 'Not your provider' }, 403)
      return
    }

    const body = await readJson(req)
    const updatable = [
      'name', 'type', 'description', 'sessionDuration', 'sessionCount',
      'price', 'currency', 'discountFullPay', 'installments',
      'groupCapacity', 'intakeFormId', 'contractId', 'image', 'active'
    ]
    for (const key of updatable) {
      if (body[key] !== undefined) pkg[key] = body[key]
    }
    pkg.updatedAt = Date.now()

    const batch = server.db.batch()
    await batch.put(`package:${pkg.providerId}:${pkg.id}`, pkg)
    await batch.put(`package-detail:${pkg.id}`, pkg)
    await batch.flush()

    json(res, pkg)
  })

  // Delete (archive) package (provider)
  router.delete('/api/packages/:id', async (req, res) => {
    const auth = await requireAuth(req, server, 'write')
    const entry = await server.db.get(`package-detail:${req.params.id}`)
    if (!entry) return notFound(res, 'Package not found')

    const pkg = entry.value
    pkg.active = false
    pkg.archivedAt = Date.now()

    const batch = server.db.batch()
    await batch.put(`package:${pkg.providerId}:${pkg.id}`, pkg)
    await batch.put(`package-detail:${pkg.id}`, pkg)
    await batch.flush()

    json(res, { ok: true })
  })
}