[...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 }