access.js 2.41 KB
import * as v from 'valibot'

import { codedError, requireUser } from './auth.js'

const SubmitBody = v.object({ note: v.optional(v.pipe(v.string(), v.maxLength(1000))) })
const ListQuery = v.object({
  status: v.optional(v.picklist(['pending', 'approved', 'rejected']), 'pending'),
  limit: v.optional(v.pipe(v.string(), v.transform(Number), v.integer(), v.minValue(1), v.maxValue(200)), '50'),
})
const ReviewParams = v.object({ id: v.pipe(v.string(), v.minLength(1)) })
const ReviewBody = v.object({ decision: v.picklist(['approved', 'rejected']) })

/**
 * 加入申请(申请制):用户提交申请并查看排队状态;运营在 /admin 人工审核。
 * @param {import('fastify').FastifyInstance} app
 * @param {Record<string, any>} services
 */
export function registerAccessRoutes(app, services) {
  const { access } = services

  app.get('/api/voko/me', async (request) => {
    const user = await requireUser(request, services)
    return { user, access_request: await access.getForUser(request.ctx, { user_id: user.id }) }
  })

  app.post('/api/voko/access-requests', async (request) => {
    const user = await requireUser(request, services)
    const body = v.parse(SubmitBody, request.body || {})
    return { access_request: await access.submit(request.ctx, { user_id: user.id, email: user.email, note: body.note ?? null }) }
  })

  app.get('/api/voko/admin/access-requests', async (request) => {
    await requireAdmin(request, services)
    const query = v.parse(ListQuery, request.query)
    return { access_requests: await access.list(request.ctx, { status: query.status, limit: query.limit }) }
  })

  app.post('/api/voko/admin/access-requests/:id/review', async (request) => {
    const admin = await requireAdmin(request, services)
    const params = v.parse(ReviewParams, request.params)
    const body = v.parse(ReviewBody, request.body)
    const result = await access.review(request.ctx, { request_id: params.id, reviewer_user_id: admin.id, decision: body.decision })
    if (!result.ok) throw codedError(`Access request cannot be reviewed: ${params.id}`, result.code)
    return { access_request: result.request }
  })
}

/**
 * @param {import('fastify').FastifyRequest} request
 * @param {Record<string, any>} services
 */
async function requireAdmin(request, services) {
  const user = await requireUser(request, services)
  if (user.role !== 'admin') throw codedError('admin permission required', 'FORBIDDEN')
  return user
}