@meith/web 0.16.0

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 (511) hide show
  1. package/LICENSE.md +165 -0
  2. package/app/(auth)/layout.tsx +6 -0
  3. package/app/(auth)/login/page.tsx +106 -0
  4. package/app/(auth)/login/verify/page.tsx +55 -0
  5. package/app/(auth)/register/page.tsx +83 -0
  6. package/app/(auth)/reset/confirm/page.tsx +45 -0
  7. package/app/(auth)/reset/page.tsx +22 -0
  8. package/app/(auth)/verify/page.tsx +44 -0
  9. package/app/(auth)/verify/resend/page.tsx +51 -0
  10. package/app/(board)/[slug]/feed.xml/route.ts +48 -0
  11. package/app/(board)/[slug]/new/page.tsx +112 -0
  12. package/app/(board)/[slug]/page.tsx +403 -0
  13. package/app/(board)/discover/[view]/page.tsx +115 -0
  14. package/app/(board)/discover/page.tsx +5 -0
  15. package/app/(board)/layout.tsx +22 -0
  16. package/app/(board)/member/[id]/page.tsx +187 -0
  17. package/app/(board)/member/[id]/reputation/page.tsx +195 -0
  18. package/app/(board)/member/by-name/[name]/page.tsx +32 -0
  19. package/app/(board)/messages/[id]/page.tsx +103 -0
  20. package/app/(board)/messages/compose/page.tsx +60 -0
  21. package/app/(board)/messages/layout.tsx +5 -0
  22. package/app/(board)/messages/page.tsx +179 -0
  23. package/app/(board)/modcp/forums/page.tsx +59 -0
  24. package/app/(board)/modcp/ip/page.tsx +158 -0
  25. package/app/(board)/modcp/layout.tsx +5 -0
  26. package/app/(board)/modcp/log/page.tsx +98 -0
  27. package/app/(board)/modcp/page.tsx +112 -0
  28. package/app/(board)/moderation/layout.tsx +5 -0
  29. package/app/(board)/moderation/page.tsx +111 -0
  30. package/app/(board)/moderation/reports/page.tsx +193 -0
  31. package/app/(board)/moderation/warn/page.tsx +158 -0
  32. package/app/(board)/notifications/layout.tsx +5 -0
  33. package/app/(board)/notifications/page.tsx +144 -0
  34. package/app/(board)/notifications/preferences/page.tsx +65 -0
  35. package/app/(board)/online/page.tsx +119 -0
  36. package/app/(board)/page.tsx +195 -0
  37. package/app/(board)/plugins/[key]/[[...path]]/page.tsx +72 -0
  38. package/app/(board)/privacy/page.tsx +12 -0
  39. package/app/(board)/report/page.tsx +71 -0
  40. package/app/(board)/rules/page.tsx +12 -0
  41. package/app/(board)/search/[token]/page.tsx +130 -0
  42. package/app/(board)/search/page.tsx +289 -0
  43. package/app/(board)/stats/page.tsx +202 -0
  44. package/app/(board)/subscriptions/layout.tsx +5 -0
  45. package/app/(board)/subscriptions/page.tsx +142 -0
  46. package/app/(board)/terms/page.tsx +12 -0
  47. package/app/(board)/thread/[slug]/edit/page.tsx +107 -0
  48. package/app/(board)/thread/[slug]/feed.xml/route.ts +42 -0
  49. package/app/(board)/thread/[slug]/page.tsx +707 -0
  50. package/app/(board)/thread/[slug]/poll/page.tsx +41 -0
  51. package/app/(board)/thread/[slug]/post/[postId]/history/page.tsx +160 -0
  52. package/app/(board)/thread/[slug]/reply/page.tsx +136 -0
  53. package/app/(board)/unsubscribe/page.tsx +73 -0
  54. package/app/(board)/usercp/avatar/page.tsx +56 -0
  55. package/app/(board)/usercp/contacts/page.tsx +141 -0
  56. package/app/(board)/usercp/email/confirm/page.tsx +52 -0
  57. package/app/(board)/usercp/layout.tsx +23 -0
  58. package/app/(board)/usercp/options/page.tsx +56 -0
  59. package/app/(board)/usercp/page.tsx +71 -0
  60. package/app/(board)/usercp/profile/page.tsx +81 -0
  61. package/app/(board)/usercp/security/page.tsx +238 -0
  62. package/app/(board)/usercp/security/verify/page.tsx +92 -0
  63. package/app/(board)/usercp/signature/page.tsx +67 -0
  64. package/app/admin/antispam/page.tsx +155 -0
  65. package/app/admin/api/marketplace/screenshot/route.ts +68 -0
  66. package/app/admin/api/plugins/[key]/[...path]/route.ts +27 -0
  67. package/app/admin/api-tokens/page.tsx +129 -0
  68. package/app/admin/content/announcements/page.tsx +110 -0
  69. package/app/admin/content/attachments/page.tsx +199 -0
  70. package/app/admin/content/navigation/page.tsx +123 -0
  71. package/app/admin/content/page.tsx +170 -0
  72. package/app/admin/forums/[id]/page.tsx +96 -0
  73. package/app/admin/forums/[id]/permissions/page.tsx +108 -0
  74. package/app/admin/forums/page.tsx +45 -0
  75. package/app/admin/groups/[id]/page.tsx +133 -0
  76. package/app/admin/groups/memberships/page.tsx +56 -0
  77. package/app/admin/groups/page.tsx +104 -0
  78. package/app/admin/groups/promotions/page.tsx +143 -0
  79. package/app/admin/layout.tsx +86 -0
  80. package/app/admin/log/page.tsx +116 -0
  81. package/app/admin/page.tsx +206 -0
  82. package/app/admin/plugins/[key]/[[...path]]/page.tsx +276 -0
  83. package/app/admin/plugins/browse/page.tsx +70 -0
  84. package/app/admin/plugins/page.tsx +165 -0
  85. package/app/admin/security/page.tsx +124 -0
  86. package/app/admin/settings/page.tsx +190 -0
  87. package/app/admin/system/page.tsx +303 -0
  88. package/app/admin/themes/[key]/page.tsx +110 -0
  89. package/app/admin/themes/browse/page.tsx +70 -0
  90. package/app/admin/themes/page.tsx +163 -0
  91. package/app/admin/users/[id]/merge/page.tsx +136 -0
  92. package/app/admin/users/[id]/page.tsx +208 -0
  93. package/app/admin/users/mail/page.tsx +68 -0
  94. package/app/admin/users/page.tsx +260 -0
  95. package/app/admin/users/prune/page.tsx +186 -0
  96. package/app/api/health/route.ts +11 -0
  97. package/app/api/metrics/route.ts +66 -0
  98. package/app/api/plugins/[key]/[...path]/route.ts +27 -0
  99. package/app/api/read/all/route.ts +15 -0
  100. package/app/api/read/forum/[id]/route.ts +29 -0
  101. package/app/api/read/thread/[id]/route.ts +40 -0
  102. package/app/api/ready/route.ts +63 -0
  103. package/app/api/system/tick/route.ts +77 -0
  104. package/app/api/v1/[...path]/route.ts +208 -0
  105. package/app/api/v1/openapi.json/route.ts +12 -0
  106. package/app/apple-icon.ts +11 -0
  107. package/app/atom.xml/route.ts +7 -0
  108. package/app/attachment/[id]/route.ts +10 -0
  109. package/app/attachment/[id]/thumb/route.ts +10 -0
  110. package/app/auth/passkey/options/route.ts +145 -0
  111. package/app/auth/passkey/verify/route.ts +241 -0
  112. package/app/auth/resume/route.ts +55 -0
  113. package/app/auth/sso/[provider]/callback/route.ts +173 -0
  114. package/app/auth/sso/[provider]/go/route.ts +57 -0
  115. package/app/auth/sso/[provider]/route.ts +105 -0
  116. package/app/avatar/[id]/route.ts +31 -0
  117. package/app/brand/icon-192.png/route.ts +7 -0
  118. package/app/brand/icon-512.png/route.ts +7 -0
  119. package/app/brand/icon-maskable.png/route.ts +7 -0
  120. package/app/demo/stripe/[[...path]]/route.ts +27 -0
  121. package/app/error.tsx +15 -0
  122. package/app/feed.xml/route.ts +7 -0
  123. package/app/forumdisplay.php/page.tsx +11 -0
  124. package/app/group/[id]/badge/[scheme]/route.ts +26 -0
  125. package/app/icon.ts +13 -0
  126. package/app/install/page.tsx +221 -0
  127. package/app/jump/page.tsx +28 -0
  128. package/app/layout.tsx +124 -0
  129. package/app/logo/[scheme]/route.ts +24 -0
  130. package/app/manifest.ts +43 -0
  131. package/app/member.php/page.tsx +11 -0
  132. package/app/memberlist.php/page.tsx +11 -0
  133. package/app/not-found.tsx +14 -0
  134. package/app/opengraph-image.ts +11 -0
  135. package/app/redirect/page.tsx +44 -0
  136. package/app/robots.txt/route.ts +35 -0
  137. package/app/showthread.php/page.tsx +11 -0
  138. package/app/sitemap/[page]/route.ts +64 -0
  139. package/app/sitemap.xml/route.ts +31 -0
  140. package/app/twitter-image.ts +11 -0
  141. package/app/viewforum.php/page.tsx +11 -0
  142. package/app/viewtopic.php/page.tsx +11 -0
  143. package/bin/forum-web.mjs +241 -0
  144. package/components.json +21 -0
  145. package/instrumentation.ts +18 -0
  146. package/next.config.mjs +182 -0
  147. package/package.json +99 -0
  148. package/postcss.config.mjs +7 -0
  149. package/proxy.ts +160 -0
  150. package/src/components/account/active-sessions.tsx +87 -0
  151. package/src/components/account/avatar-form.tsx +119 -0
  152. package/src/components/account/credential-proof-form.tsx +76 -0
  153. package/src/components/account/logout-form.tsx +14 -0
  154. package/src/components/account/notification-forms.tsx +157 -0
  155. package/src/components/account/passkey-enrol.tsx +81 -0
  156. package/src/components/account/passkey-proof-button.tsx +47 -0
  157. package/src/components/account/push-device-form.tsx +193 -0
  158. package/src/components/account/relation-forms.tsx +69 -0
  159. package/src/components/account/reputation-forms.tsx +124 -0
  160. package/src/components/account/security-activity.tsx +46 -0
  161. package/src/components/account/sign-in-methods.tsx +166 -0
  162. package/src/components/account/subscription-forms.tsx +151 -0
  163. package/src/components/account/two-factor-forms.tsx +226 -0
  164. package/src/components/account/usercp-forms.tsx +387 -0
  165. package/src/components/account/usercp-nav.tsx +14 -0
  166. package/src/components/account/usercp-shell.tsx +34 -0
  167. package/src/components/admin/admin-forms.tsx +92 -0
  168. package/src/components/admin/admin-nav.tsx +52 -0
  169. package/src/components/admin/admin-undo.tsx +32 -0
  170. package/src/components/admin/api-token-forms.tsx +89 -0
  171. package/src/components/admin/badge-forms.tsx +97 -0
  172. package/src/components/admin/branding-forms.tsx +102 -0
  173. package/src/components/admin/content-forms.tsx +712 -0
  174. package/src/components/admin/form-bits.tsx +11 -0
  175. package/src/components/admin/forum-forms.tsx +524 -0
  176. package/src/components/admin/forum-tree.tsx +454 -0
  177. package/src/components/admin/group-forms.tsx +646 -0
  178. package/src/components/admin/mail-test-card.tsx +90 -0
  179. package/src/components/admin/marketplace-forms.tsx +30 -0
  180. package/src/components/admin/marketplace-listing.tsx +123 -0
  181. package/src/components/admin/navigation-forms.tsx +229 -0
  182. package/src/components/admin/navigation-tree.tsx +442 -0
  183. package/src/components/admin/oklch-picker.tsx +180 -0
  184. package/src/components/admin/plugin-forms.tsx +225 -0
  185. package/src/components/admin/settings-form.tsx +148 -0
  186. package/src/components/admin/system-forms.tsx +169 -0
  187. package/src/components/admin/theme-changes.tsx +354 -0
  188. package/src/components/admin/theme-forms.tsx +636 -0
  189. package/src/components/admin/theme-palette.tsx +119 -0
  190. package/src/components/admin/theme-preview.tsx +461 -0
  191. package/src/components/admin/user-bulk-toolbar.tsx +99 -0
  192. package/src/components/admin/user-forms.tsx +488 -0
  193. package/src/components/auth/auth-page.tsx +45 -0
  194. package/src/components/auth/form-controls.tsx +117 -0
  195. package/src/components/auth/login-form.tsx +47 -0
  196. package/src/components/auth/passkey-client.ts +171 -0
  197. package/src/components/auth/passkey-second-factor.tsx +69 -0
  198. package/src/components/auth/passkey-sign-in.tsx +61 -0
  199. package/src/components/auth/register-form.tsx +136 -0
  200. package/src/components/auth/resend-verification-form.tsx +34 -0
  201. package/src/components/auth/reset-confirm-form.tsx +43 -0
  202. package/src/components/auth/reset-request-form.tsx +35 -0
  203. package/src/components/auth/second-factor-form.tsx +51 -0
  204. package/src/components/auth/sso-buttons.tsx +47 -0
  205. package/src/components/board/live-region.tsx +74 -0
  206. package/src/components/board/search-off-notice.tsx +18 -0
  207. package/src/components/board/section-page.tsx +114 -0
  208. package/src/components/content/attachment-field.tsx +51 -0
  209. package/src/components/content/composer-field.ts +31 -0
  210. package/src/components/content/composer-ids.ts +6 -0
  211. package/src/components/content/composer-intents.tsx +27 -0
  212. package/src/components/content/composer-recovery.tsx +215 -0
  213. package/src/components/content/edit-post-form.tsx +121 -0
  214. package/src/components/content/markdown-editor.tsx +590 -0
  215. package/src/components/content/markdown-syntax.ts +53 -0
  216. package/src/components/content/mention-token.ts +23 -0
  217. package/src/components/content/multiquote-button.tsx +32 -0
  218. package/src/components/content/multiquote-selection.tsx +105 -0
  219. package/src/components/content/multiquote.ts +125 -0
  220. package/src/components/content/new-thread-form.tsx +169 -0
  221. package/src/components/content/poll-edit-form.tsx +96 -0
  222. package/src/components/content/poll.tsx +89 -0
  223. package/src/components/content/quote-in-place.tsx +51 -0
  224. package/src/components/content/reply-form.tsx +113 -0
  225. package/src/components/content/thanks-button.tsx +65 -0
  226. package/src/components/content/thread-rating.tsx +143 -0
  227. package/src/components/install/install-form.tsx +497 -0
  228. package/src/components/legal/legal-document.tsx +22 -0
  229. package/src/components/messages/message-forms.tsx +152 -0
  230. package/src/components/moderation/inline-moderation-form.tsx +158 -0
  231. package/src/components/moderation/modcp-nav.tsx +15 -0
  232. package/src/components/moderation/modcp-shell.tsx +41 -0
  233. package/src/components/moderation/queue-form.tsx +96 -0
  234. package/src/components/moderation/report-forms.tsx +120 -0
  235. package/src/components/moderation/signature-lock-form.tsx +83 -0
  236. package/src/components/moderation/thread-surgery-form.tsx +96 -0
  237. package/src/components/moderation/thread-tools-form.tsx +137 -0
  238. package/src/components/moderation/warning-forms.tsx +125 -0
  239. package/src/components/profile/custom-field.tsx +79 -0
  240. package/src/components/shell/appearance-icons.tsx +35 -0
  241. package/src/components/shell/board-notice.tsx +21 -0
  242. package/src/components/shell/copy-record.ts +20 -0
  243. package/src/components/shell/copy.tsx +17 -0
  244. package/src/components/shell/crash-notice.tsx +86 -0
  245. package/src/components/shell/demo-banner.tsx +49 -0
  246. package/src/components/shell/group-name-style.tsx +6 -0
  247. package/src/components/shell/measure.ts +1 -0
  248. package/src/components/shell/notification-menu.tsx +361 -0
  249. package/src/components/shell/offline-notice.tsx +29 -0
  250. package/src/components/shell/onboarding-banner.tsx +39 -0
  251. package/src/components/shell/page-shell.tsx +167 -0
  252. package/src/components/shell/panel-list.tsx +95 -0
  253. package/src/components/shell/panel-nav.tsx +40 -0
  254. package/src/components/shell/panel-overview.tsx +81 -0
  255. package/src/components/shell/panel-page.tsx +96 -0
  256. package/src/components/shell/panel-pagination.tsx +28 -0
  257. package/src/components/shell/panel-shell.tsx +31 -0
  258. package/src/components/shell/theme-runtime-style.tsx +6 -0
  259. package/src/components/shell/theme-switcher.tsx +97 -0
  260. package/src/components/shell/timezone-probe.tsx +45 -0
  261. package/src/components/shell/view-tabs.tsx +60 -0
  262. package/src/config.ts +13 -0
  263. package/src/server/absent-services.ts +29 -0
  264. package/src/server/admin-actions.ts +171 -0
  265. package/src/server/admin-settings-actions.ts +83 -0
  266. package/src/server/admin-undo-actions.ts +70 -0
  267. package/src/server/admin-undo.ts +75 -0
  268. package/src/server/admin.ts +112 -0
  269. package/src/server/announcements.ts +82 -0
  270. package/src/server/antispam.ts +310 -0
  271. package/src/server/api/content.ts +486 -0
  272. package/src/server/api/http.ts +119 -0
  273. package/src/server/api/members.ts +109 -0
  274. package/src/server/api/messages.ts +113 -0
  275. package/src/server/api/registry.ts +30 -0
  276. package/src/server/api/search.ts +98 -0
  277. package/src/server/api/subscriptions.ts +106 -0
  278. package/src/server/api-auth.ts +66 -0
  279. package/src/server/api-token-actions.ts +85 -0
  280. package/src/server/api-tokens-admin.ts +95 -0
  281. package/src/server/appearance-actions.ts +39 -0
  282. package/src/server/attachment-download.ts +43 -0
  283. package/src/server/attachment-embed.ts +63 -0
  284. package/src/server/attachment-upload-actions.ts +92 -0
  285. package/src/server/attachments.ts +257 -0
  286. package/src/server/auth-actions.ts +479 -0
  287. package/src/server/auth-config.ts +61 -0
  288. package/src/server/auth-events.ts +92 -0
  289. package/src/server/auth-form-state.ts +14 -0
  290. package/src/server/auth-mail.ts +104 -0
  291. package/src/server/avatars.ts +83 -0
  292. package/src/server/board-latest-actions.ts +7 -0
  293. package/src/server/board-latest.tsx +103 -0
  294. package/src/server/board-offline.ts +23 -0
  295. package/src/server/board-url.ts +14 -0
  296. package/src/server/brand-assets.ts +151 -0
  297. package/src/server/brand-mark.tsx +110 -0
  298. package/src/server/branding-actions.ts +53 -0
  299. package/src/server/branding.ts +101 -0
  300. package/src/server/cache-targets.ts +13 -0
  301. package/src/server/code-highlighting.ts +33 -0
  302. package/src/server/container.ts +402 -0
  303. package/src/server/content-actions.ts +459 -0
  304. package/src/server/content-admin-actions.ts +492 -0
  305. package/src/server/content-admin.ts +95 -0
  306. package/src/server/content-security-policy.ts +35 -0
  307. package/src/server/context.ts +32 -0
  308. package/src/server/cookies.ts +139 -0
  309. package/src/server/credential-proof-actions.ts +42 -0
  310. package/src/server/credential-proof.ts +35 -0
  311. package/src/server/current-location.ts +19 -0
  312. package/src/server/demo-stripe.ts +104 -0
  313. package/src/server/demo-uploads.ts +28 -0
  314. package/src/server/demo.ts +80 -0
  315. package/src/server/discovery.ts +104 -0
  316. package/src/server/embeds.ts +113 -0
  317. package/src/server/error-notice.tsx +41 -0
  318. package/src/server/federation-actions.ts +113 -0
  319. package/src/server/federation.ts +138 -0
  320. package/src/server/feed-builder.ts +72 -0
  321. package/src/server/feed-routes.ts +122 -0
  322. package/src/server/fixture-actor-source.ts +62 -0
  323. package/src/server/fixture-forum-repo.ts +59 -0
  324. package/src/server/fixture-member-profile-repo.ts +30 -0
  325. package/src/server/fixture-post-repo.ts +108 -0
  326. package/src/server/fixture-thread-repo.ts +98 -0
  327. package/src/server/form-state-reporter.ts +34 -0
  328. package/src/server/form-values.ts +23 -0
  329. package/src/server/forum-admin-actions.ts +353 -0
  330. package/src/server/forum-admin.ts +98 -0
  331. package/src/server/group-admin-actions.ts +341 -0
  332. package/src/server/group-admin.ts +98 -0
  333. package/src/server/group-badge-actions.ts +61 -0
  334. package/src/server/group-badge.ts +62 -0
  335. package/src/server/group-identity.ts +104 -0
  336. package/src/server/i18n-catalogs.ts +42 -0
  337. package/src/server/i18n.ts +69 -0
  338. package/src/server/image-upload.ts +109 -0
  339. package/src/server/inline-moderation-actions.ts +129 -0
  340. package/src/server/install-actions.ts +124 -0
  341. package/src/server/install.ts +281 -0
  342. package/src/server/legacy-redirect.ts +83 -0
  343. package/src/server/legal.ts +43 -0
  344. package/src/server/location-header.ts +37 -0
  345. package/src/server/mail-brand.ts +43 -0
  346. package/src/server/mail-health.ts +41 -0
  347. package/src/server/mail-send.ts +44 -0
  348. package/src/server/mail-test-actions.ts +67 -0
  349. package/src/server/mail-test.ts +56 -0
  350. package/src/server/markdown-pipeline.ts +31 -0
  351. package/src/server/marketplace-actions.ts +47 -0
  352. package/src/server/marketplace-admin.ts +260 -0
  353. package/src/server/mention-actions.ts +21 -0
  354. package/src/server/mention-search.ts +35 -0
  355. package/src/server/message-actions.ts +110 -0
  356. package/src/server/messages.ts +141 -0
  357. package/src/server/modcp.ts +125 -0
  358. package/src/server/moderation-actions.ts +165 -0
  359. package/src/server/navigation-admin-actions.ts +217 -0
  360. package/src/server/navigation.ts +112 -0
  361. package/src/server/nonce.ts +13 -0
  362. package/src/server/notification-actions.ts +84 -0
  363. package/src/server/notification-audience.ts +10 -0
  364. package/src/server/notification-menu-actions.ts +82 -0
  365. package/src/server/notification-menu.ts +96 -0
  366. package/src/server/notifications.ts +93 -0
  367. package/src/server/onboarding-actions.ts +27 -0
  368. package/src/server/onboarding.ts +89 -0
  369. package/src/server/page-metadata.ts +14 -0
  370. package/src/server/passkey-challenge.ts +42 -0
  371. package/src/server/plugin-admin-actions.ts +232 -0
  372. package/src/server/plugin-admin.ts +276 -0
  373. package/src/server/plugin-host.ts +143 -0
  374. package/src/server/plugin-pages.ts +192 -0
  375. package/src/server/plugin-panel.ts +49 -0
  376. package/src/server/plugin-routes.ts +298 -0
  377. package/src/server/plugin-view.tsx +58 -0
  378. package/src/server/poll-actions.ts +104 -0
  379. package/src/server/poll-scope.ts +52 -0
  380. package/src/server/post-notifications.ts +71 -0
  381. package/src/server/post-scope.ts +86 -0
  382. package/src/server/presence.ts +177 -0
  383. package/src/server/profile-fields.ts +112 -0
  384. package/src/server/push-actions.ts +113 -0
  385. package/src/server/push.ts +40 -0
  386. package/src/server/redirect-back.ts +15 -0
  387. package/src/server/registration.ts +7 -0
  388. package/src/server/relation-actions.ts +89 -0
  389. package/src/server/relations.ts +45 -0
  390. package/src/server/reply-core.ts +173 -0
  391. package/src/server/report-actions.ts +165 -0
  392. package/src/server/report-scope.ts +21 -0
  393. package/src/server/reputation-actions.ts +146 -0
  394. package/src/server/reputation-target.ts +49 -0
  395. package/src/server/reputation.ts +70 -0
  396. package/src/server/request-fingerprint.ts +65 -0
  397. package/src/server/safe-path.ts +16 -0
  398. package/src/server/same-origin.ts +45 -0
  399. package/src/server/search-page.ts +313 -0
  400. package/src/server/search.ts +45 -0
  401. package/src/server/secret-auth.ts +20 -0
  402. package/src/server/see-other.ts +11 -0
  403. package/src/server/seed-board.ts +417 -0
  404. package/src/server/session-actions.ts +78 -0
  405. package/src/server/session-cookies.ts +124 -0
  406. package/src/server/session-key.ts +17 -0
  407. package/src/server/settings.ts +47 -0
  408. package/src/server/signatures.ts +62 -0
  409. package/src/server/sso-handshake.ts +79 -0
  410. package/src/server/stats.ts +63 -0
  411. package/src/server/subscription-actions.ts +161 -0
  412. package/src/server/surgery-actions.ts +180 -0
  413. package/src/server/syndication.ts +77 -0
  414. package/src/server/system-admin-actions.ts +134 -0
  415. package/src/server/system-admin.ts +90 -0
  416. package/src/server/test-container.ts +114 -0
  417. package/src/server/theme-admin-actions.ts +242 -0
  418. package/src/server/theme-admin.ts +203 -0
  419. package/src/server/theme-runtime.ts +89 -0
  420. package/src/server/theme-style.ts +279 -0
  421. package/src/server/theme.ts +52 -0
  422. package/src/server/thread-core.ts +207 -0
  423. package/src/server/thread-rating-actions.ts +55 -0
  424. package/src/server/thread-tool-actions.ts +154 -0
  425. package/src/server/two-factor-actions.ts +178 -0
  426. package/src/server/two-factor.ts +86 -0
  427. package/src/server/upgrade-notice.ts +41 -0
  428. package/src/server/user-admin-actions.ts +572 -0
  429. package/src/server/user-admin.ts +165 -0
  430. package/src/server/usercp-actions.ts +263 -0
  431. package/src/server/usercp-mail.ts +120 -0
  432. package/src/server/viewer-preferences.ts +64 -0
  433. package/src/server/warning-actions.ts +122 -0
  434. package/src/styles/globals.css +969 -0
  435. package/src/theme/contract.fixture.ts +793 -0
  436. package/src/view/account-copy.ts +351 -0
  437. package/src/view/admin-content-copy.ts +66 -0
  438. package/src/view/admin-copy.ts +106 -0
  439. package/src/view/admin-forum-tree-copy.ts +40 -0
  440. package/src/view/admin-group-copy.ts +80 -0
  441. package/src/view/admin-log.ts +58 -0
  442. package/src/view/admin-nav.ts +164 -0
  443. package/src/view/admin-navigation-copy.ts +58 -0
  444. package/src/view/admin-panel-copy.ts +194 -0
  445. package/src/view/admin-settings.ts +173 -0
  446. package/src/view/admin-theme-copy.ts +229 -0
  447. package/src/view/admin-user-copy.ts +96 -0
  448. package/src/view/attachments.ts +51 -0
  449. package/src/view/auth-copy.ts +147 -0
  450. package/src/view/board-index.ts +167 -0
  451. package/src/view/board-latest.ts +101 -0
  452. package/src/view/breadcrumb.ts +50 -0
  453. package/src/view/contacts.ts +85 -0
  454. package/src/view/content-copy.ts +118 -0
  455. package/src/view/contrast.ts +286 -0
  456. package/src/view/copy.ts +101 -0
  457. package/src/view/count.ts +8 -0
  458. package/src/view/discovery-view.ts +74 -0
  459. package/src/view/editor-toolbar.ts +84 -0
  460. package/src/view/error-notice.ts +52 -0
  461. package/src/view/feed.ts +130 -0
  462. package/src/view/forum-cursor.ts +54 -0
  463. package/src/view/forum-display.ts +152 -0
  464. package/src/view/forum-jump.ts +60 -0
  465. package/src/view/forum-notice.ts +24 -0
  466. package/src/view/inline-moderation.ts +87 -0
  467. package/src/view/install-copy.ts +95 -0
  468. package/src/view/legal.ts +58 -0
  469. package/src/view/locale.ts +40 -0
  470. package/src/view/marketplace-panel.ts +51 -0
  471. package/src/view/member-identity.ts +23 -0
  472. package/src/view/member-profile.ts +65 -0
  473. package/src/view/messages.ts +222 -0
  474. package/src/view/metadata.ts +75 -0
  475. package/src/view/modcp-nav.ts +76 -0
  476. package/src/view/moderation-copy.ts +84 -0
  477. package/src/view/moderation-queue.ts +67 -0
  478. package/src/view/navigation-arrange.ts +188 -0
  479. package/src/view/navigation.ts +173 -0
  480. package/src/view/notification-menu.ts +202 -0
  481. package/src/view/notifications.ts +84 -0
  482. package/src/view/onboarding.ts +26 -0
  483. package/src/view/pager.ts +191 -0
  484. package/src/view/panel-nav.ts +203 -0
  485. package/src/view/plugin-panel.ts +42 -0
  486. package/src/view/post-form.ts +61 -0
  487. package/src/view/post-link.ts +29 -0
  488. package/src/view/presence.ts +99 -0
  489. package/src/view/promotion-rules.ts +52 -0
  490. package/src/view/redirect-notice.ts +21 -0
  491. package/src/view/reputation.ts +107 -0
  492. package/src/view/search-controls.ts +103 -0
  493. package/src/view/search-results.ts +291 -0
  494. package/src/view/security-activity.ts +73 -0
  495. package/src/view/setting-groups.ts +43 -0
  496. package/src/view/shell.ts +164 -0
  497. package/src/view/slug-id.ts +12 -0
  498. package/src/view/sso-hand-off.ts +44 -0
  499. package/src/view/sso-notices.ts +48 -0
  500. package/src/view/subscriptions.ts +76 -0
  501. package/src/view/theme-draft.ts +167 -0
  502. package/src/view/theme-preference.ts +30 -0
  503. package/src/view/theme-tokens.ts +333 -0
  504. package/src/view/thread-view.ts +280 -0
  505. package/src/view/time.ts +31 -0
  506. package/src/view/timezone.ts +35 -0
  507. package/src/view/two-factor.ts +6 -0
  508. package/src/view/usercp-nav.ts +103 -0
  509. package/src/view/usercp.ts +196 -0
  510. package/src/view/warnings.ts +128 -0
  511. package/src/view/word-filter.ts +5 -0
