@crowi/api 2.0.0-alpha.1 → 2.0.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (403) hide show
  1. package/dist/hono/handlers/activation.d.ts +3 -3
  2. package/dist/hono/handlers/admin/users.d.ts +118 -0
  3. package/dist/hono/handlers/admin/users.js +28 -0
  4. package/dist/hono/handlers/admin/users.js.map +1 -1
  5. package/dist/hono/handlers/app.d.ts +1 -0
  6. package/dist/hono/handlers/app.js +11 -0
  7. package/dist/hono/handlers/app.js.map +1 -1
  8. package/dist/hono/handlers/attachment-stream.js +23 -0
  9. package/dist/hono/handlers/attachment-stream.js.map +1 -1
  10. package/dist/hono/handlers/draft.js +10 -0
  11. package/dist/hono/handlers/draft.js.map +1 -1
  12. package/dist/hono/handlers/emailChange.d.ts +4 -4
  13. package/dist/hono/handlers/inviteAccept.d.ts +6 -6
  14. package/dist/hono/handlers/page.d.ts +251 -0
  15. package/dist/hono/handlers/page.js +123 -6
  16. package/dist/hono/handlers/page.js.map +1 -1
  17. package/dist/hono/handlers/passwordReset.d.ts +5 -5
  18. package/dist/hono/handlers/tokenAuth.d.ts +7 -7
  19. package/dist/mcp/result.d.ts +42 -16
  20. package/dist/mcp/result.js +56 -10
  21. package/dist/mcp/result.js.map +1 -1
  22. package/dist/mcp/tools/page.js +21 -1
  23. package/dist/mcp/tools/page.js.map +1 -1
  24. package/dist/mcp/tools/search.d.ts +12 -0
  25. package/dist/mcp/tools/search.js +21 -5
  26. package/dist/mcp/tools/search.js.map +1 -1
  27. package/dist/migration/helpers.d.ts +13 -0
  28. package/dist/migration/helpers.js +29 -0
  29. package/dist/migration/helpers.js.map +1 -0
  30. package/dist/migration/migrations/files-url-to-attachments.d.ts +35 -0
  31. package/dist/migration/migrations/files-url-to-attachments.js +291 -0
  32. package/dist/migration/migrations/files-url-to-attachments.js.map +1 -0
  33. package/dist/migration/migrations/index.js +4 -0
  34. package/dist/migration/migrations/index.js.map +1 -1
  35. package/dist/migration/migrations/published-current-revision.d.ts +47 -0
  36. package/dist/migration/migrations/published-current-revision.js +90 -0
  37. package/dist/migration/migrations/published-current-revision.js.map +1 -0
  38. package/dist/migration/migrations/wikilink-format.d.ts +0 -11
  39. package/dist/migration/migrations/wikilink-format.js +5 -156
  40. package/dist/migration/migrations/wikilink-format.js.map +1 -1
  41. package/dist/migration/migrations/wikilink-html-recover.d.ts +116 -0
  42. package/dist/migration/migrations/wikilink-html-recover.js +314 -0
  43. package/dist/migration/migrations/wikilink-html-recover.js.map +1 -0
  44. package/dist/models/page.d.ts +3 -0
  45. package/dist/models/page.js +31 -0
  46. package/dist/models/page.js.map +1 -1
  47. package/dist/models/user.d.ts +1 -0
  48. package/dist/models/user.js +40 -21
  49. package/dist/models/user.js.map +1 -1
  50. package/dist/renderer/core/headings.d.ts +12 -1
  51. package/dist/renderer/core/headings.js +48 -8
  52. package/dist/renderer/core/headings.js.map +1 -1
  53. package/dist/renderer/pipeline.d.ts +6 -0
  54. package/dist/renderer/pipeline.js.map +1 -1
  55. package/dist/util/page-response.js +19 -2
  56. package/dist/util/page-response.js.map +1 -1
  57. package/package.json +12 -6
  58. package/views/mail/layout.mjml +7 -5
  59. package/dist/common/functions/path2name.d.ts +0 -1
  60. package/dist/common/functions/path2name.js +0 -22
  61. package/dist/common/functions/path2name.js.map +0 -1
  62. package/dist/common/functions/renderIcon.d.ts +0 -1
  63. package/dist/common/functions/renderIcon.js +0 -9
  64. package/dist/common/functions/renderIcon.js.map +0 -1
  65. package/dist/controllers/admin.d.ts +0 -3
  66. package/dist/controllers/admin.js +0 -474
  67. package/dist/controllers/admin.js.map +0 -1
  68. package/dist/controllers/attachment.d.ts +0 -4
  69. package/dist/controllers/attachment.js +0 -200
  70. package/dist/controllers/attachment.js.map +0 -1
  71. package/dist/controllers/backlink.d.ts +0 -3
  72. package/dist/controllers/backlink.js +0 -42
  73. package/dist/controllers/backlink.js.map +0 -1
  74. package/dist/controllers/bookmark.d.ts +0 -3
  75. package/dist/controllers/bookmark.js +0 -100
  76. package/dist/controllers/bookmark.js.map +0 -1
  77. package/dist/controllers/comment.d.ts +0 -3
  78. package/dist/controllers/comment.js +0 -111
  79. package/dist/controllers/comment.js.map +0 -1
  80. package/dist/controllers/index.d.ts +0 -25
  81. package/dist/controllers/index.js +0 -44
  82. package/dist/controllers/index.js.map +0 -1
  83. package/dist/controllers/installer.d.ts +0 -3
  84. package/dist/controllers/installer.js +0 -48
  85. package/dist/controllers/installer.js.map +0 -1
  86. package/dist/controllers/login.d.ts +0 -4
  87. package/dist/controllers/login.js +0 -438
  88. package/dist/controllers/login.js.map +0 -1
  89. package/dist/controllers/logout.d.ts +0 -5
  90. package/dist/controllers/logout.js +0 -11
  91. package/dist/controllers/logout.js.map +0 -1
  92. package/dist/controllers/me.d.ts +0 -4
  93. package/dist/controllers/me.js +0 -369
  94. package/dist/controllers/me.js.map +0 -1
  95. package/dist/controllers/notification.d.ts +0 -3
  96. package/dist/controllers/notification.js +0 -88
  97. package/dist/controllers/notification.js.map +0 -1
  98. package/dist/controllers/page.d.ts +0 -3
  99. package/dist/controllers/page.js +0 -881
  100. package/dist/controllers/page.js.map +0 -1
  101. package/dist/controllers/revision.d.ts +0 -3
  102. package/dist/controllers/revision.js +0 -91
  103. package/dist/controllers/revision.js.map +0 -1
  104. package/dist/controllers/search.d.ts +0 -3
  105. package/dist/controllers/search.js +0 -93
  106. package/dist/controllers/search.js.map +0 -1
  107. package/dist/controllers/share.d.ts +0 -3
  108. package/dist/controllers/share.js +0 -207
  109. package/dist/controllers/share.js.map +0 -1
  110. package/dist/controllers/shareAccess.d.ts +0 -3
  111. package/dist/controllers/shareAccess.js +0 -28
  112. package/dist/controllers/shareAccess.js.map +0 -1
  113. package/dist/controllers/slack.d.ts +0 -3
  114. package/dist/controllers/slack.js +0 -87
  115. package/dist/controllers/slack.js.map +0 -1
  116. package/dist/controllers/tokenAuth.d.ts +0 -10
  117. package/dist/controllers/tokenAuth.js +0 -292
  118. package/dist/controllers/tokenAuth.js.map +0 -1
  119. package/dist/controllers/user.d.ts +0 -3
  120. package/dist/controllers/user.js +0 -67
  121. package/dist/controllers/user.js.map +0 -1
  122. package/dist/controllers/version.d.ts +0 -4
  123. package/dist/controllers/version.js +0 -19
  124. package/dist/controllers/version.js.map +0 -1
  125. package/dist/crowi/express-init.d.ts +0 -4
  126. package/dist/crowi/express-init.js +0 -101
  127. package/dist/crowi/express-init.js.map +0 -1
  128. package/dist/form/admin/app.d.ts +0 -2
  129. package/dist/form/admin/app.js +0 -9
  130. package/dist/form/admin/app.js.map +0 -1
  131. package/dist/form/admin/auth.d.ts +0 -2
  132. package/dist/form/admin/auth.js +0 -9
  133. package/dist/form/admin/auth.js.map +0 -1
  134. package/dist/form/admin/aws.d.ts +0 -2
  135. package/dist/form/admin/aws.js +0 -13
  136. package/dist/form/admin/aws.js.map +0 -1
  137. package/dist/form/admin/github.d.ts +0 -2
  138. package/dist/form/admin/github.js +0 -15
  139. package/dist/form/admin/github.js.map +0 -1
  140. package/dist/form/admin/google.d.ts +0 -2
  141. package/dist/form/admin/google.js +0 -13
  142. package/dist/form/admin/google.js.map +0 -1
  143. package/dist/form/admin/mail.d.ts +0 -2
  144. package/dist/form/admin/mail.js +0 -13
  145. package/dist/form/admin/mail.js.map +0 -1
  146. package/dist/form/admin/sec.d.ts +0 -2
  147. package/dist/form/admin/sec.js +0 -10
  148. package/dist/form/admin/sec.js.map +0 -1
  149. package/dist/form/admin/slackSetting.d.ts +0 -2
  150. package/dist/form/admin/slackSetting.js +0 -13
  151. package/dist/form/admin/slackSetting.js.map +0 -1
  152. package/dist/form/admin/userEdit.d.ts +0 -2
  153. package/dist/form/admin/userEdit.js +0 -9
  154. package/dist/form/admin/userEdit.js.map +0 -1
  155. package/dist/form/admin/userInvite.d.ts +0 -2
  156. package/dist/form/admin/userInvite.js +0 -9
  157. package/dist/form/admin/userInvite.js.map +0 -1
  158. package/dist/form/comment.d.ts +0 -2
  159. package/dist/form/comment.js +0 -9
  160. package/dist/form/comment.js.map +0 -1
  161. package/dist/form/index.d.ts +0 -25
  162. package/dist/form/index.js +0 -48
  163. package/dist/form/index.js.map +0 -1
  164. package/dist/form/invited.d.ts +0 -2
  165. package/dist/form/invited.js +0 -13
  166. package/dist/form/invited.js.map +0 -1
  167. package/dist/form/login.d.ts +0 -2
  168. package/dist/form/login.js +0 -11
  169. package/dist/form/login.js.map +0 -1
  170. package/dist/form/me/apiToken.d.ts +0 -2
  171. package/dist/form/me/apiToken.js +0 -9
  172. package/dist/form/me/apiToken.js.map +0 -1
  173. package/dist/form/me/password.d.ts +0 -2
  174. package/dist/form/me/password.js +0 -11
  175. package/dist/form/me/password.js.map +0 -1
  176. package/dist/form/me/user.d.ts +0 -2
  177. package/dist/form/me/user.js +0 -9
  178. package/dist/form/me/user.js.map +0 -1
  179. package/dist/form/register.d.ts +0 -2
  180. package/dist/form/register.js +0 -13
  181. package/dist/form/register.js.map +0 -1
  182. package/dist/form/revision.d.ts +0 -2
  183. package/dist/form/revision.js +0 -13
  184. package/dist/form/revision.js.map +0 -1
  185. package/dist/hono/handlers/admin/share.d.ts +0 -106
  186. package/dist/hono/handlers/admin/share.js +0 -55
  187. package/dist/hono/handlers/admin/share.js.map +0 -1
  188. package/dist/middlewares/accessTokenParser.d.ts +0 -4
  189. package/dist/middlewares/accessTokenParser.js +0 -29
  190. package/dist/middlewares/accessTokenParser.js.map +0 -1
  191. package/dist/middlewares/adminRequired.d.ts +0 -10
  192. package/dist/middlewares/adminRequired.js +0 -35
  193. package/dist/middlewares/adminRequired.js.map +0 -1
  194. package/dist/middlewares/applicationInstalled.d.ts +0 -3
  195. package/dist/middlewares/applicationInstalled.js +0 -20
  196. package/dist/middlewares/applicationInstalled.js.map +0 -1
  197. package/dist/middlewares/applicationNotInstalled.d.ts +0 -3
  198. package/dist/middlewares/applicationNotInstalled.js +0 -13
  199. package/dist/middlewares/applicationNotInstalled.js.map +0 -1
  200. package/dist/middlewares/basicAuth.d.ts +0 -4
  201. package/dist/middlewares/basicAuth.js +0 -23
  202. package/dist/middlewares/basicAuth.js.map +0 -1
  203. package/dist/middlewares/csrfVerify.d.ts +0 -4
  204. package/dist/middlewares/csrfVerify.js +0 -24
  205. package/dist/middlewares/csrfVerify.js.map +0 -1
  206. package/dist/middlewares/encodeSpace.d.ts +0 -3
  207. package/dist/middlewares/encodeSpace.js +0 -14
  208. package/dist/middlewares/encodeSpace.js.map +0 -1
  209. package/dist/middlewares/fileAccessRightOrLoginRequired.d.ts +0 -4
  210. package/dist/middlewares/fileAccessRightOrLoginRequired.js +0 -29
  211. package/dist/middlewares/fileAccessRightOrLoginRequired.js.map +0 -1
  212. package/dist/middlewares/index.d.ts +0 -16
  213. package/dist/middlewares/index.js +0 -30
  214. package/dist/middlewares/index.js.map +0 -1
  215. package/dist/middlewares/jwtAdminRequired.d.ts +0 -8
  216. package/dist/middlewares/jwtAdminRequired.js +0 -35
  217. package/dist/middlewares/jwtAdminRequired.js.map +0 -1
  218. package/dist/middlewares/jwtAuth.d.ts +0 -4
  219. package/dist/middlewares/jwtAuth.js +0 -104
  220. package/dist/middlewares/jwtAuth.js.map +0 -1
  221. package/dist/middlewares/loginChecker.d.ts +0 -4
  222. package/dist/middlewares/loginChecker.js +0 -32
  223. package/dist/middlewares/loginChecker.js.map +0 -1
  224. package/dist/middlewares/loginRequired.d.ts +0 -4
  225. package/dist/middlewares/loginRequired.js +0 -88
  226. package/dist/middlewares/loginRequired.js.map +0 -1
  227. package/dist/routes/admin.d.ts +0 -4
  228. package/dist/routes/admin.js +0 -17
  229. package/dist/routes/admin.js.map +0 -1
  230. package/dist/routes/api/admin.d.ts +0 -4
  231. package/dist/routes/api/admin.js +0 -37
  232. package/dist/routes/api/admin.js.map +0 -1
  233. package/dist/routes/api/attachment.d.ts +0 -4
  234. package/dist/routes/api/attachment.js +0 -19
  235. package/dist/routes/api/attachment.js.map +0 -1
  236. package/dist/routes/api/bookmark.d.ts +0 -4
  237. package/dist/routes/api/bookmark.js +0 -15
  238. package/dist/routes/api/bookmark.js.map +0 -1
  239. package/dist/routes/api/comment.d.ts +0 -4
  240. package/dist/routes/api/comment.js +0 -14
  241. package/dist/routes/api/comment.js.map +0 -1
  242. package/dist/routes/api/index.d.ts +0 -4
  243. package/dist/routes/api/index.js +0 -36
  244. package/dist/routes/api/index.js.map +0 -1
  245. package/dist/routes/api/like.d.ts +0 -4
  246. package/dist/routes/api/like.js +0 -13
  247. package/dist/routes/api/like.js.map +0 -1
  248. package/dist/routes/api/notification.d.ts +0 -4
  249. package/dist/routes/api/notification.js +0 -15
  250. package/dist/routes/api/notification.js.map +0 -1
  251. package/dist/routes/api/page.d.ts +0 -4
  252. package/dist/routes/api/page.js +0 -24
  253. package/dist/routes/api/page.js.map +0 -1
  254. package/dist/routes/api/revision.d.ts +0 -4
  255. package/dist/routes/api/revision.js +0 -14
  256. package/dist/routes/api/revision.js.map +0 -1
  257. package/dist/routes/api/share.d.ts +0 -4
  258. package/dist/routes/api/share.js +0 -16
  259. package/dist/routes/api/share.js.map +0 -1
  260. package/dist/routes/api/version.d.ts +0 -4
  261. package/dist/routes/api/version.js +0 -10
  262. package/dist/routes/api/version.js.map +0 -1
  263. package/dist/routes/index.d.ts +0 -4
  264. package/dist/routes/index.js +0 -71
  265. package/dist/routes/index.js.map +0 -1
  266. package/dist/routes/login.d.ts +0 -4
  267. package/dist/routes/login.js +0 -18
  268. package/dist/routes/login.js.map +0 -1
  269. package/dist/routes/me.d.ts +0 -4
  270. package/dist/routes/me.js +0 -24
  271. package/dist/routes/me.js.map +0 -1
  272. package/dist/routes/ts-rest/admin/app.d.ts +0 -4
  273. package/dist/routes/ts-rest/admin/app.js +0 -67
  274. package/dist/routes/ts-rest/admin/app.js.map +0 -1
  275. package/dist/routes/ts-rest/admin/auth.d.ts +0 -4
  276. package/dist/routes/ts-rest/admin/auth.js +0 -95
  277. package/dist/routes/ts-rest/admin/auth.js.map +0 -1
  278. package/dist/routes/ts-rest/admin/index.d.ts +0 -10
  279. package/dist/routes/ts-rest/admin/index.js +0 -35
  280. package/dist/routes/ts-rest/admin/index.js.map +0 -1
  281. package/dist/routes/ts-rest/admin/mail.d.ts +0 -4
  282. package/dist/routes/ts-rest/admin/mail.js +0 -156
  283. package/dist/routes/ts-rest/admin/mail.js.map +0 -1
  284. package/dist/routes/ts-rest/admin/plugins.d.ts +0 -4
  285. package/dist/routes/ts-rest/admin/plugins.js +0 -317
  286. package/dist/routes/ts-rest/admin/plugins.js.map +0 -1
  287. package/dist/routes/ts-rest/admin/search.d.ts +0 -4
  288. package/dist/routes/ts-rest/admin/search.js +0 -67
  289. package/dist/routes/ts-rest/admin/search.js.map +0 -1
  290. package/dist/routes/ts-rest/admin/security.d.ts +0 -4
  291. package/dist/routes/ts-rest/admin/security.js +0 -114
  292. package/dist/routes/ts-rest/admin/security.js.map +0 -1
  293. package/dist/routes/ts-rest/admin/share.d.ts +0 -4
  294. package/dist/routes/ts-rest/admin/share.js +0 -69
  295. package/dist/routes/ts-rest/admin/share.js.map +0 -1
  296. package/dist/routes/ts-rest/admin/storage.d.ts +0 -4
  297. package/dist/routes/ts-rest/admin/storage.js +0 -59
  298. package/dist/routes/ts-rest/admin/storage.js.map +0 -1
  299. package/dist/routes/ts-rest/admin/users.d.ts +0 -4
  300. package/dist/routes/ts-rest/admin/users.js +0 -215
  301. package/dist/routes/ts-rest/admin/users.js.map +0 -1
  302. package/dist/routes/ts-rest/adminCrypto.d.ts +0 -4
  303. package/dist/routes/ts-rest/adminCrypto.js +0 -111
  304. package/dist/routes/ts-rest/adminCrypto.js.map +0 -1
  305. package/dist/routes/ts-rest/app.d.ts +0 -4
  306. package/dist/routes/ts-rest/app.js +0 -23
  307. package/dist/routes/ts-rest/app.js.map +0 -1
  308. package/dist/routes/ts-rest/attachment.d.ts +0 -4
  309. package/dist/routes/ts-rest/attachment.js +0 -830
  310. package/dist/routes/ts-rest/attachment.js.map +0 -1
  311. package/dist/routes/ts-rest/auth.d.ts +0 -4
  312. package/dist/routes/ts-rest/auth.js +0 -70
  313. package/dist/routes/ts-rest/auth.js.map +0 -1
  314. package/dist/routes/ts-rest/autocomplete.d.ts +0 -30
  315. package/dist/routes/ts-rest/autocomplete.js +0 -189
  316. package/dist/routes/ts-rest/autocomplete.js.map +0 -1
  317. package/dist/routes/ts-rest/backlink.d.ts +0 -4
  318. package/dist/routes/ts-rest/backlink.js +0 -106
  319. package/dist/routes/ts-rest/backlink.js.map +0 -1
  320. package/dist/routes/ts-rest/bookmark.d.ts +0 -4
  321. package/dist/routes/ts-rest/bookmark.js +0 -189
  322. package/dist/routes/ts-rest/bookmark.js.map +0 -1
  323. package/dist/routes/ts-rest/comment.d.ts +0 -4
  324. package/dist/routes/ts-rest/comment.js +0 -217
  325. package/dist/routes/ts-rest/comment.js.map +0 -1
  326. package/dist/routes/ts-rest/draft.d.ts +0 -22
  327. package/dist/routes/ts-rest/draft.js +0 -200
  328. package/dist/routes/ts-rest/draft.js.map +0 -1
  329. package/dist/routes/ts-rest/index.d.ts +0 -4
  330. package/dist/routes/ts-rest/index.js +0 -103
  331. package/dist/routes/ts-rest/index.js.map +0 -1
  332. package/dist/routes/ts-rest/installer.d.ts +0 -4
  333. package/dist/routes/ts-rest/installer.js +0 -77
  334. package/dist/routes/ts-rest/installer.js.map +0 -1
  335. package/dist/routes/ts-rest/me.d.ts +0 -4
  336. package/dist/routes/ts-rest/me.js +0 -410
  337. package/dist/routes/ts-rest/me.js.map +0 -1
  338. package/dist/routes/ts-rest/notification.d.ts +0 -4
  339. package/dist/routes/ts-rest/notification.js +0 -241
  340. package/dist/routes/ts-rest/notification.js.map +0 -1
  341. package/dist/routes/ts-rest/page-collab.d.ts +0 -29
  342. package/dist/routes/ts-rest/page-collab.js +0 -90
  343. package/dist/routes/ts-rest/page-collab.js.map +0 -1
  344. package/dist/routes/ts-rest/page-preview.d.ts +0 -26
  345. package/dist/routes/ts-rest/page-preview.js +0 -80
  346. package/dist/routes/ts-rest/page-preview.js.map +0 -1
  347. package/dist/routes/ts-rest/page.d.ts +0 -4
  348. package/dist/routes/ts-rest/page.js +0 -676
  349. package/dist/routes/ts-rest/page.js.map +0 -1
  350. package/dist/routes/ts-rest/presence.d.ts +0 -30
  351. package/dist/routes/ts-rest/presence.js +0 -155
  352. package/dist/routes/ts-rest/presence.js.map +0 -1
  353. package/dist/routes/ts-rest/revision.d.ts +0 -4
  354. package/dist/routes/ts-rest/revision.js +0 -240
  355. package/dist/routes/ts-rest/revision.js.map +0 -1
  356. package/dist/routes/ts-rest/search.d.ts +0 -4
  357. package/dist/routes/ts-rest/search.js +0 -121
  358. package/dist/routes/ts-rest/search.js.map +0 -1
  359. package/dist/routes/ts-rest/tokenAuth.d.ts +0 -4
  360. package/dist/routes/ts-rest/tokenAuth.js +0 -94
  361. package/dist/routes/ts-rest/tokenAuth.js.map +0 -1
  362. package/dist/routes/ts-rest/user.d.ts +0 -4
  363. package/dist/routes/ts-rest/user.js +0 -307
  364. package/dist/routes/ts-rest/user.js.map +0 -1
  365. package/dist/types/express.d.ts +0 -34
  366. package/dist/types/express.js +0 -50
  367. package/dist/types/express.js.map +0 -1
  368. package/dist/util/accessTokenParser.d.ts +0 -1
  369. package/dist/util/accessTokenParser.js +0 -34
  370. package/dist/util/accessTokenParser.js.map +0 -1
  371. package/dist/util/apiPaginate.d.ts +0 -11
  372. package/dist/util/apiPaginate.js +0 -33
  373. package/dist/util/apiPaginate.js.map +0 -1
  374. package/dist/util/apiResponse.d.ts +0 -9
  375. package/dist/util/apiResponse.js +0 -23
  376. package/dist/util/apiResponse.js.map +0 -1
  377. package/dist/util/auth.d.ts +0 -11
  378. package/dist/util/auth.js +0 -48
  379. package/dist/util/auth.js.map +0 -1
  380. package/dist/util/aws-config-migration.d.ts +0 -11
  381. package/dist/util/aws-config-migration.js +0 -68
  382. package/dist/util/aws-config-migration.js.map +0 -1
  383. package/dist/util/formUtil.d.ts +0 -2
  384. package/dist/util/formUtil.js +0 -15
  385. package/dist/util/formUtil.js.map +0 -1
  386. package/dist/util/githubAuth.d.ts +0 -2
  387. package/dist/util/githubAuth.js +0 -82
  388. package/dist/util/githubAuth.js.map +0 -1
  389. package/dist/util/googleAuth.d.ts +0 -2
  390. package/dist/util/googleAuth.js +0 -85
  391. package/dist/util/googleAuth.js.map +0 -1
  392. package/dist/util/mailer.d.ts +0 -7
  393. package/dist/util/mailer.js +0 -98
  394. package/dist/util/mailer.js.map +0 -1
  395. package/dist/util/page-status-migration.d.ts +0 -23
  396. package/dist/util/page-status-migration.js +0 -48
  397. package/dist/util/page-status-migration.js.map +0 -1
  398. package/dist/util/ssr.d.ts +0 -3
  399. package/dist/util/ssr.js +0 -9
  400. package/dist/util/ssr.js.map +0 -1
  401. package/dist/util/view.d.ts +0 -10
  402. package/dist/util/view.js +0 -99
  403. package/dist/util/view.js.map +0 -1
