dh_demo

DreamHanks demo project
git clone git://git.lair.cx/dh_demo
Log | Files | Refs | README

[...path].tsx (4529B)


      1 import { ApiError, ERR_FORBIDDEN, ERR_INVALID_ACL, ERR_NOT_FOUND } from '@/lib/apierror'
      2 import { ERR_CODE_EMPTY_CONTENT } from '@/lib/error_codes'
      3 import { withConnection } from '@/lib/model_helpers'
      4 import { getWikiAndPageACLViaPath } from '@/lib/models/wiki_acl'
      5 import { createWikiChange } from '@/lib/models/wiki_change'
      6 import { getWiki, getWikiViaSlug } from '@/lib/models/wiki_info'
      7 import { getWikiPage as modelGetWikiPage, putWikiPage, updateWikiPageAcl } from '@/lib/models/wiki_page'
      8 import { createWikiText } from '@/lib/models/wiki_text'
      9 import { getRedis } from '@/lib/redis'
     10 import { ACL_ACTION_MANAGE, ACL_ACTION_READ, ACL_ACTION_WRITE, resolveACL, validateACL } from '@/lib/security/acl'
     11 import { AccessTokenPayload, authenticationFromCookies } from '@/lib/security/token'
     12 import { getRemoteIp } from '@/lib/utils/ip'
     13 import { getSlugAndPath } from '@/lib/utils/wiki'
     14 import { Connection } from 'mysql2/promise'
     15 import { NextApiRequest, NextApiResponse } from 'next'
     16 
     17 export default async function handler (req: NextApiRequest, res: NextApiResponse) {
     18   switch (req.method) {
     19     case 'GET':
     20       return await handleGet(req, res)
     21 
     22     case 'PUT':
     23       return await handlePut(req, res)
     24 
     25     default:
     26       res.status(405).json({ status: 'method not allowed' })
     27   }
     28 }
     29 
     30 export async function getWikiPage (
     31   conn: Connection,
     32   token: AccessTokenPayload | null,
     33   wikiIdOrSlug: number | string,
     34   path: string,
     35 ) {
     36   const wiki = typeof wikiIdOrSlug === 'string'
     37     ? await getWikiViaSlug(conn, [wikiIdOrSlug])
     38     : await getWiki(conn, [wikiIdOrSlug])
     39   if (wiki == null) {
     40     return null
     41   }
     42 
     43   if (!resolveACL(token, wiki.acl, ACL_ACTION_READ)) {
     44     return null
     45   }
     46 
     47   const page = await modelGetWikiPage(conn, [wiki.id, path])
     48   if (page == null) {
     49     return null
     50   }
     51 
     52   if (!resolveACL(token, page.acl, ACL_ACTION_READ)) {
     53     return null
     54   }
     55 
     56   return page
     57 }
     58 
     59 async function handleGet (req: NextApiRequest, res: NextApiResponse) {
     60   const [slug, path] = getSlugAndPath(req)
     61   if (slug == null || path == null) {
     62     console.error('slug or path is null?')
     63     res.status(404).json({ status: 'not found' })
     64     return
     65   }
     66 
     67   const token = await authenticationFromCookies(req.cookies)
     68 
     69   const wikiPage = await withConnection(async (conn) => {
     70     return await getWikiPage(conn, token, slug, path)
     71   })
     72   if (wikiPage == null) {
     73     res.status(404).json({ status: 'not found' })
     74     return
     75   }
     76 
     77   res.status(200).json(wikiPage)
     78 }
     79 
     80 export interface WikiPagePutRequest {
     81   content?: string
     82   acl?: string
     83 }
     84 
     85 async function handlePut (
     86   req: NextApiRequest,
     87   res: NextApiResponse<ApiError | { status: 'ok' }>,
     88 ) {
     89   const [slug, path] = getSlugAndPath(req)
     90   if (slug == null || path == null) {
     91     console.error('slug or path is null?')
     92     res.status(404).json(ERR_NOT_FOUND)
     93     return
     94   }
     95 
     96   const { content, acl } = req.body as WikiPagePutRequest
     97   if ((content == null || content.length === 0) && acl == null) {
     98     res.status(400).json({ code: ERR_CODE_EMPTY_CONTENT, message: 'content is empty' })
     99     return
    100   }
    101 
    102   const token = await authenticationFromCookies(req.cookies)
    103 
    104   await withConnection(async (conn) => {
    105     const aclInfo = await getWikiAndPageACLViaPath(conn, [slug, path])
    106     if (aclInfo == null) {
    107       res.status(404).json(ERR_NOT_FOUND)
    108       return
    109     }
    110 
    111     if (acl != null) {
    112       if (aclInfo.pageId == null || !resolveACL(token, aclInfo.wiki, ACL_ACTION_MANAGE)) {
    113         res.status(403).json(ERR_FORBIDDEN)
    114         return
    115       }
    116 
    117       let aclData;
    118       try {
    119         aclData = JSON.parse(acl)
    120       } catch (e) {
    121         res.status(400).json(ERR_INVALID_ACL)
    122         return
    123       }
    124 
    125       if (!validateACL(aclData)) {
    126         res.status(400).json(ERR_INVALID_ACL)
    127         return
    128       }
    129 
    130       await updateWikiPageAcl(conn, [aclInfo.pageId, acl])
    131     }
    132 
    133     if (content != null) {
    134       if (!resolveACL(token, aclInfo.wiki, ACL_ACTION_WRITE)) {
    135         res.status(403).json({ code: 'ERR_FORBIDDEN', message: 'forbidden' })
    136         return
    137       }
    138 
    139       const textId = await createWikiText(conn, [content, 'utf-8'])
    140       const pageId = await putWikiPage(conn, [aclInfo.wikiId, path, textId])
    141       await createWikiChange(conn, [
    142         pageId,
    143         token?.uid ?? null,
    144         token?.uid == null ? getRemoteIp(req) : null,
    145         textId,
    146       ])
    147 
    148       const redis = await getRedis()
    149       await redis.publish('event', `/edit/${slug}/${path}|pageChange|${token?.uid ?? '__anonymous'}`)
    150     }
    151 
    152     res.status(200).json({ status: 'ok' })
    153     return
    154   }, false)
    155 }