@@ -0,0 +1,101 @@
1
+ import 'server-only'
2
+
3
+ import { cache } from 'react'
4
+
5
+ import { CacheTags } from '@meith/core'
6
+ import { getDb, PostgresSettingsRepository } from '@meith/db'
7
+ import { drivers } from '@meith/drivers'
8
+ import { isLogoKey, type LogoScheme, logoPath, saveSettings } from '@meith/settings'
9
+
10
+ import { forgetImage, storeImage } from './image-upload'
11
+ import { getSettings } from './settings'
12
+ import { currentColourScheme } from './theme'
13
+
14
+ export const LOGO_FIELD = 'logo'
15
+
16
+ export { isLogoScheme, type LogoScheme } from '@meith/settings'
17
+
18
+ const SETTING_FOR: Record<LogoScheme, 'board.logo_light' | 'board.logo_dark'> = {
19
+ light: 'board.logo_light',
20
+ dark: 'board.logo_dark',
21
+ }
22
+
23
+ async function writeSetting(key: string, value: string): Promise<void> {
24
+ const result = await saveSettings(
25
+ new PostgresSettingsRepository(getDb()),
26
+ { [key]: value },
27
+ await getSettings(),
28
+ )
29
+
30
+ if (result.changed.length > 0) {
31
+ await drivers().cache.invalidateTags([CacheTags.settings(), ...result.invalidates])
32
+ }
33
+ }
34
+
35
+ export async function saveLogo(scheme: LogoScheme, file: File): Promise<void> {
36
+ const key = await storeImage('board', `logo-${scheme}`, file)
37
+
38
+ const previous = (await getSettings()).get(SETTING_FOR[scheme])
39
+ await writeSetting(SETTING_FOR[scheme], key)
40
+
41
+ if (previous !== key) await forgetImage(previous)
42
+ }
43
+
44
+ export async function removeLogo(scheme: LogoScheme): Promise<void> {
45
+ const previous = (await getSettings()).get(SETTING_FOR[scheme])
46
+ await writeSetting(SETTING_FOR[scheme], '')
47
+
48
+ await forgetImage(previous)
49
+ }
50
+
51
+ export async function logoKey(scheme: LogoScheme): Promise<string | null> {
52
+ const key = (await getSettings()).get(SETTING_FOR[scheme])
53
+ return isLogoKey(key) ? key : null
54
+ }
55
+
56
+ export const logoSrc = logoPath
57
+
58
+ export interface BoardLogo {
59
+ readonly src: string
60
+ readonly darkSrc: string | null
61
+ readonly alt: string
62
+ }
63
+
64
+ export function resolveLogo(input: {
65
+ readonly lightKey: string | null
66
+ readonly darkKey: string | null
67
+ readonly scheme: 'light' | 'dark' | 'system'
68
+ readonly alt: string
69
+ readonly boardTitle: string
70
+ }): BoardLogo | null {
71
+ const alt = input.alt.trim() === '' ? input.boardTitle : input.alt.trim()
72
+
73
+ const light = input.lightKey ?? input.darkKey
74
+ const dark = input.darkKey ?? input.lightKey
75
+ if (light === null || dark === null) return null
76
+
77
+ const lightSrc = logoSrc(input.lightKey === null ? 'dark' : 'light', light)
78
+ const darkSrc = logoSrc(input.darkKey === null ? 'light' : 'dark', dark)
79
+
80
+ if (input.scheme === 'light') return { src: lightSrc, darkSrc: null, alt }
81
+ if (input.scheme === 'dark') return { src: darkSrc, darkSrc: null, alt }
82
+
83
+ return { src: lightSrc, darkSrc: darkSrc === lightSrc ? null : darkSrc, alt }
84
+ }
85
+
86
+ export const currentLogo = cache(async (boardTitle: string): Promise<BoardLogo | null> => {
87
+ const [settings, scheme, lightKey, darkKey] = await Promise.all([
88
+ getSettings(),
89
+ currentColourScheme(),
90
+ logoKey('light'),
91
+ logoKey('dark'),
92
+ ])
93
+
94
+ return resolveLogo({
95
+ lightKey,
96
+ darkKey,
97
+ scheme,
98
+ alt: settings.get('board.logo_alt'),
99
+ boardTitle,
100
+ })
101
+ })
@@ -0,0 +1,13 @@
1
+ import { CacheTags } from '@meith/core'
2
+
3
+ export type CacheTagFactory = () => string
4
+
5
+ export const CLEARABLE_CACHES: Readonly<Record<string, CacheTagFactory>> = {
6
+ forums: CacheTags.forumTree,
7
+ }
8
+
9
+ export function clearableTag(what: unknown): string | null {
10
+ if (typeof what !== 'string') return null
11
+ const factory = CLEARABLE_CACHES[what]
12
+ return factory === undefined ? null : factory()
13
+ }
@@ -0,0 +1,33 @@
1
+ import 'server-only'
2
+
3
+ import { codeHighlighter } from '@meith/drivers/highlighting'
4
+ import { escapeAttribute, unescapeHtml } from '@meith/markdown'
5
+
6
+ const CODE_BLOCK =
7
+ /<pre class="md-code"><code(?: class="md-code-lang-([a-z0-9+#._-]+)")?>([\s\S]*?)\n<\/code><\/pre>/g
8
+
9
+ export async function highlightCodeBlocks(html: string): Promise<string> {
10
+ if (!html.includes('md-code')) return html
11
+
12
+ const matches = [...html.matchAll(CODE_BLOCK)]
13
+ if (matches.length === 0) return html
14
+
15
+ const replacements = new Map<string, string>()
16
+
17
+ for (const match of matches) {
18
+ const whole = match[0]
19
+ if (replacements.has(whole)) continue
20
+
21
+ const code = unescapeHtml(match[2] ?? '')
22
+ const highlighted = await codeHighlighter.highlight(code, match[1])
23
+ if (highlighted === null) continue
24
+
25
+ replacements.set(
26
+ whole,
27
+ `<pre class="md-code" data-lang="${escapeAttribute(match[1]!)}"><code class="md-code-lang-${escapeAttribute(match[1]!)}">${highlighted}</code></pre>`,
28
+ )
29
+ }
30
+
31
+ if (replacements.size === 0) return html
32
+ return html.replace(CODE_BLOCK, (whole) => replacements.get(whole) ?? whole)
33
+ }
@@ -0,0 +1,402 @@
1
+ import 'server-only'
2
+
3
+ import forumConfig from '@board/config'
4
+
5
+ import {
6
+ type AccountStore,
7
+ type BanLookup,
8
+ BanService,
9
+ createMemoryStore,
10
+ enrolmentLookup,
11
+ IdentityService,
12
+ type MemberProfileRepository,
13
+ type MemberSettingsRepository,
14
+ SessionService,
15
+ } from '@meith/accounts'
16
+ import type { AdminLogRepository, AdminSessionRepository } from '@meith/admin'
17
+ import type { AttachmentRepository } from '@meith/attachments'
18
+ import {
19
+ type ActorSource,
20
+ type AuthorizationSource,
21
+ Authorizer,
22
+ type BypassEvent,
23
+ InMemoryAuthorizationSource,
24
+ } from '@meith/authorization'
25
+ import type { AvatarRepository } from '@meith/avatars'
26
+ import { env, logger } from '@meith/core'
27
+ import {
28
+ ActorBuilder,
29
+ createPostgresAccountStore,
30
+ getDb,
31
+ PostgresAdminLogRepository,
32
+ PostgresAdminSessionRepository,
33
+ PostgresAttachmentRepository,
34
+ PostgresAuthorizationSource,
35
+ PostgresAvatarRepository,
36
+ PostgresBanRepository,
37
+ PostgresDraftRepository,
38
+ PostgresForumRepository,
39
+ PostgresInlineModerationRepository,
40
+ PostgresMemberProfileRepository,
41
+ PostgresMemberSettingsRepository,
42
+ PostgresMessageRepository,
43
+ PostgresModCpRepository,
44
+ PostgresModerationQueueRepository,
45
+ PostgresNotificationRepository,
46
+ PostgresPollRepository,
47
+ PostgresPostRepository,
48
+ PostgresPostWriteRepository,
49
+ PostgresProfileFieldRepository,
50
+ PostgresReadStateRepository,
51
+ PostgresRelationRepository,
52
+ PostgresReportRepository,
53
+ PostgresReputationRepository,
54
+ PostgresSignatureRepository,
55
+ PostgresSubscriptionRepository,
56
+ PostgresThreadRepository,
57
+ PostgresThreadSurgeryRepository,
58
+ PostgresThreadToolsRepository,
59
+ PostgresThreadViewBuffer,
60
+ PostgresThreadWriteRepository,
61
+ PostgresWarningRepository,
62
+ } from '@meith/db'
63
+ import { demoResetTask } from '@meith/demo'
64
+ import type { DraftRepository } from '@meith/drafts'
65
+ import { drivers } from '@meith/drivers'
66
+ import { imageProcessor } from '@meith/drivers/images'
67
+ import { CachedForumRepository, type ForumRepository } from '@meith/forums'
68
+ import type { MessageRepository } from '@meith/messages'
69
+ import type {
70
+ InlineModerationRepository,
71
+ ModCpRepository,
72
+ ModerationQueueRepository,
73
+ ReportRepository,
74
+ ThreadSurgeryRepository,
75
+ ThreadToolsRepository,
76
+ WarningBanPort,
77
+ WarningRepository,
78
+ } from '@meith/moderation'
79
+ import type { NotificationRepository } from '@meith/notifications'
80
+ import type { PollRepository, ThreadRatingRepository } from '@meith/polls'
81
+ import type { PostRepository, PostWriteRepository } from '@meith/posts'
82
+ import type { ProfileFieldRepository } from '@meith/profile-fields'
83
+ import type { RelationRepository } from '@meith/relations'
84
+ import type { ReputationRepository } from '@meith/reputation'
85
+ import { buildSchedulerBundle } from '@meith/runtime'
86
+ import type { SubscriptionRepository } from '@meith/subscriptions'
87
+ import type { TaskDefinition, TaskRepository } from '@meith/tasks'
88
+ import type {
89
+ ReadStateRepository,
90
+ ReplyWriteRepository,
91
+ ThreadRepository,
92
+ ThreadWriteRepository,
93
+ } from '@meith/threads'
94
+
95
+ import { ABSENT_SERVICES } from './absent-services'
96
+ import {
97
+ AUTH_CONFIG,
98
+ boardAuthConfig,
99
+ boardSessionConfig,
100
+ REMEMBER_DAYS,
101
+ SESSION_LIFETIME_DAYS,
102
+ } from './auth-config'
103
+ import { clearUploadedFiles } from './demo-uploads'
104
+ import { FixtureActorSource } from './fixture-actor-source'
105
+ import { FixtureForumRepository } from './fixture-forum-repo'
106
+ import { FixtureMemberProfileRepository } from './fixture-member-profile-repo'
107
+ import { FixturePostRepository } from './fixture-post-repo'
108
+ import { FixtureThreadRepository } from './fixture-thread-repo'
109
+ import { translatorForLocale } from './i18n-catalogs'
110
+ import { boardRendering } from './markdown-pipeline'
111
+ import { activeDefinitions } from './plugin-host'
112
+ import { FIXTURE_DATA_VERSION, SEED_BOARD, SEED_GROUP } from './seed-board'
113
+
114
+ export interface Container {
115
+ readonly authorizer: Authorizer
116
+ readonly authorizationSource: AuthorizationSource
117
+ readonly actorSource: ActorSource
118
+ readonly identity: IdentityService
119
+ readonly sessions: SessionService
120
+ readonly banLookup: BanLookup | null
121
+ readonly forums: ForumRepository
122
+ readonly threads: ThreadRepository
123
+ readonly threadWrites: (ThreadWriteRepository & ReplyWriteRepository) | null
124
+ readonly postWrites: PostWriteRepository | null
125
+ readonly moderationQueue: ModerationQueueRepository | null
126
+ readonly reports: ReportRepository | null
127
+ readonly threadTools: ThreadToolsRepository | null
128
+ readonly threadSurgery: ThreadSurgeryRepository | null
129
+ readonly inlineModeration: InlineModerationRepository | null
130
+ readonly warnings: WarningRepository | null
131
+ readonly warningBans: WarningBanPort | null
132
+ readonly modcp: ModCpRepository | null
133
+ readonly notifications: NotificationRepository | null
134
+ readonly subscriptions: SubscriptionRepository | null
135
+ readonly memberSettings: MemberSettingsRepository | null
136
+ readonly profileFields: ProfileFieldRepository | null
137
+ readonly messages: MessageRepository | null
138
+ readonly relations: RelationRepository | null
139
+ readonly reputation: ReputationRepository | null
140
+ readonly polls: (PollRepository & ThreadRatingRepository) | null
141
+ readonly drafts: DraftRepository | null
142
+ readonly signatures: PostgresSignatureRepository | null
143
+ readonly adminSessions: AdminSessionRepository | null
144
+ readonly adminLog: AdminLogRepository | null
145
+ readonly attachments: AttachmentRepository | null
146
+ readonly avatars: AvatarRepository | null
147
+ readonly accountStore: AccountStore
148
+ readonly posts: PostRepository
149
+ readonly readState: ReadStateRepository | null
150
+ readonly memberProfiles: MemberProfileRepository
151
+ readonly threadViews: ThreadViewRecorder | null
152
+ readonly fixtureDataVersion: number | null
153
+ readonly scheduler: SchedulerBundle | null
154
+ readonly dataSource: 'fixture' | 'postgres'
155
+ }
156
+
157
+ export interface ThreadViewRecorder {
158
+ record(threadId: number): Promise<void>
159
+ }
160
+
161
+ export interface SchedulerBundle {
162
+ readonly repository: TaskRepository
163
+ readonly tasks: readonly TaskDefinition[]
164
+ readonly onTaskFailure: (taskId: string, error: unknown) => void
165
+ }
166
+
167
+ const GLOBAL_KEY = Symbol.for('@meith/forum.container')
168
+
169
+ type GlobalWithContainer = typeof globalThis & {
170
+ [GLOBAL_KEY]?: Container
171
+ }
172
+
173
+ function build(): Container {
174
+ const log = logger({ module: 'authorization' })
175
+
176
+ const onBypass = (event: BypassEvent): void => {
177
+ log.info(
178
+ {
179
+ kind: event.kind,
180
+ userId: event.userId,
181
+ action: event.action,
182
+ forumId: event.forumId ?? null,
183
+ },
184
+ 'authorization bypass',
185
+ )
186
+ }
187
+
188
+ if (env.DATA_SOURCE === 'postgres') {
189
+ return buildPostgres(onBypass)
190
+ }
191
+ return buildFixture(onBypass)
192
+ }
193
+
194
+ function buildFixture(onBypass: (e: BypassEvent) => void): Container {
195
+ const authorizationSource = new InMemoryAuthorizationSource(SEED_BOARD)
196
+ const store: AccountStore = createMemoryStore()
197
+ return {
198
+ authorizationSource,
199
+ authorizer: new Authorizer(authorizationSource, { onBypass }),
200
+ actorSource: new FixtureActorSource(store),
201
+ forums: cached(new FixtureForumRepository()),
202
+ threads: new FixtureThreadRepository(),
203
+ ...ABSENT_SERVICES,
204
+ posts: new FixturePostRepository(),
205
+ memberProfiles: new FixtureMemberProfileRepository(),
206
+ fixtureDataVersion: FIXTURE_DATA_VERSION,
207
+ ...identityServices(store),
208
+ accountStore: store,
209
+ dataSource: 'fixture',
210
+ }
211
+ }
212
+
213
+ function cached(inner: ForumRepository): ForumRepository {
214
+ return new CachedForumRepository(inner, drivers().cache)
215
+ }
216
+
217
+ function identityServices(
218
+ store: AccountStore,
219
+ bans?: BanLookup,
220
+ ): {
221
+ identity: IdentityService
222
+ sessions: SessionService
223
+ banLookup: BanLookup | null
224
+ } {
225
+ return {
226
+ identity: new IdentityService({
227
+ store,
228
+ config: AUTH_CONFIG,
229
+ secondFactor: enrolmentLookup(store.twoFactor),
230
+ ...(bans === undefined ? {} : { bans }),
231
+ }),
232
+ sessions: new SessionService({
233
+ store,
234
+ rememberDays: REMEMBER_DAYS,
235
+ sessionLifetimeDays: SESSION_LIFETIME_DAYS,
236
+ }),
237
+ banLookup: bans ?? null,
238
+ }
239
+ }
240
+
241
+ function buildPostgres(onBypass: (e: BypassEvent) => void): Container {
242
+ const db = getDb()
243
+ const authorizationSource = new PostgresAuthorizationSource(db)
244
+ const store: AccountStore = createPostgresAccountStore(db)
245
+ const threadViews = new PostgresThreadViewBuffer(db)
246
+ const warningRepo = new PostgresWarningRepository(db)
247
+ return {
248
+ authorizationSource,
249
+ authorizer: new Authorizer(authorizationSource, { onBypass }),
250
+ actorSource: new ActorBuilder(db, { guestGroupId: 1 }),
251
+ forums: cached(new PostgresForumRepository(db)),
252
+ threads: new PostgresThreadRepository(db),
253
+ threadWrites: new PostgresThreadWriteRepository(db, boardRendering),
254
+ postWrites: new PostgresPostWriteRepository(db, boardRendering),
255
+ moderationQueue: new PostgresModerationQueueRepository(db),
256
+ reports: new PostgresReportRepository(db),
257
+ threadTools: new PostgresThreadToolsRepository(db),
258
+ threadSurgery: new PostgresThreadSurgeryRepository(db),
259
+ inlineModeration: new PostgresInlineModerationRepository(db),
260
+ warnings: warningRepo,
261
+ modcp: new PostgresModCpRepository(db),
262
+ notifications: new PostgresNotificationRepository(db),
263
+ subscriptions: new PostgresSubscriptionRepository(db),
264
+ memberSettings: new PostgresMemberSettingsRepository(db),
265
+ profileFields: new PostgresProfileFieldRepository(db),
266
+ messages: new PostgresMessageRepository(db),
267
+ relations: new PostgresRelationRepository(db),
268
+ reputation: new PostgresReputationRepository(db),
269
+ polls: new PostgresPollRepository(db),
270
+ drafts: new PostgresDraftRepository(db),
271
+ signatures: new PostgresSignatureRepository(db),
272
+ adminSessions: new PostgresAdminSessionRepository(db),
273
+ adminLog: new PostgresAdminLogRepository(db),
274
+ attachments: new PostgresAttachmentRepository(db),
275
+ avatars: new PostgresAvatarRepository(db),
276
+ warningBans: {
277
+ async ban(input) {
278
+ await new BanService({
279
+ bans: new PostgresBanRepository(db),
280
+ bannedGroupId: SEED_GROUP.banned,
281
+ }).ban(input)
282
+ },
283
+ },
284
+ posts: new PostgresPostRepository(db),
285
+ readState: new PostgresReadStateRepository(db),
286
+ memberProfiles: new PostgresMemberProfileRepository(db),
287
+ threadViews,
288
+ fixtureDataVersion: null,
289
+ accountStore: store,
290
+ ...identityServices(store, new PostgresBanRepository(db)),
291
+ scheduler: withDemoReset(
292
+ buildSchedulerBundle({
293
+ queue: drivers().queue,
294
+ db,
295
+ mail: drivers().mail,
296
+ themeKey: forumConfig.defaultTheme,
297
+ themeTokens: Object.fromEntries(
298
+ Object.values(forumConfig.themes).map((theme) => [theme.key, theme.tokens]),
299
+ ),
300
+ files: drivers().files,
301
+ images: imageProcessor,
302
+ plugins: activeDefinitions(),
303
+ translatorForLocale,
304
+ }),
305
+ db,
306
+ ),
307
+ dataSource: 'postgres',
308
+ }
309
+ }
310
+
311
+ /**
312
+ * The reset is registered here rather than in `buildSchedulerBundle` because
313
+ * this is the only composition root with a cache to clear afterwards. The
314
+ * worker's `NextCacheDriver` is a different process holding a different map, so
315
+ * a reset it ran would leave this process serving the forum tree of a board
316
+ * that no longer exists — for up to the tree's 60-second TTL, on the one page
317
+ * every visitor lands on.
318
+ */
319
+ function withDemoReset(bundle: SchedulerBundle, db: ReturnType<typeof getDb>): SchedulerBundle {
320
+ if (!env.DEMO_MODE) return bundle
321
+
322
+ return {
323
+ ...bundle,
324
+ tasks: [
325
+ ...bundle.tasks,
326
+ demoResetTask({
327
+ db,
328
+ cache: drivers().cache,
329
+ clearUploads: () => clearUploadedFiles(),
330
+ plugins: activeDefinitions(),
331
+ }),
332
+ ],
333
+ }
334
+ }
335
+
336
+ export function getContainer(): Container {
337
+ const g = globalThis as GlobalWithContainer
338
+ const cached = g[GLOBAL_KEY] as Partial<Container> | undefined
339
+ if (
340
+ !cached ||
341
+ typeof cached.threads?.locate !== 'function' ||
342
+ typeof cached.posts?.listThread !== 'function' ||
343
+ typeof cached.posts?.findVisibleById !== 'function' ||
344
+ cached.readState === undefined ||
345
+ cached.threadViews === undefined ||
346
+ cached.threadWrites === undefined ||
347
+ cached.postWrites === undefined ||
348
+ cached.moderationQueue === undefined ||
349
+ cached.reports === undefined ||
350
+ cached.threadTools === undefined ||
351
+ cached.threadSurgery === undefined ||
352
+ cached.inlineModeration === undefined ||
353
+ cached.warnings === undefined ||
354
+ cached.warningBans === undefined ||
355
+ cached.modcp === undefined ||
356
+ cached.notifications === undefined ||
357
+ cached.subscriptions === undefined ||
358
+ cached.memberSettings === undefined ||
359
+ cached.profileFields === undefined ||
360
+ cached.messages === undefined ||
361
+ cached.relations === undefined ||
362
+ cached.reputation === undefined ||
363
+ cached.polls === undefined ||
364
+ cached.drafts === undefined ||
365
+ cached.signatures === undefined ||
366
+ cached.adminSessions === undefined ||
367
+ cached.adminLog === undefined ||
368
+ cached.attachments === undefined ||
369
+ cached.avatars === undefined ||
370
+ typeof cached.memberProfiles?.findPublicById !== 'function' ||
371
+ typeof cached.accountStore?.identities?.listForUser !== 'function' ||
372
+ typeof cached.accountStore?.passkeys?.listForUser !== 'function' ||
373
+ (cached.dataSource === 'fixture' && cached.fixtureDataVersion !== FIXTURE_DATA_VERSION) ||
374
+ (cached.dataSource === 'postgres' && typeof cached.readState?.forUser !== 'function')
375
+ ) {
376
+ g[GLOBAL_KEY] = build()
377
+ }
378
+ return g[GLOBAL_KEY] as Container
379
+ }
380
+
381
+ export function getAuthorizer(): Authorizer {
382
+ return getContainer().authorizer
383
+ }
384
+
385
+ export async function configuredIdentity(): Promise<IdentityService> {
386
+ const { accountStore, banLookup } = getContainer()
387
+ return new IdentityService({
388
+ store: accountStore,
389
+ config: await boardAuthConfig(),
390
+ secondFactor: enrolmentLookup(accountStore.twoFactor),
391
+ ...(banLookup === null ? {} : { bans: banLookup }),
392
+ })
393
+ }
394
+
395
+ export async function configuredSessions(): Promise<SessionService> {
396
+ const identity = await configuredIdentity()
397
+ return new SessionService({
398
+ store: getContainer().accountStore,
399
+ ...(await boardSessionConfig()),
400
+ assertSignInAllowed: (account) => identity.assertSignInAllowed(account),
401
+ })
402
+ }