@@ -1,830 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const express_1 = require("@ts-rest/express");
7
- const api_contract_1 = require("@crowi/api-contract");
8
- const express_2 = require("express");
9
- const multer_1 = __importDefault(require("multer"));
10
- const node_fs_1 = __importDefault(require("node:fs"));
11
- const node_path_1 = __importDefault(require("node:path"));
12
- const mongoose_1 = require("mongoose");
13
- const debug_1 = __importDefault(require("debug"));
14
- const fileUploader_1 = __importDefault(require("../../util/fileUploader"));
15
- const rate_limit_1 = require("../../util/rate-limit");
16
- const ts_rest_helpers_1 = require("../../util/ts-rest-helpers");
17
- const debug = (0, debug_1.default)('crowi:routes:ts-rest:attachment');
18
- /**
19
- * RFC-0004 Phase 6/7 — intent-aware limits for `POST /api/v2/attachments/upload`.
20
- *
21
- * The endpoint serves two editor intents with different ceilings:
22
- * - `paste` (RFC §"Image paste limits"): 10 MB, images only — a
23
- * clipboard image blob is always an image.
24
- * - `dnd` (RFC §"D&D limits"): 50 MB, images + documents (`.pdf`,
25
- * `.txt`, `.md`, `.csv`) + archives (`.zip`).
26
- *
27
- * multer is configured with the larger (50 MB) cap so the multipart
28
- * parse never aborts a legitimate D&D upload; the per-intent size cap
29
- * is then enforced in-handler once `intent` has been parsed. The
30
- * per-intent MIME allow-list is likewise applied after the parse.
31
- */
32
- const PASTE_MAX_BYTES = 10 * 1024 * 1024;
33
- const DND_MAX_BYTES = 50 * 1024 * 1024;
34
- /** multer-level hard cap — the larger of the two intents (D&D). */
35
- const UPLOAD_MULTER_MAX_BYTES = DND_MAX_BYTES;
36
- // MIME allow-lists shared with the web editor via `@crowi/api-contract`
37
- // so client-side rejection and this authoritative check cannot drift.
38
- const PASTE_ALLOWED_MIME = new Set(api_contract_1.IMAGE_UPLOAD_MIME);
39
- const DND_ALLOWED_MIME = new Set([...api_contract_1.IMAGE_UPLOAD_MIME, ...api_contract_1.DND_EXTRA_UPLOAD_MIME]);
40
- /** Resolve the size cap + MIME allow-list for one upload intent. */
41
- const limitsForIntent = (intent) => intent === 'dnd' ? { maxBytes: DND_MAX_BYTES, allowedMime: DND_ALLOWED_MIME } : { maxBytes: PASTE_MAX_BYTES, allowedMime: PASTE_ALLOWED_MIME };
42
- /** Per-user budget for the editor upload endpoint — RFC §"Attachment upload endpoint". */
43
- const UPLOAD_RATE_LIMIT = 20;
44
- const UPLOAD_RATE_WINDOW_MS = 60_000;
45
- /**
46
- * Mime types we allow over the public `by-key` route. The route is intended
47
- * for profile pictures only (key prefix `user/`); image/* covers every
48
- * format `User.createUserPictureFilePath` can produce.
49
- */
50
- const BY_KEY_ALLOWED_PREFIX = 'user/';
51
- const KEY_EXT_TO_MIME = {
52
- png: 'image/png',
53
- jpg: 'image/jpeg',
54
- jpeg: 'image/jpeg',
55
- gif: 'image/gif',
56
- webp: 'image/webp',
57
- svg: 'image/svg+xml',
58
- bmp: 'image/bmp',
59
- };
60
- const guessMimeFromKey = (key) => {
61
- const m = key.match(/\.([^.]+)$/);
62
- if (!m)
63
- return 'application/octet-stream';
64
- return KEY_EXT_TO_MIME[m[1].toLowerCase()] || 'application/octet-stream';
65
- };
66
- const errorBody = (code, message) => ({ error: { code, message } });
67
- /**
68
- * Lowercase RFC-0004 error envelope for `POST /attachments/upload`. Kept
69
- * separate from `errorBody` because the upload endpoint's error codes
70
- * are lowercase + RFC-pinned (the editor maps each to a specific toast),
71
- * distinct from the uppercase codes of the list / add / delete routes.
72
- */
73
- const uploadErrorBody = (error, message, details) => ({
74
- error,
75
- message,
76
- ...(details ? { details } : {}),
77
- });
78
- const invalidPageIdResponse = {
79
- status: 400,
80
- body: errorBody('INVALID_PAGE_ID', 'Invalid pageId'),
81
- };
82
- const pageNotFoundResponse = {
83
- status: 404,
84
- body: errorBody('PAGE_NOT_FOUND', 'Page not found'),
85
- };
86
- /**
87
- * Phase 7 — extract the set of attachment ObjectId hex strings referenced by
88
- * a revision body. We scan the raw Markdown source (not the rendered AST)
89
- * because embed URLs appear verbatim in the source. Two URI forms are
90
- * matched: the current `/api/v2/attachments/<id>` (the `fileUrl` virtual /
91
- * stream route) and the legacy `/files/<id>` form still present in bodies
92
- * saved before the migration. Ids are lower-cased for a defensive,
93
- * case-insensitive `Set` lookup against `attachment._id.toString()`.
94
- */
95
- const ATTACHMENT_URI_RE = /(?:\/api\/v2\/attachments\/|\/files\/)([0-9a-f]{24})/gi;
96
- const collectReferencedAttachmentIds = (body) => {
97
- const ids = new Set();
98
- for (const match of body.matchAll(ATTACHMENT_URI_RE)) {
99
- ids.add(match[1].toLowerCase());
100
- }
101
- return ids;
102
- };
103
- /**
104
- * Convert an AttachmentDocument (with optional populated `creator`) into the
105
- * wire response. The model's `fileUrl` virtual returns
106
- * `/api/v2/attachments/:id` after this migration, so we surface that as
107
- * `url`.
108
- *
109
- * `inUse` (Phase 7) is supplied by the caller: `listAttachments` derives it
110
- * from the latest revision body scan, while `addAttachment` passes `false`
111
- * because a just-uploaded file is not yet referenced in the body.
112
- */
113
- const attachmentToResponse = (attachment, inUse) => {
114
- // Re-read off a JSON-serialized clone so populated subdocs (creator) come
115
- // through plainly. attachmentSchema has `toJSON: { virtuals: true }` so the
116
- // `fileUrl` virtual is included automatically.
117
- const obj = attachment.toJSON();
118
- const creator = obj.creator;
119
- const creatorPublic = (0, ts_rest_helpers_1.isPopulatedUser)(creator)
120
- ? (0, ts_rest_helpers_1.toUserPublic)(creator)
121
- : // Fallback when creator is unpopulated (shouldn't happen on our paths
122
- // because list / add both populate, but the schema requires the public
123
- // shape so we synthesize the minimum surface).
124
- (0, ts_rest_helpers_1.toUserPublic)({ _id: creator ? (0, ts_rest_helpers_1.toStringId)(creator) : '' });
125
- return {
126
- _id: (0, ts_rest_helpers_1.toStringId)(obj._id),
127
- page: (0, ts_rest_helpers_1.toStringId)(obj.page),
128
- creator: creatorPublic,
129
- filePath: obj.filePath,
130
- fileName: obj.fileName,
131
- originalName: obj.originalName ?? '',
132
- fileFormat: obj.fileFormat,
133
- fileSize: obj.fileSize,
134
- createdAt: (0, ts_rest_helpers_1.toISOStringOrNull)(obj.createdAt) ?? new Date(0).toISOString(),
135
- url: obj.fileUrl,
136
- inUse,
137
- };
138
- };
139
- /**
140
- * The `AttachmentMeta` projection (`AttachmentSchema` minus `inUse`) used by
141
- * `GET /attachments/:id/meta`. `inUse` is a page-scoped derivation; the meta
142
- * endpoint resolves a bare id and has no page context, so the field is
143
- * dropped rather than faked. Reuses `attachmentToResponse` and strips the
144
- * flag so the two stay in sync.
145
- */
146
- const attachmentToMetaResponse = (attachment) => {
147
- const { inUse: _inUse, ...meta } = attachmentToResponse(attachment, false);
148
- return meta;
149
- };
150
- exports.default = (crowi, _app) => {
151
- const s = (0, express_1.initServer)();
152
- const router = (0, express_2.Router)();
153
- const Attachment = crowi.model('Attachment');
154
- const Page = crowi.model('Page');
155
- const fileUploader = (0, fileUploader_1.default)(crowi);
156
- const upload = (0, multer_1.default)({ dest: crowi.tmpDir });
157
- /** Absolute path to the placeholder image served when an attachment is gone. */
158
- const FILE_NOT_FOUND_IMAGE = node_path_1.default.join(crowi.publicDir, 'images', 'file-not-found.png');
159
- /**
160
- * Stream the `file-not-found.png` placeholder as a `200 image/png` response.
161
- *
162
- * Phase 3 — used by `GET /api/v2/attachments/:id` when the attachment record
163
- * is missing OR its backing object is gone from storage. We deliberately
164
- * return `200` (not `404`) so an embedded `<img>` in a wiki page renders the
165
- * placeholder inline instead of a broken-image glyph. No `Content-Disposition`
166
- * is set so the image displays inline.
167
- */
168
- const servePlaceholder = (res) => {
169
- const stream = node_fs_1.default.createReadStream(FILE_NOT_FOUND_IMAGE);
170
- stream.on('error', (err) => {
171
- debug('placeholder stream error', err);
172
- if (!res.headersSent) {
173
- res.status(500).end();
174
- }
175
- else {
176
- res.end();
177
- }
178
- });
179
- res.status(200).setHeader('Content-Type', 'image/png');
180
- stream.pipe(res);
181
- };
182
- /**
183
- * Whether a storage-driver `get()` rejection means the object is simply
184
- * missing (as opposed to a real failure). The local driver throws
185
- * `code: 'ENOENT'`; the S3 driver surfaces a missing object as the AWS SDK
186
- * v3 `NoSuchKey` error (`$metadata.httpStatusCode === 404`, no `code`).
187
- */
188
- const isMissingFileError = (err) => {
189
- const e = err;
190
- return e.code === 'ENOENT' || e.name === 'NoSuchKey' || e.$metadata?.httpStatusCode === 404;
191
- };
192
- // RFC-0004 Phase 6/7 — the editor-upload endpoint caps multer at the
193
- // larger D&D ceiling (50 MB) so a legitimate drag-and-drop upload is
194
- // never aborted mid-parse. The per-intent cap (paste 10 MB / dnd
195
- // 50 MB) is then enforced in-handler once `intent` has been parsed.
196
- const editorUpload = (0, multer_1.default)({ dest: crowi.tmpDir, limits: { fileSize: UPLOAD_MULTER_MAX_BYTES } });
197
- // One shared upload limiter per process. `crowi.redis` is `null` in
198
- // single-instance dev, which selects the in-memory fallback.
199
- const uploadLimiter = (0, rate_limit_1.createRateLimiter)({
200
- name: 'attachment-upload',
201
- limit: UPLOAD_RATE_LIMIT,
202
- windowMs: UPLOAD_RATE_WINDOW_MS,
203
- redisClient: crowi.redis ?? null,
204
- });
205
- // ---------------------------------------------------------------------------
206
- // Raw Express endpoints (registered BEFORE createExpressEndpoints so they
207
- // are matched before ts-rest's path matcher attempts to dispatch).
208
- //
209
- // These deliver bytes via Readable.pipe(), which ts-rest's "return body"
210
- // handler model cannot express without buffering the entire file. Authn
211
- // is already provided by the parent authenticatedRouter (jwtAuth).
212
- // ---------------------------------------------------------------------------
213
- /**
214
- * GET /api/v2/attachments/by-key/:key(*)
215
- *
216
- * Streams a stored object identified by storage key. To prevent the route
217
- * from acting as an arbitrary read primitive, we whitelist the `user/`
218
- * prefix only — these are profile pictures whose URL is computed by
219
- * `fileUploader.generateUrl` and stored in `user.image`. Attachment-row
220
- * backed files are served by `/api/v2/attachments/:id` via grant checks.
221
- */
222
- router.get('/attachments/by-key/*', async (req, res) => {
223
- const rawKey = req.params[0];
224
- if (typeof rawKey !== 'string' || rawKey.length === 0) {
225
- res.status(400).json(errorBody('FILE_MISSING', 'Missing storage key'));
226
- return;
227
- }
228
- let key;
229
- try {
230
- key = decodeURIComponent(rawKey);
231
- }
232
- catch {
233
- res.status(400).json(errorBody('FILE_MISSING', 'Invalid storage key'));
234
- return;
235
- }
236
- if (!key.startsWith(BY_KEY_ALLOWED_PREFIX)) {
237
- res.status(403).json(errorBody('FORBIDDEN_FOR_DELETE', 'Storage key not permitted by this endpoint'));
238
- return;
239
- }
240
- let stream;
241
- try {
242
- stream = await fileUploader.findDeliveryFile(null, key);
243
- }
244
- catch (err) {
245
- const e = err;
246
- if (e.code === 'ENOENT') {
247
- res.status(404).json(errorBody('ATTACHMENT_NOT_FOUND', 'File not found'));
248
- return;
249
- }
250
- debug('by-key delivery error', err);
251
- res.status(500).json(errorBody('UPLOAD_FAILED', 'Failed to deliver file'));
252
- return;
253
- }
254
- res.setHeader('Content-Type', guessMimeFromKey(key));
255
- stream.on('error', (err) => {
256
- debug('by-key stream error', err);
257
- if (!res.headersSent) {
258
- res.status(500).end();
259
- }
260
- else {
261
- res.end();
262
- }
263
- });
264
- stream.pipe(res);
265
- });
266
- /**
267
- * GET /api/v2/attachments/:id
268
- *
269
- * Streams an attachment by Mongo ObjectId. Authorization: the caller must
270
- * be able to view the page that owns the attachment (loadGrantedPage).
271
- * 404 (not 403) when grant fails to avoid leaking the existence of a
272
- * page the caller cannot view.
273
- */
274
- router.get('/attachments/:id([0-9a-f]{24})', async (req, res) => {
275
- const user = req.user;
276
- if (!user) {
277
- res.status(401).json({ error: { code: 'AUTHENTICATION_REQUIRED', message: 'Authentication is required' } });
278
- return;
279
- }
280
- const id = req.params.id;
281
- if (!(0, ts_rest_helpers_1.isValidObjectId)(id)) {
282
- res.status(400).json(errorBody('INVALID_ATTACHMENT_ID', 'Invalid attachment id'));
283
- return;
284
- }
285
- let attachment;
286
- try {
287
- attachment = (await Attachment.findById(id));
288
- }
289
- catch (err) {
290
- debug('attachment lookup error', err);
291
- res.status(500).json(errorBody('UPLOAD_FAILED', 'Failed to load attachment'));
292
- return;
293
- }
294
- if (!attachment) {
295
- // Phase 3 — a missing attachment record means the file was deleted or
296
- // never existed; serve the placeholder image instead of a 404 so
297
- // embedded references render gracefully.
298
- servePlaceholder(res);
299
- return;
300
- }
301
- const grant = await (0, ts_rest_helpers_1.loadGrantedPage)(Page, attachment.page.toString(), user);
302
- if ('error' in grant) {
303
- // Collapse INVALID_PAGE_ID + PAGE_NOT_FOUND alike to 404 here — the
304
- // page id comes from the persisted attachment, so an INVALID_PAGE_ID
305
- // would only mean the document is corrupt.
306
- res.status(404).json(errorBody('ATTACHMENT_NOT_FOUND', 'Attachment not found'));
307
- return;
308
- }
309
- let stream;
310
- try {
311
- stream = await Attachment.findDeliveryFile(attachment);
312
- }
313
- catch (err) {
314
- // Phase 3 — the record exists but the backing object is gone from
315
- // storage (local `ENOENT` / S3 `NoSuchKey`). Serve the placeholder
316
- // image so embedded references render gracefully. Any other driver
317
- // error is a genuine failure → 500.
318
- if (isMissingFileError(err)) {
319
- servePlaceholder(res);
320
- return;
321
- }
322
- debug('attachment delivery error', err);
323
- res.status(500).json(errorBody('UPLOAD_FAILED', 'Failed to deliver file'));
324
- return;
325
- }
326
- res.setHeader('Content-Type', attachment.fileFormat);
327
- res.setHeader('Content-Disposition', `inline; filename*=UTF-8''${encodeURIComponent(attachment.originalName || attachment.fileName)}`);
328
- stream.on('error', (err) => {
329
- debug('attachment stream error', err);
330
- if (!res.headersSent) {
331
- res.status(500).end();
332
- }
333
- else {
334
- res.end();
335
- }
336
- });
337
- stream.pipe(res);
338
- });
339
- // ---------------------------------------------------------------------------
340
- // ts-rest contract handlers
341
- // ---------------------------------------------------------------------------
342
- const attachmentRouter = s.router(api_contract_1.apiContract.attachment, {
343
- /**
344
- * GET /api/v2/pages/:pageId/attachments
345
- */
346
- listAttachments: async ({ params, req }) => {
347
- const user = req.user;
348
- const { pageId } = params;
349
- const grant = await (0, ts_rest_helpers_1.loadGrantedPage)(Page, pageId, user);
350
- if ('error' in grant) {
351
- // Collapse 400 / 404 into the contract-typed shape.
352
- if (grant.error.status === 400) {
353
- return invalidPageIdResponse;
354
- }
355
- return pageNotFoundResponse;
356
- }
357
- try {
358
- const attachments = (await Attachment.getListByPageId(new mongoose_1.Types.ObjectId(pageId)));
359
- // Phase 7 — derive `inUse` from the page's latest revision body. The
360
- // page is already loaded via `loadGrantedPage` (with no revisionId,
361
- // so `grant.page.revision` is the latest revision). We read just the
362
- // body and scan it once for attachment URIs. If the revision is
363
- // missing or its body is empty we cannot determine references, so we
364
- // fall back to `inUse: true` for every attachment rather than hide
365
- // files while the reference state is undetermined.
366
- const revisionId = grant.page.revision;
367
- let referencedIds = null;
368
- if (revisionId) {
369
- const Revision = crowi.model('Revision');
370
- const revision = (await Revision.findById(revisionId).select('body'));
371
- if (revision?.body) {
372
- referencedIds = collectReferencedAttachmentIds(revision.body);
373
- }
374
- }
375
- return {
376
- status: 200,
377
- body: {
378
- attachments: attachments.map((a) => attachmentToResponse(a, referencedIds === null ? true : referencedIds.has(a._id.toString().toLowerCase()))),
379
- },
380
- };
381
- }
382
- catch (err) {
383
- debug('listAttachments error', err);
384
- return ts_rest_helpers_1.internalServerErrorResponse;
385
- }
386
- },
387
- /**
388
- * GET /api/v2/pages/:pageId/attachments/usage
389
- *
390
- * Phase 8 — full attachment usage breakdown for a page. Scans every
391
- * revision body of the page (via the `path` key) for attachment embed
392
- * URIs and splits the page's attachments into:
393
- * - `latest`: referenced by the page's current revision body.
394
- * - `past`: referenced only by older revisions (plus orphans referenced
395
- * by none), each carrying the revisions that used it.
396
- *
397
- * On-demand (no caching) — `/_attachments` is a deliberate navigation,
398
- * not a hot path. The revision query deliberately omits `renderedAst`
399
- * (multi-MB per page); only `body` is needed for the scan.
400
- */
401
- getAttachmentUsage: async ({ params, req }) => {
402
- const user = req.user;
403
- const { pageId } = params;
404
- const grant = await (0, ts_rest_helpers_1.loadGrantedPage)(Page, pageId, user);
405
- if ('error' in grant) {
406
- if (grant.error.status === 400) {
407
- return invalidPageIdResponse;
408
- }
409
- return pageNotFoundResponse;
410
- }
411
- const page = grant.page;
412
- try {
413
- const Revision = crowi.model('Revision');
414
- // All revisions of the page, newest-first. `author` is populated for
415
- // the past-revision link rendering; `renderedAst` is intentionally
416
- // excluded — it is heavy and the scan only needs the raw body.
417
- const revisions = (await Revision.find({ path: page.path }).select('_id body createdAt author').sort({ createdAt: -1 }).populate('author'));
418
- // `page.revision` may be a bare ObjectId or a populated Revision
419
- // document (findPageById populates it). Normalise to the hex id.
420
- const rawRevision = page.revision;
421
- const latestRevisionId = rawRevision == null
422
- ? null
423
- : typeof rawRevision === 'object' && rawRevision !== null && '_id' in rawRevision
424
- ? (0, ts_rest_helpers_1.toStringId)(rawRevision._id)
425
- : (0, ts_rest_helpers_1.toStringId)(rawRevision);
426
- // Per-revision referenced-id sets, plus the aggregate of which past
427
- // (non-latest) revisions reference each attachment id.
428
- let latestIds = new Set();
429
- const referencedByPast = new Map();
430
- for (const revision of revisions) {
431
- const ids = revision.body ? collectReferencedAttachmentIds(revision.body) : new Set();
432
- const isLatest = latestRevisionId !== null && revision._id.toString() === latestRevisionId;
433
- if (isLatest) {
434
- latestIds = ids;
435
- continue;
436
- }
437
- for (const id of ids) {
438
- const list = referencedByPast.get(id) ?? [];
439
- list.push({
440
- revisionId: revision._id.toString(),
441
- createdAt: (0, ts_rest_helpers_1.toISOStringOrNull)(revision.createdAt) ?? new Date(0).toISOString(),
442
- author: revision.author ?? null,
443
- });
444
- referencedByPast.set(id, list);
445
- }
446
- }
447
- const attachments = (await Attachment.getListByPageId(new mongoose_1.Types.ObjectId(pageId)));
448
- const latest = [];
449
- const past = [];
450
- for (const att of attachments) {
451
- const attId = att._id.toString().toLowerCase();
452
- if (latestIds.has(attId)) {
453
- latest.push(attachmentToResponse(att, true));
454
- continue;
455
- }
456
- // Past-only or orphan (orphan → empty referencingRevisions).
457
- const refs = referencedByPast.get(attId) ?? [];
458
- past.push({
459
- attachment: attachmentToResponse(att, false),
460
- referencingRevisions: refs.map((r) => ({
461
- revisionId: r.revisionId,
462
- createdAt: r.createdAt,
463
- author: (0, ts_rest_helpers_1.isPopulatedUser)(r.author)
464
- ? (0, ts_rest_helpers_1.toUserPublic)(r.author)
465
- : (0, ts_rest_helpers_1.toUserPublic)({ _id: r.author ? (0, ts_rest_helpers_1.toStringId)(r.author) : '' }),
466
- })),
467
- });
468
- }
469
- return {
470
- status: 200,
471
- body: { pagePath: page.path, latest, past },
472
- };
473
- }
474
- catch (err) {
475
- debug('getAttachmentUsage error', err);
476
- return ts_rest_helpers_1.internalServerErrorResponse;
477
- }
478
- },
479
- /**
480
- * GET /api/v2/attachments/:id/meta
481
- *
482
- * Metadata for a single attachment by id — backs the in-body attachment
483
- * modal. Authorization mirrors the streaming route
484
- * `GET /api/v2/attachments/:id`: the caller must be able to view the
485
- * owning page (`loadGrantedPage`). 404 (not 403) on any failure so the
486
- * existence of a hidden page / attachment is not leaked. Unlike the
487
- * streaming route there is no placeholder fallback — a missing record is
488
- * a plain 404 because the JSON consumer (the modal) cannot render an
489
- * image placeholder.
490
- */
491
- getAttachmentMeta: async ({ params, req }) => {
492
- const user = req.user;
493
- const { id } = params;
494
- if (!(0, ts_rest_helpers_1.isValidObjectId)(id)) {
495
- return {
496
- status: 400,
497
- body: errorBody('INVALID_ATTACHMENT_ID', 'Invalid attachment id'),
498
- };
499
- }
500
- let attachment;
501
- try {
502
- attachment = (await Attachment.findById(id).populate('creator'));
503
- }
504
- catch (err) {
505
- debug('getAttachmentMeta lookup error', err);
506
- return ts_rest_helpers_1.internalServerErrorResponse;
507
- }
508
- if (!attachment) {
509
- return {
510
- status: 404,
511
- body: errorBody('ATTACHMENT_NOT_FOUND', 'Attachment not found'),
512
- };
513
- }
514
- const grant = await (0, ts_rest_helpers_1.loadGrantedPage)(Page, attachment.page.toString(), user);
515
- if ('error' in grant) {
516
- // Collapse INVALID_PAGE_ID + PAGE_NOT_FOUND alike to 404 — the page
517
- // id comes from the persisted attachment, and a hidden page must not
518
- // be distinguishable from a missing one.
519
- return {
520
- status: 404,
521
- body: errorBody('ATTACHMENT_NOT_FOUND', 'Attachment not found'),
522
- };
523
- }
524
- return {
525
- status: 200,
526
- body: attachmentToMetaResponse(attachment),
527
- };
528
- },
529
- /**
530
- * POST /api/v2/pages/:pageId/attachments (multipart/form-data)
531
- *
532
- * The legacy controller supported `page_id=0` + path to implicitly create
533
- * the page. We dropped that — the client must call `createPage` first.
534
- * This is documented as a semantic change in the task plan.
535
- */
536
- addAttachment: async ({ params, req, res }) => {
537
- const user = req.user;
538
- const { pageId } = params;
539
- const grant = await (0, ts_rest_helpers_1.loadGrantedPage)(Page, pageId, user);
540
- if ('error' in grant) {
541
- if (grant.error.status === 400) {
542
- return invalidPageIdResponse;
543
- }
544
- return pageNotFoundResponse;
545
- }
546
- const pageData = grant.page;
547
- // Multer is run inside the handler — same pattern as `me.uploadPicture`.
548
- // We resolve the ts-rest response from a Promise so the handler can
549
- // wait for multer's async parse to complete.
550
- return new Promise((resolve) => {
551
- upload.single('file')(req, res, async (multerErr) => {
552
- if (multerErr) {
553
- debug('multer error', multerErr);
554
- return resolve({
555
- status: 400,
556
- body: errorBody('FILE_MISSING', 'File upload error'),
557
- });
558
- }
559
- const tmpFile = req.file || null;
560
- if (!tmpFile) {
561
- return resolve({
562
- status: 400,
563
- body: errorBody('FILE_MISSING', 'No file provided'),
564
- });
565
- }
566
- const tmpPath = tmpFile.path;
567
- const cleanupTmp = () => {
568
- node_fs_1.default.unlink(tmpPath, (unlinkErr) => {
569
- if (unlinkErr)
570
- debug('failed to unlink tmp file', unlinkErr);
571
- });
572
- };
573
- const originalName = tmpFile.originalname;
574
- const fileName = tmpFile.filename + tmpFile.originalname;
575
- const fileType = tmpFile.mimetype;
576
- const fileSize = tmpFile.size;
577
- const creator = user._id;
578
- try {
579
- const filePath = Attachment.createAttachmentFilePath(pageData._id, fileName, fileType);
580
- const tmpFileStream = node_fs_1.default.createReadStream(tmpPath, { flags: 'r', autoClose: true });
581
- await fileUploader.uploadFile(filePath, fileType, tmpFileStream, {});
582
- const created = (await Attachment.create({
583
- page: pageData._id,
584
- creator,
585
- filePath,
586
- originalName,
587
- fileName,
588
- fileFormat: fileType,
589
- fileSize,
590
- }));
591
- // Populate `creator` so the response shape matches list output.
592
- await created.populate('creator');
593
- cleanupTmp();
594
- // Phase 7 — a freshly uploaded file is not yet referenced in the
595
- // page body, so it starts `inUse: false`. The next
596
- // `listAttachments` recomputes this from the latest revision.
597
- const body = attachmentToResponse(created, false);
598
- return resolve({
599
- status: 200,
600
- body: { attachment: body, url: body.url },
601
- });
602
- }
603
- catch (err) {
604
- debug('attachment upload error', err);
605
- cleanupTmp();
606
- return resolve({
607
- status: 500,
608
- body: errorBody('UPLOAD_FAILED', 'Failed to save attachment'),
609
- });
610
- }
611
- });
612
- });
613
- },
614
- /**
615
- * POST /api/v2/attachments/upload (multipart/form-data)
616
- *
617
- * RFC-0004 Phase 6 — direct upload for the editor's paste / D&D
618
- * handlers. Differs from `addAttachment` in three ways:
619
- * 1. Rate-limited to 20 uploads/min/user (429 + `Retry-After`).
620
- * 2. Enforces the editor size (10 MB) + MIME allow-list, returning
621
- * the RFC's lowercase `{ error, message, details? }` envelope.
622
- * 3. Returns the lean `{ url, filename, mimeType, sizeBytes }`
623
- * shape the editor splices straight into the Markdown source.
624
- *
625
- * `pageId` / `intent` are multipart text fields parsed by multer
626
- * (not validated by ts-rest — see the contract comment), so they
627
- * are validated in-handler after the parse completes. Upload
628
- * progress is observed entirely client-side via
629
- * `XMLHttpRequest.upload.onprogress`; the server receives the
630
- * multipart body with no special streaming protocol.
631
- */
632
- uploadAttachment: async ({ req, res }) => {
633
- const user = req.user;
634
- // 1. Rate limit before touching the (potentially large) body.
635
- const rate = await uploadLimiter.hit(user._id.toString());
636
- if (!rate.allowed) {
637
- res.setHeader('Retry-After', String(rate.retryAfterSeconds));
638
- return {
639
- status: 429,
640
- body: uploadErrorBody('rate_limited', `Upload limit reached. Try again in ${rate.retryAfterSeconds} seconds.`, {
641
- retryAfterSeconds: rate.retryAfterSeconds,
642
- }),
643
- };
644
- }
645
- // Multer runs inside the handler (same pattern as `addAttachment`)
646
- // so the ts-rest response can await the async multipart parse.
647
- return new Promise((resolve) => {
648
- editorUpload.single('file')(req, res, async (multerErr) => {
649
- const tmpFile = req.file || null;
650
- const cleanupTmp = () => {
651
- if (!tmpFile)
652
- return;
653
- node_fs_1.default.unlink(tmpFile.path, (unlinkErr) => {
654
- if (unlinkErr)
655
- debug('failed to unlink tmp file', unlinkErr);
656
- });
657
- };
658
- if (multerErr) {
659
- cleanupTmp();
660
- // multer raises `LIMIT_FILE_SIZE` when the body exceeds the
661
- // configured (50 MB / D&D) cap — surface it as the RFC's
662
- // `too_large` (413). The per-intent paste cap (10 MB) is a
663
- // smaller in-handler check below.
664
- const code = multerErr.code;
665
- if (code === 'LIMIT_FILE_SIZE') {
666
- return resolve({
667
- status: 413,
668
- body: uploadErrorBody('too_large', 'The file is too large to upload.', { maxBytes: UPLOAD_MULTER_MAX_BYTES }),
669
- });
670
- }
671
- debug('editor upload multer error', multerErr);
672
- return resolve({
673
- status: 400,
674
- body: uploadErrorBody('disallowed_type', 'File upload error.'),
675
- });
676
- }
677
- // --- Validate the multipart text fields (multer has parsed them) ---
678
- const body = req.body;
679
- const pageId = typeof body.pageId === 'string' ? body.pageId : '';
680
- const intent = body.intent === 'paste' || body.intent === 'dnd' ? body.intent : null;
681
- if (!tmpFile) {
682
- cleanupTmp();
683
- return resolve({
684
- status: 400,
685
- body: uploadErrorBody('disallowed_type', 'No file provided.'),
686
- });
687
- }
688
- if (!(0, ts_rest_helpers_1.isValidObjectId)(pageId)) {
689
- cleanupTmp();
690
- return resolve({
691
- status: 400,
692
- body: uploadErrorBody('no_permission', 'A valid pageId is required.'),
693
- });
694
- }
695
- if (!intent) {
696
- cleanupTmp();
697
- return resolve({
698
- status: 400,
699
- body: uploadErrorBody('disallowed_type', "The intent field must be 'paste' or 'dnd'."),
700
- });
701
- }
702
- // --- Intent-aware size + MIME enforcement ---
703
- // multer's 50 MB cap is the D&D ceiling; a `paste` upload
704
- // (clipboard image) is held to the smaller 10 MB cap here.
705
- const { maxBytes, allowedMime } = limitsForIntent(intent);
706
- if (tmpFile.size > maxBytes) {
707
- cleanupTmp();
708
- return resolve({
709
- status: 413,
710
- body: uploadErrorBody('too_large', 'The file is too large to upload.', { maxBytes }),
711
- });
712
- }
713
- const fileType = tmpFile.mimetype;
714
- if (!allowedMime.has(fileType)) {
715
- cleanupTmp();
716
- return resolve({
717
- status: 415,
718
- body: uploadErrorBody('disallowed_type', `Files of type ${fileType} cannot be uploaded.`, { mimeType: fileType }),
719
- });
720
- }
721
- // --- Permission: a caller who can view the page can attach to
722
- // it (same posture as `addAttachment`). Grant failure → 403. ---
723
- const grant = await (0, ts_rest_helpers_1.loadGrantedPage)(Page, pageId, user);
724
- if ('error' in grant) {
725
- cleanupTmp();
726
- return resolve({
727
- status: 403,
728
- body: uploadErrorBody('no_permission', 'You do not have permission to attach files to this page.'),
729
- });
730
- }
731
- const pageData = grant.page;
732
- const originalName = tmpFile.originalname;
733
- const fileName = tmpFile.filename + tmpFile.originalname;
734
- const fileSize = tmpFile.size;
735
- try {
736
- const filePath = Attachment.createAttachmentFilePath(pageData._id, fileName, fileType);
737
- const tmpFileStream = node_fs_1.default.createReadStream(tmpFile.path, { flags: 'r', autoClose: true });
738
- await fileUploader.uploadFile(filePath, fileType, tmpFileStream, {});
739
- const created = (await Attachment.create({
740
- page: pageData._id,
741
- creator: user._id,
742
- filePath,
743
- originalName,
744
- fileName,
745
- fileFormat: fileType,
746
- fileSize,
747
- }));
748
- cleanupTmp();
749
- debug('editor upload ok', { intent, pageId, attachmentId: created._id.toString() });
750
- return resolve({
751
- status: 200,
752
- body: {
753
- url: created.fileUrl,
754
- filename: originalName,
755
- mimeType: fileType,
756
- sizeBytes: fileSize,
757
- },
758
- });
759
- }
760
- catch (err) {
761
- debug('editor upload error', err);
762
- cleanupTmp();
763
- return resolve(ts_rest_helpers_1.internalServerErrorResponse);
764
- }
765
- });
766
- });
767
- },
768
- /**
769
- * DELETE /api/v2/attachments/:id
770
- *
771
- * Authorization (wiki policy): any authenticated user who can view the
772
- * owning page may delete an attachment — the same open-collaboration
773
- * posture as page editing. The caller still has to pass the page grant
774
- * check (so an attachment on a page they cannot see stays a 404), but
775
- * we no longer restrict deletion to the creator / admin / grantedUsers.
776
- * The legacy `/_api/attachments.remove` allowed even anonymous
777
- * deletion; we keep it authenticated-only (`authenticatedRouter`).
778
- */
779
- removeAttachment: async ({ params, req }) => {
780
- const user = req.user;
781
- const { id } = params;
782
- if (!(0, ts_rest_helpers_1.isValidObjectId)(id)) {
783
- return {
784
- status: 400,
785
- body: errorBody('INVALID_ATTACHMENT_ID', 'Invalid attachment id'),
786
- };
787
- }
788
- let attachment;
789
- try {
790
- attachment = (await Attachment.findById(id));
791
- }
792
- catch (err) {
793
- debug('attachment lookup error', err);
794
- return {
795
- status: 500,
796
- body: errorBody('REMOVE_FAILED', 'Failed to load attachment'),
797
- };
798
- }
799
- if (!attachment) {
800
- return {
801
- status: 404,
802
- body: errorBody('ATTACHMENT_NOT_FOUND', 'Attachment not found'),
803
- };
804
- }
805
- // Resolve the page for the view-grant check. Any authenticated user
806
- // who can view the page may delete its attachments (wiki policy).
807
- const grant = await (0, ts_rest_helpers_1.loadGrantedPage)(Page, attachment.page.toString(), user);
808
- if ('error' in grant) {
809
- return {
810
- status: 404,
811
- body: errorBody('ATTACHMENT_NOT_FOUND', 'Attachment not found'),
812
- };
813
- }
814
- try {
815
- await Attachment.removeAttachment(attachment);
816
- return { status: 200, body: { success: true } };
817
- }
818
- catch (err) {
819
- debug('removeAttachment error', err);
820
- return {
821
- status: 500,
822
- body: errorBody('REMOVE_FAILED', 'Failed to delete attachment'),
823
- };
824
- }
825
- },
826
- });
827
- (0, express_1.createExpressEndpoints)(api_contract_1.apiContract.attachment, attachmentRouter, router);
828
- return router;
829
- };
830
- //# sourceMappingURL=attachment.js.map