create-nextdevtpl 2.6.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 (425) hide show
  1. package/README.md +29 -0
  2. package/catalog.json +385 -0
  3. package/dist/catalog.d.ts +3 -0
  4. package/dist/catalog.js +112 -0
  5. package/dist/catalog.js.map +1 -0
  6. package/dist/cli.d.ts +3 -0
  7. package/dist/cli.js +115 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/filesystem.d.ts +10 -0
  10. package/dist/filesystem.js +86 -0
  11. package/dist/filesystem.js.map +1 -0
  12. package/dist/generate.d.ts +5 -0
  13. package/dist/generate.js +758 -0
  14. package/dist/generate.js.map +1 -0
  15. package/dist/index.d.ts +4 -0
  16. package/dist/index.js +4 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/selection.d.ts +2 -0
  19. package/dist/selection.js +111 -0
  20. package/dist/selection.js.map +1 -0
  21. package/dist/service-templates.d.ts +4 -0
  22. package/dist/service-templates.js +180 -0
  23. package/dist/service-templates.js.map +1 -0
  24. package/dist/types.d.ts +65 -0
  25. package/dist/types.js +2 -0
  26. package/dist/types.js.map +1 -0
  27. package/package.json +54 -0
  28. package/template/.dockerignore +14 -0
  29. package/template/.env.example +282 -0
  30. package/template/.gitattributes +17 -0
  31. package/template/.node-version +1 -0
  32. package/template/Dockerfile +45 -0
  33. package/template/LICENSE +21 -0
  34. package/template/README.md +267 -0
  35. package/template/biome.json +68 -0
  36. package/template/cloudflare/templates/logger.ts +162 -0
  37. package/template/cloudflare/templates/mail/server.ts +6 -0
  38. package/template/cloudflare/templates/mail/templates.ts +87 -0
  39. package/template/cloudflare/templates/mail/utils.ts +69 -0
  40. package/template/cloudflare/worker.mjs +25 -0
  41. package/template/components.json +22 -0
  42. package/template/compose.yaml +74 -0
  43. package/template/deploy/check-health.mjs +25 -0
  44. package/template/deploy/docker/README.md +14 -0
  45. package/template/deploy/server/README.md +24 -0
  46. package/template/deploy/server/build.sh +24 -0
  47. package/template/deploy/server/nextdevtpl.service +19 -0
  48. package/template/deploy/server/start.sh +19 -0
  49. package/template/deploy/vercel/README.md +16 -0
  50. package/template/deploy/verify-production-health.mjs +86 -0
  51. package/template/drizzle.config.ts +47 -0
  52. package/template/gitignore +62 -0
  53. package/template/manifest.json +35 -0
  54. package/template/messages/en.json +766 -0
  55. package/template/messages/zh.json +762 -0
  56. package/template/next.config.mjs +28 -0
  57. package/template/open-next.config.ts +3 -0
  58. package/template/package.json +125 -0
  59. package/template/pnpm-lock.yaml +15901 -0
  60. package/template/postcss.config.mjs +7 -0
  61. package/template/public/file.svg +1 -0
  62. package/template/public/globe.svg +1 -0
  63. package/template/public/logo.svg +6 -0
  64. package/template/public/next.svg +1 -0
  65. package/template/public/vercel.svg +1 -0
  66. package/template/public/window.svg +1 -0
  67. package/template/sentry.client.config.ts +10 -0
  68. package/template/sentry.edge.config.ts +10 -0
  69. package/template/sentry.server.config.ts +10 -0
  70. package/template/source.config.ts +59 -0
  71. package/template/src/adapters/ai/anthropic.ts +113 -0
  72. package/template/src/adapters/ai/index.ts +6 -0
  73. package/template/src/adapters/ai/openai-compatible.ts +88 -0
  74. package/template/src/adapters/ai/workers-ai.ts +63 -0
  75. package/template/src/adapters/index.ts +10 -0
  76. package/template/src/adapters/jobs/cloudflare-workflows.ts +46 -0
  77. package/template/src/adapters/jobs/index.ts +5 -0
  78. package/template/src/adapters/jobs/inngest/adapter.ts +29 -0
  79. package/template/src/adapters/jobs/inngest/client.ts +8 -0
  80. package/template/src/adapters/jobs/inngest/functions.ts +17 -0
  81. package/template/src/adapters/jobs/inngest/handler.ts +6 -0
  82. package/template/src/adapters/jobs/inngest/index.ts +4 -0
  83. package/template/src/adapters/mail/cloudflare-email.ts +50 -0
  84. package/template/src/adapters/mail/disabled.ts +17 -0
  85. package/template/src/adapters/mail/index.ts +7 -0
  86. package/template/src/adapters/mail/resend.ts +76 -0
  87. package/template/src/adapters/mail/smtp.ts +82 -0
  88. package/template/src/adapters/payment/creem.ts +269 -0
  89. package/template/src/adapters/payment/index.ts +2 -0
  90. package/template/src/adapters/payment/stripe.ts +302 -0
  91. package/template/src/adapters/rate-limit/cloudflare.ts +42 -0
  92. package/template/src/adapters/rate-limit/index.ts +6 -0
  93. package/template/src/adapters/rate-limit/noop.ts +27 -0
  94. package/template/src/adapters/rate-limit/upstash.ts +113 -0
  95. package/template/src/adapters/registry.ts +228 -0
  96. package/template/src/adapters/storage/index.ts +3 -0
  97. package/template/src/adapters/storage/r2-binding.ts +105 -0
  98. package/template/src/adapters/storage/s3-compatible.ts +149 -0
  99. package/template/src/app/[locale]/(admin)/admin/layout.tsx +36 -0
  100. package/template/src/app/[locale]/(admin)/admin/page.tsx +345 -0
  101. package/template/src/app/[locale]/(admin)/admin/tickets/[id]/page.tsx +261 -0
  102. package/template/src/app/[locale]/(admin)/admin/tickets/page.tsx +236 -0
  103. package/template/src/app/[locale]/(admin)/admin/users/page.tsx +557 -0
  104. package/template/src/app/[locale]/(auth)/forgot-password/page.tsx +9 -0
  105. package/template/src/app/[locale]/(auth)/layout.tsx +28 -0
  106. package/template/src/app/[locale]/(auth)/sign-in/page.tsx +9 -0
  107. package/template/src/app/[locale]/(auth)/sign-up/page.tsx +9 -0
  108. package/template/src/app/[locale]/(dashboard)/dashboard/credits/buy/buy-credits-view.tsx +170 -0
  109. package/template/src/app/[locale]/(dashboard)/dashboard/credits/buy/page.tsx +12 -0
  110. package/template/src/app/[locale]/(dashboard)/dashboard/credits/page.tsx +5 -0
  111. package/template/src/app/[locale]/(dashboard)/dashboard/loading.tsx +41 -0
  112. package/template/src/app/[locale]/(dashboard)/dashboard/page.tsx +299 -0
  113. package/template/src/app/[locale]/(dashboard)/dashboard/settings/page.tsx +38 -0
  114. package/template/src/app/[locale]/(dashboard)/dashboard/support/[id]/page.tsx +220 -0
  115. package/template/src/app/[locale]/(dashboard)/dashboard/support/new/page.tsx +183 -0
  116. package/template/src/app/[locale]/(dashboard)/dashboard/support/page.tsx +145 -0
  117. package/template/src/app/[locale]/(dashboard)/layout.tsx +35 -0
  118. package/template/src/app/[locale]/(marketing)/blog/[slug]/page.tsx +178 -0
  119. package/template/src/app/[locale]/(marketing)/blog/blog-post-card.tsx +95 -0
  120. package/template/src/app/[locale]/(marketing)/blog/page.tsx +127 -0
  121. package/template/src/app/[locale]/(marketing)/demo/plan-badges/page.tsx +36 -0
  122. package/template/src/app/[locale]/(marketing)/layout.tsx +15 -0
  123. package/template/src/app/[locale]/(marketing)/legal/[slug]/page.tsx +130 -0
  124. package/template/src/app/[locale]/(marketing)/page.tsx +94 -0
  125. package/template/src/app/[locale]/(marketing)/pseo/[slug]/page.tsx +68 -0
  126. package/template/src/app/[locale]/(marketing)/pseo/page.tsx +137 -0
  127. package/template/src/app/[locale]/docs/[[...slug]]/page.tsx +63 -0
  128. package/template/src/app/[locale]/docs/layout.tsx +39 -0
  129. package/template/src/app/[locale]/layout.tsx +80 -0
  130. package/template/src/app/[locale]/opengraph-image.tsx +20 -0
  131. package/template/src/app/[locale]/twitter-image.tsx +20 -0
  132. package/template/src/app/api/auth/[...all]/route.ts +20 -0
  133. package/template/src/app/api/health/route.ts +45 -0
  134. package/template/src/app/api/inngest/route.ts +12 -0
  135. package/template/src/app/api/jobs/credits/expire/route.ts +102 -0
  136. package/template/src/app/api/search/route.ts +17 -0
  137. package/template/src/app/api/upload/presigned/route.ts +98 -0
  138. package/template/src/app/api/webhooks/payment/route.ts +363 -0
  139. package/template/src/app/globals.css +406 -0
  140. package/template/src/app/icon.svg +6 -0
  141. package/template/src/app/image-proxy/[...path]/route.ts +137 -0
  142. package/template/src/app/layout.tsx +73 -0
  143. package/template/src/app/robots.ts +29 -0
  144. package/template/src/app/sitemap.ts +68 -0
  145. package/template/src/components/motion/reveal.tsx +49 -0
  146. package/template/src/components/seo/json-ld.tsx +87 -0
  147. package/template/src/components/seo/og-image-template.tsx +118 -0
  148. package/template/src/components/ui/accordion.tsx +66 -0
  149. package/template/src/components/ui/alert-dialog.tsx +195 -0
  150. package/template/src/components/ui/avatar.tsx +53 -0
  151. package/template/src/components/ui/badge.tsx +46 -0
  152. package/template/src/components/ui/button.tsx +65 -0
  153. package/template/src/components/ui/card.tsx +92 -0
  154. package/template/src/components/ui/checkbox.tsx +32 -0
  155. package/template/src/components/ui/collapsible.tsx +33 -0
  156. package/template/src/components/ui/dialog.tsx +124 -0
  157. package/template/src/components/ui/dropdown-menu.tsx +257 -0
  158. package/template/src/components/ui/form.tsx +233 -0
  159. package/template/src/components/ui/index.ts +2 -0
  160. package/template/src/components/ui/input.tsx +21 -0
  161. package/template/src/components/ui/label.tsx +24 -0
  162. package/template/src/components/ui/navigation-menu.tsx +167 -0
  163. package/template/src/components/ui/pagination.tsx +125 -0
  164. package/template/src/components/ui/popover.tsx +48 -0
  165. package/template/src/components/ui/progress.tsx +27 -0
  166. package/template/src/components/ui/radio-group.tsx +45 -0
  167. package/template/src/components/ui/select.tsx +190 -0
  168. package/template/src/components/ui/separator.tsx +28 -0
  169. package/template/src/components/ui/sheet.tsx +140 -0
  170. package/template/src/components/ui/switch.tsx +31 -0
  171. package/template/src/components/ui/tabs.tsx +75 -0
  172. package/template/src/components/ui/textarea.tsx +18 -0
  173. package/template/src/config/index.ts +31 -0
  174. package/template/src/config/nav.ts +238 -0
  175. package/template/src/config/payment.ts +282 -0
  176. package/template/src/config/site.ts +53 -0
  177. package/template/src/config/subscription-plan.ts +164 -0
  178. package/template/src/content/blog/en/hello-world.mdx +48 -0
  179. package/template/src/content/blog/zh/hello-world.mdx +48 -0
  180. package/template/src/content/blog/zh/nextdevtpl-linuxdo-intro.mdx +98 -0
  181. package/template/src/content/docs/en/admin-and-support.mdx +65 -0
  182. package/template/src/content/docs/en/authentication.mdx +71 -0
  183. package/template/src/content/docs/en/background-jobs.mdx +53 -0
  184. package/template/src/content/docs/en/configuration.mdx +71 -0
  185. package/template/src/content/docs/en/credits.mdx +69 -0
  186. package/template/src/content/docs/en/customization.mdx +95 -0
  187. package/template/src/content/docs/en/database.mdx +66 -0
  188. package/template/src/content/docs/en/deployment.mdx +62 -0
  189. package/template/src/content/docs/en/email.mdx +48 -0
  190. package/template/src/content/docs/en/index.mdx +48 -0
  191. package/template/src/content/docs/en/internationalization.mdx +78 -0
  192. package/template/src/content/docs/en/meta.json +22 -0
  193. package/template/src/content/docs/en/observability.mdx +69 -0
  194. package/template/src/content/docs/en/payments.mdx +64 -0
  195. package/template/src/content/docs/en/project-structure.mdx +82 -0
  196. package/template/src/content/docs/en/quick-start.mdx +113 -0
  197. package/template/src/content/docs/en/storage.mdx +51 -0
  198. package/template/src/content/docs/zh/admin-and-support.mdx +65 -0
  199. package/template/src/content/docs/zh/authentication.mdx +71 -0
  200. package/template/src/content/docs/zh/background-jobs.mdx +53 -0
  201. package/template/src/content/docs/zh/configuration.mdx +70 -0
  202. package/template/src/content/docs/zh/credits.mdx +69 -0
  203. package/template/src/content/docs/zh/customization.mdx +95 -0
  204. package/template/src/content/docs/zh/database.mdx +66 -0
  205. package/template/src/content/docs/zh/deployment.mdx +61 -0
  206. package/template/src/content/docs/zh/email.mdx +47 -0
  207. package/template/src/content/docs/zh/index.mdx +48 -0
  208. package/template/src/content/docs/zh/internationalization.mdx +78 -0
  209. package/template/src/content/docs/zh/meta.json +22 -0
  210. package/template/src/content/docs/zh/observability.mdx +68 -0
  211. package/template/src/content/docs/zh/payments.mdx +64 -0
  212. package/template/src/content/docs/zh/project-structure.mdx +82 -0
  213. package/template/src/content/docs/zh/quick-start.mdx +110 -0
  214. package/template/src/content/docs/zh/storage.mdx +51 -0
  215. package/template/src/content/legal/en/cookie-policy.mdx +78 -0
  216. package/template/src/content/legal/en/privacy.mdx +131 -0
  217. package/template/src/content/legal/en/terms.mdx +93 -0
  218. package/template/src/content/legal/zh/cookie-policy.mdx +78 -0
  219. package/template/src/content/legal/zh/privacy.mdx +131 -0
  220. package/template/src/content/legal/zh/terms.mdx +92 -0
  221. package/template/src/core/modules/contract.ts +225 -0
  222. package/template/src/core/modules/index.ts +1 -0
  223. package/template/src/core/services/ai.ts +31 -0
  224. package/template/src/core/services/common.ts +112 -0
  225. package/template/src/core/services/index.ts +8 -0
  226. package/template/src/core/services/jobs.ts +24 -0
  227. package/template/src/core/services/mail.ts +35 -0
  228. package/template/src/core/services/manifest.ts +51 -0
  229. package/template/src/core/services/payment.ts +92 -0
  230. package/template/src/core/services/rate-limit.ts +47 -0
  231. package/template/src/core/services/storage.ts +42 -0
  232. package/template/src/db/index.ts +71 -0
  233. package/template/src/db/schema/auth.ts +67 -0
  234. package/template/src/db/schema/credits.ts +94 -0
  235. package/template/src/db/schema/index.ts +5 -0
  236. package/template/src/db/schema/mail.ts +14 -0
  237. package/template/src/db/schema/subscription.ts +21 -0
  238. package/template/src/db/schema/support.ts +56 -0
  239. package/template/src/db/schema.ts +2 -0
  240. package/template/src/features/admin/components/admin-sidebar.tsx +213 -0
  241. package/template/src/features/admin/components/index.ts +2 -0
  242. package/template/src/features/admin/index.ts +3 -0
  243. package/template/src/features/admin/manifest.ts +41 -0
  244. package/template/src/features/analytics/components/analytics.tsx +62 -0
  245. package/template/src/features/analytics/components/index.ts +2 -0
  246. package/template/src/features/analytics/index.ts +3 -0
  247. package/template/src/features/analytics/manifest.ts +13 -0
  248. package/template/src/features/auth/components/auth-brand-panel.tsx +82 -0
  249. package/template/src/features/auth/components/auth-error-alert.tsx +28 -0
  250. package/template/src/features/auth/components/auth-footer.tsx +52 -0
  251. package/template/src/features/auth/components/auth-logo.tsx +30 -0
  252. package/template/src/features/auth/components/forgot-password-form.tsx +146 -0
  253. package/template/src/features/auth/components/index.ts +9 -0
  254. package/template/src/features/auth/components/sign-in-form.tsx +241 -0
  255. package/template/src/features/auth/components/sign-up-form.tsx +307 -0
  256. package/template/src/features/auth/index.ts +3 -0
  257. package/template/src/features/auth/manifest.ts +30 -0
  258. package/template/src/features/blog/components/blog-post-item.tsx +72 -0
  259. package/template/src/features/blog/components/index.ts +1 -0
  260. package/template/src/features/blog/data/mock-posts.ts +62 -0
  261. package/template/src/features/blog/index.ts +3 -0
  262. package/template/src/features/blog/manifest.ts +22 -0
  263. package/template/src/features/credits/account.ts +76 -0
  264. package/template/src/features/credits/actions.ts +427 -0
  265. package/template/src/features/credits/components/credit-balance-badge.tsx +54 -0
  266. package/template/src/features/credits/components/credit-usage-section.tsx +160 -0
  267. package/template/src/features/credits/components/index.ts +7 -0
  268. package/template/src/features/credits/components/transaction-history.tsx +346 -0
  269. package/template/src/features/credits/config.ts +70 -0
  270. package/template/src/features/credits/consume.ts +140 -0
  271. package/template/src/features/credits/core.ts +23 -0
  272. package/template/src/features/credits/errors.ts +26 -0
  273. package/template/src/features/credits/expire.ts +112 -0
  274. package/template/src/features/credits/grant.ts +168 -0
  275. package/template/src/features/credits/index.ts +6 -0
  276. package/template/src/features/credits/manifest.ts +36 -0
  277. package/template/src/features/credits/query.ts +65 -0
  278. package/template/src/features/credits/types.ts +48 -0
  279. package/template/src/features/dashboard/components/index.ts +3 -0
  280. package/template/src/features/dashboard/components/main-wrapper.tsx +100 -0
  281. package/template/src/features/dashboard/components/sidebar.tsx +341 -0
  282. package/template/src/features/dashboard/context/index.ts +2 -0
  283. package/template/src/features/dashboard/context/sidebar-context.tsx +72 -0
  284. package/template/src/features/dashboard/index.ts +4 -0
  285. package/template/src/features/dashboard/manifest.ts +28 -0
  286. package/template/src/features/mail/actions.ts +175 -0
  287. package/template/src/features/mail/index.ts +5 -0
  288. package/template/src/features/mail/manifest.ts +13 -0
  289. package/template/src/features/mail/server.ts +8 -0
  290. package/template/src/features/mail/templates/index.ts +11 -0
  291. package/template/src/features/mail/templates/primary-action-email.tsx +224 -0
  292. package/template/src/features/mail/templates/welcome-email.tsx +121 -0
  293. package/template/src/features/mail/utils.ts +190 -0
  294. package/template/src/features/marketing/components/animated-price.tsx +51 -0
  295. package/template/src/features/marketing/components/cookie-consent.tsx +255 -0
  296. package/template/src/features/marketing/components/cta-section.tsx +50 -0
  297. package/template/src/features/marketing/components/faq-section.tsx +59 -0
  298. package/template/src/features/marketing/components/feature-grid.tsx +80 -0
  299. package/template/src/features/marketing/components/footer.tsx +128 -0
  300. package/template/src/features/marketing/components/header.tsx +255 -0
  301. package/template/src/features/marketing/components/hero-section.tsx +323 -0
  302. package/template/src/features/marketing/components/how-it-works.tsx +115 -0
  303. package/template/src/features/marketing/components/index.ts +14 -0
  304. package/template/src/features/marketing/components/nav-menu.tsx +236 -0
  305. package/template/src/features/marketing/components/pricing-section.tsx +446 -0
  306. package/template/src/features/marketing/components/testimonials.tsx +59 -0
  307. package/template/src/features/marketing/components/use-cases-section.tsx +78 -0
  308. package/template/src/features/marketing/index.ts +3 -0
  309. package/template/src/features/marketing/manifest.ts +35 -0
  310. package/template/src/features/payment/actions.ts +182 -0
  311. package/template/src/features/payment/index.ts +4 -0
  312. package/template/src/features/payment/manifest.ts +18 -0
  313. package/template/src/features/payment/server.ts +7 -0
  314. package/template/src/features/payment/types.ts +202 -0
  315. package/template/src/features/pseo/components/index.ts +6 -0
  316. package/template/src/features/pseo/components/pseo-cta.tsx +51 -0
  317. package/template/src/features/pseo/components/pseo-faq.tsx +40 -0
  318. package/template/src/features/pseo/components/pseo-feature-grid.tsx +74 -0
  319. package/template/src/features/pseo/components/pseo-hero.tsx +106 -0
  320. package/template/src/features/pseo/components/pseo-related.tsx +62 -0
  321. package/template/src/features/pseo/components/pseo-use-cases.tsx +50 -0
  322. package/template/src/features/pseo/data/pseo-pages.json +534 -0
  323. package/template/src/features/pseo/index.ts +4 -0
  324. package/template/src/features/pseo/lib/pseo-data.ts +140 -0
  325. package/template/src/features/pseo/manifest.ts +22 -0
  326. package/template/src/features/settings/actions/delete-account.ts +50 -0
  327. package/template/src/features/settings/actions/index.ts +3 -0
  328. package/template/src/features/settings/actions/update-profile.ts +49 -0
  329. package/template/src/features/settings/components/billing-section.tsx +283 -0
  330. package/template/src/features/settings/components/index.ts +3 -0
  331. package/template/src/features/settings/components/profile-form.tsx +154 -0
  332. package/template/src/features/settings/components/security-section.tsx +158 -0
  333. package/template/src/features/settings/components/settings-profile-view.tsx +506 -0
  334. package/template/src/features/settings/index.ts +5 -0
  335. package/template/src/features/settings/manifest.ts +28 -0
  336. package/template/src/features/settings/schemas/index.ts +2 -0
  337. package/template/src/features/settings/schemas/update-profile.ts +21 -0
  338. package/template/src/features/shared/components/cookie-settings-dialog.tsx +282 -0
  339. package/template/src/features/shared/components/index.ts +5 -0
  340. package/template/src/features/shared/components/language-switcher.tsx +82 -0
  341. package/template/src/features/shared/components/max-width-wrapper.tsx +49 -0
  342. package/template/src/features/shared/components/mode-toggle.tsx +119 -0
  343. package/template/src/features/shared/icons/google-icon.tsx +39 -0
  344. package/template/src/features/shared/icons/index.ts +7 -0
  345. package/template/src/features/shared/index.ts +5 -0
  346. package/template/src/features/shared/manifest.ts +13 -0
  347. package/template/src/features/shared/providers.tsx +59 -0
  348. package/template/src/features/storage/actions.ts +107 -0
  349. package/template/src/features/storage/index.ts +6 -0
  350. package/template/src/features/storage/manifest.ts +22 -0
  351. package/template/src/features/storage/types.ts +143 -0
  352. package/template/src/features/storage/utils.ts +85 -0
  353. package/template/src/features/storage/validation.ts +151 -0
  354. package/template/src/features/subscription/actions/get-user-plan.ts +26 -0
  355. package/template/src/features/subscription/actions/index.ts +5 -0
  356. package/template/src/features/subscription/components/current-plan-badge.tsx +18 -0
  357. package/template/src/features/subscription/components/index.ts +6 -0
  358. package/template/src/features/subscription/components/plan-badge.tsx +98 -0
  359. package/template/src/features/subscription/index.ts +25 -0
  360. package/template/src/features/subscription/manifest.ts +13 -0
  361. package/template/src/features/subscription/services/user-plan.ts +181 -0
  362. package/template/src/features/support/actions/admin-ticket.ts +172 -0
  363. package/template/src/features/support/actions/admin-users.ts +295 -0
  364. package/template/src/features/support/actions/index.ts +22 -0
  365. package/template/src/features/support/actions/ticket.ts +12 -0
  366. package/template/src/features/support/actions/user-ticket.ts +153 -0
  367. package/template/src/features/support/components/admin-ticket-reply-form.tsx +109 -0
  368. package/template/src/features/support/components/admin-ticket-status-select.tsx +119 -0
  369. package/template/src/features/support/components/index.ts +6 -0
  370. package/template/src/features/support/components/ticket-message-form.tsx +99 -0
  371. package/template/src/features/support/components/user-role-select.tsx +107 -0
  372. package/template/src/features/support/index.ts +5 -0
  373. package/template/src/features/support/manifest.ts +53 -0
  374. package/template/src/features/support/schemas/index.ts +15 -0
  375. package/template/src/features/support/schemas/ticket.ts +81 -0
  376. package/template/src/hooks/index.ts +2 -0
  377. package/template/src/i18n/index.ts +12 -0
  378. package/template/src/i18n/request.ts +24 -0
  379. package/template/src/i18n/routing.ts +23 -0
  380. package/template/src/instrumentation.ts +19 -0
  381. package/template/src/lib/ai/index.ts +16 -0
  382. package/template/src/lib/api-logger.ts +24 -0
  383. package/template/src/lib/auth/admin.ts +54 -0
  384. package/template/src/lib/auth/api.ts +17 -0
  385. package/template/src/lib/auth/client.ts +227 -0
  386. package/template/src/lib/auth/edge.ts +84 -0
  387. package/template/src/lib/auth/errors.ts +99 -0
  388. package/template/src/lib/auth/index.ts +159 -0
  389. package/template/src/lib/auth/server.ts +46 -0
  390. package/template/src/lib/auth/session-context.tsx +96 -0
  391. package/template/src/lib/cloudflare/bindings.ts +31 -0
  392. package/template/src/lib/cookie-consent.ts +33 -0
  393. package/template/src/lib/file-utils.ts +45 -0
  394. package/template/src/lib/fingerprint/index.ts +73 -0
  395. package/template/src/lib/health/checks.ts +6 -0
  396. package/template/src/lib/health/core.ts +86 -0
  397. package/template/src/lib/health/index.ts +11 -0
  398. package/template/src/lib/health/types.ts +13 -0
  399. package/template/src/lib/index.ts +2 -0
  400. package/template/src/lib/logger/index.ts +258 -0
  401. package/template/src/lib/monitoring/index.ts +384 -0
  402. package/template/src/lib/rate-limit/anonymous.ts +85 -0
  403. package/template/src/lib/rate-limit/index.ts +111 -0
  404. package/template/src/lib/safe-action.ts +127 -0
  405. package/template/src/lib/seo/json-ld.ts +188 -0
  406. package/template/src/lib/source.ts +122 -0
  407. package/template/src/lib/utils.ts +6 -0
  408. package/template/src/middleware.ts +162 -0
  409. package/template/src/modules/index.ts +10 -0
  410. package/template/src/modules/presets.ts +8 -0
  411. package/template/src/modules/registry.ts +34 -0
  412. package/template/src/modules/selection.ts +45 -0
  413. package/template/src/services/ai.ts +49 -0
  414. package/template/src/services/index.ts +6 -0
  415. package/template/src/services/jobs.ts +3 -0
  416. package/template/src/services/mail.ts +7 -0
  417. package/template/src/services/payment.ts +3 -0
  418. package/template/src/services/rate-limit.ts +12 -0
  419. package/template/src/services/storage.ts +3 -0
  420. package/template/src/types/fumadocs-source.d.ts +14 -0
  421. package/template/src/types/index.ts +2 -0
  422. package/template/tsconfig.json +53 -0
  423. package/template/vercel.json +11 -0
  424. package/template/vitest.config.ts +51 -0
  425. package/template/wrangler.jsonc +30 -0
@@ -0,0 +1,68 @@
1
+ import type { Metadata } from "next";
2
+ import { notFound } from "next/navigation";
3
+
4
+ import {
5
+ PseoCta,
6
+ PseoFaq,
7
+ PseoFeatureGrid,
8
+ PseoHero,
9
+ PseoRelated,
10
+ PseoUseCases,
11
+ } from "@/features/pseo";
12
+ import {
13
+ getAllPseoParams,
14
+ getPseoPage,
15
+ getRelatedPseoPages,
16
+ } from "@/features/pseo";
17
+
18
+ export function generateStaticParams() {
19
+ return getAllPseoParams();
20
+ }
21
+
22
+ export async function generateMetadata({
23
+ params,
24
+ }: {
25
+ params: Promise<{ locale: string; slug: string }>;
26
+ }): Promise<Metadata> {
27
+ const { locale, slug } = await params;
28
+ const page = getPseoPage(locale, slug);
29
+
30
+ if (!page) {
31
+ return { title: "PSEO Template Not Found" };
32
+ }
33
+
34
+ return {
35
+ title: page.data.seo.title,
36
+ description: page.data.seo.description,
37
+ };
38
+ }
39
+
40
+ export default async function PseoPage({
41
+ params,
42
+ }: {
43
+ params: Promise<{ locale: string; slug: string }>;
44
+ }) {
45
+ const { locale, slug } = await params;
46
+ const page = getPseoPage(locale, slug);
47
+
48
+ if (!page) {
49
+ notFound();
50
+ }
51
+
52
+ const related = getRelatedPseoPages(page.locale, page.slug);
53
+
54
+ return (
55
+ <>
56
+ <PseoHero page={page} />
57
+ <PseoFeatureGrid page={page} />
58
+ <PseoUseCases page={page} />
59
+ <PseoFaq page={page} />
60
+ <PseoRelated
61
+ pages={related}
62
+ title={page.data.sections.related.title}
63
+ subtitle={page.data.sections.related.subtitle}
64
+ />
65
+ <PseoCta page={page} />
66
+ </>
67
+ );
68
+ }
@@ -0,0 +1,137 @@
1
+ import { ArrowUpRight, Database, LayoutTemplate, Route } from "lucide-react";
2
+
3
+ import { Badge } from "@/components/ui/badge";
4
+ import { Card, CardContent } from "@/components/ui/card";
5
+ import { getPseoPages } from "@/features/pseo";
6
+ import { Link } from "@/i18n/routing";
7
+
8
+ export default async function PseoIndexPage({
9
+ params,
10
+ }: {
11
+ params: Promise<{ locale: string }>;
12
+ }) {
13
+ const { locale } = await params;
14
+ const pages = getPseoPages(locale);
15
+ const isZh = locale === "zh";
16
+
17
+ const overviewCards = isZh
18
+ ? [
19
+ {
20
+ title: "JSON 数据驱动",
21
+ description: "每个 PSEO 页面都来自结构化 JSON 配置。",
22
+ icon: Database,
23
+ },
24
+ {
25
+ title: "复用页面模块",
26
+ description: "Hero、特性、场景、FAQ、CTA 可复用组合。",
27
+ icon: LayoutTemplate,
28
+ },
29
+ {
30
+ title: "多语言输出",
31
+ description: "自动匹配当前语言版本内容。",
32
+ icon: Route,
33
+ },
34
+ ]
35
+ : [
36
+ {
37
+ title: "JSON-first data",
38
+ description:
39
+ "Every PSEO page is generated from structured JSON fields.",
40
+ icon: Database,
41
+ },
42
+ {
43
+ title: "Reusable sections",
44
+ description:
45
+ "Hero, features, use cases, FAQ, and CTA reuse the same UI blocks.",
46
+ icon: LayoutTemplate,
47
+ },
48
+ {
49
+ title: "Locale-aware",
50
+ description:
51
+ "Switch locales to render the matching content automatically.",
52
+ icon: Route,
53
+ },
54
+ ];
55
+
56
+ return (
57
+ <section className="container py-20">
58
+ <div className="mx-auto mb-16 flex max-w-4xl flex-col items-center text-center">
59
+ <Badge variant="outline" className="mb-4 rounded-full px-4 py-1">
60
+ PSEO Framework
61
+ </Badge>
62
+ <h1 className="text-balance text-4xl font-bold tracking-tight md:text-5xl">
63
+ {isZh ? "PSEO 框架演示库" : "Programmatic SEO Demo Library"}
64
+ </h1>
65
+ <p className="mt-4 max-w-2xl text-lg text-muted-foreground">
66
+ {isZh
67
+ ? "所有模板均由 JSON 数据驱动,可快速扩展到成百上千个落地页。"
68
+ : "Templates are generated from JSON data and reusable UI blocks. Add entries to scale to thousands of landing pages."}
69
+ </p>
70
+ </div>
71
+
72
+ <div className="mx-auto mb-16 grid max-w-5xl gap-6 md:grid-cols-3">
73
+ {overviewCards.map((card) => {
74
+ const Icon = card.icon;
75
+ return (
76
+ <Card key={card.title} className="border-0 bg-muted/50">
77
+ <CardContent className="p-6">
78
+ <div className="mb-4 flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10 text-primary">
79
+ <Icon className="h-5 w-5" />
80
+ </div>
81
+ <h3 className="mb-2 text-base font-semibold">{card.title}</h3>
82
+ <p className="text-sm text-muted-foreground">
83
+ {card.description}
84
+ </p>
85
+ </CardContent>
86
+ </Card>
87
+ );
88
+ })}
89
+ </div>
90
+
91
+ <div className="mx-auto max-w-6xl">
92
+ <div className="mb-8 flex items-center justify-between">
93
+ <div>
94
+ <h2 className="text-2xl font-bold tracking-tight md:text-3xl">
95
+ {isZh ? "可用模板" : "Available templates"}
96
+ </h2>
97
+ <p className="mt-2 text-muted-foreground">
98
+ {isZh
99
+ ? "选择任意模板查看完整的 PSEO 落地页。"
100
+ : "Pick any template to view the full PSEO landing page."}
101
+ </p>
102
+ </div>
103
+ </div>
104
+
105
+ <div className="grid gap-6 md:grid-cols-2">
106
+ {pages.map((page) => (
107
+ <Card
108
+ key={page.slug}
109
+ className="group rounded-2xl border-0 bg-muted/50 transition-colors hover:bg-muted"
110
+ >
111
+ <CardContent className="flex h-full flex-col p-6">
112
+ <div className="mb-4 flex items-center justify-between">
113
+ <Badge variant="secondary">{page.category}</Badge>
114
+ <ArrowUpRight className="h-4 w-4 text-muted-foreground transition-colors group-hover:text-foreground" />
115
+ </div>
116
+ <h3 className="mb-2 text-lg font-semibold">
117
+ {page.data.hero.title} {page.data.hero.highlight}
118
+ </h3>
119
+ <p className="mb-6 text-sm text-muted-foreground">
120
+ {page.data.seo.description}
121
+ </p>
122
+ <div className="mt-auto">
123
+ <Link
124
+ href={`/pseo/${page.slug}`}
125
+ className="text-sm font-semibold text-primary hover:underline"
126
+ >
127
+ {isZh ? "查看模板" : "View template"}
128
+ </Link>
129
+ </div>
130
+ </CardContent>
131
+ </Card>
132
+ ))}
133
+ </div>
134
+ </div>
135
+ </section>
136
+ );
137
+ }
@@ -0,0 +1,63 @@
1
+ import { DocsBody, DocsPage, DocsTitle } from "fumadocs-ui/page";
2
+ import type { Metadata } from "next";
3
+ import { notFound } from "next/navigation";
4
+
5
+ import { routing } from "@/i18n/routing";
6
+ import { docsSource } from "@/lib/source";
7
+
8
+ export function generateStaticParams() {
9
+ return docsSource.generateParams().map(({ lang, slug }) => ({
10
+ locale: lang,
11
+ slug,
12
+ }));
13
+ }
14
+
15
+ export async function generateMetadata({
16
+ params,
17
+ }: {
18
+ params: Promise<{ locale: string; slug?: string[] }>;
19
+ }): Promise<Metadata> {
20
+ const { locale, slug } = await params;
21
+ const docsLocale = routing.locales.includes(locale as "en" | "zh")
22
+ ? locale
23
+ : routing.defaultLocale;
24
+ const page = docsSource.getPage(slug, docsLocale);
25
+
26
+ if (!page) {
27
+ return {
28
+ title: "Not Found",
29
+ };
30
+ }
31
+
32
+ return {
33
+ title: page.data.title,
34
+ description: page.data.description,
35
+ };
36
+ }
37
+
38
+ export default async function Page({
39
+ params,
40
+ }: {
41
+ params: Promise<{ locale: string; slug?: string[] }>;
42
+ }) {
43
+ const { locale, slug } = await params;
44
+ const docsLocale = routing.locales.includes(locale as "en" | "zh")
45
+ ? locale
46
+ : routing.defaultLocale;
47
+ const page = docsSource.getPage(slug, docsLocale);
48
+
49
+ if (!page) {
50
+ notFound();
51
+ }
52
+
53
+ const MDXContent = page.data.body;
54
+
55
+ return (
56
+ <DocsPage toc={page.data.toc}>
57
+ <DocsTitle>{page.data.title}</DocsTitle>
58
+ <DocsBody>
59
+ <MDXContent />
60
+ </DocsBody>
61
+ </DocsPage>
62
+ );
63
+ }
@@ -0,0 +1,39 @@
1
+ import { DocsLayout } from "fumadocs-ui/layouts/docs";
2
+ import type { ReactNode } from "react";
3
+
4
+ import { Header } from "@/features/marketing";
5
+ import { routing } from "@/i18n/routing";
6
+ import { docsI18n, docsSource } from "@/lib/source";
7
+
8
+ export default async function Layout({
9
+ children,
10
+ params,
11
+ }: {
12
+ children: ReactNode;
13
+ params: Promise<{ locale: string }>;
14
+ }) {
15
+ const { locale } = await params;
16
+ const docsLocale = routing.locales.includes(locale as "en" | "zh")
17
+ ? locale
18
+ : routing.defaultLocale;
19
+ const tree = docsSource.getPageTree(docsLocale);
20
+
21
+ return (
22
+ <>
23
+ <Header />
24
+
25
+ <DocsLayout
26
+ tree={tree}
27
+ i18n={docsI18n}
28
+ nav={{
29
+ enabled: false,
30
+ }}
31
+ sidebar={{
32
+ defaultOpenLevel: 1,
33
+ }}
34
+ >
35
+ {children}
36
+ </DocsLayout>
37
+ </>
38
+ );
39
+ }
@@ -0,0 +1,80 @@
1
+ import type { Metadata } from "next";
2
+ import { notFound } from "next/navigation";
3
+ import { NextIntlClientProvider } from "next-intl";
4
+ import { getMessages } from "next-intl/server";
5
+ import { Toaster } from "sonner";
6
+ import { siteConfig } from "@/config";
7
+ import { Analytics } from "@/features/analytics";
8
+ import { CookieConsent } from "@/features/marketing";
9
+ import { Providers } from "@/features/shared";
10
+ import { routing } from "@/i18n/routing";
11
+
12
+ /**
13
+ * 生成静态参数
14
+ * 为每个支持的语言生成静态页面
15
+ */
16
+ export function generateStaticParams() {
17
+ return routing.locales.map((locale) => ({ locale }));
18
+ }
19
+
20
+ /**
21
+ * 生成 hreflang metadata
22
+ */
23
+ export async function generateMetadata({
24
+ params,
25
+ }: {
26
+ params: Promise<{ locale: string }>;
27
+ }): Promise<Metadata> {
28
+ const { locale } = await params;
29
+ const baseUrl = siteConfig.url;
30
+
31
+ return {
32
+ alternates: {
33
+ canonical: `${baseUrl}/${locale}`,
34
+ languages: {
35
+ en: `${baseUrl}/en`,
36
+ zh: `${baseUrl}/zh`,
37
+ "x-default": `${baseUrl}/en`,
38
+ },
39
+ },
40
+ };
41
+ }
42
+
43
+ /**
44
+ * Locale 布局
45
+ *
46
+ * 功能:
47
+ * - 验证语言参数有效性
48
+ * - 提供国际化上下文 (NextIntlClientProvider)
49
+ * - 包装 Providers (主题等)
50
+ * - 全局组件 (CookieConsent, Toaster)
51
+ */
52
+ export default async function LocaleLayout({
53
+ children,
54
+ params,
55
+ }: {
56
+ children: React.ReactNode;
57
+ params: Promise<{ locale: string }>;
58
+ }) {
59
+ // 获取语言参数
60
+ const { locale } = await params;
61
+
62
+ // 验证语言是否有效
63
+ if (!routing.locales.includes(locale as "en" | "zh")) {
64
+ notFound();
65
+ }
66
+
67
+ // 获取翻译消息
68
+ const messages = await getMessages();
69
+
70
+ return (
71
+ <NextIntlClientProvider messages={messages}>
72
+ <Providers locale={locale}>
73
+ {children}
74
+ <CookieConsent />
75
+ <Toaster richColors position="top-right" />
76
+ <Analytics />
77
+ </Providers>
78
+ </NextIntlClientProvider>
79
+ );
80
+ }
@@ -0,0 +1,20 @@
1
+ import { siteConfig } from "@/config";
2
+ import {
3
+ createOgImageResponse,
4
+ OG_IMAGE_SIZE,
5
+ } from "@/components/seo/og-image-template";
6
+
7
+ /**
8
+ * Open Graph 图片配置
9
+ */
10
+ export const runtime = "nodejs";
11
+ export const alt = siteConfig.name;
12
+ export const size = OG_IMAGE_SIZE;
13
+ export const contentType = "image/png";
14
+
15
+ /**
16
+ * 动态生成 Open Graph 图片
17
+ */
18
+ export default async function Image() {
19
+ return createOgImageResponse();
20
+ }
@@ -0,0 +1,20 @@
1
+ import { siteConfig } from "@/config";
2
+ import {
3
+ createOgImageResponse,
4
+ OG_IMAGE_SIZE,
5
+ } from "@/components/seo/og-image-template";
6
+
7
+ /**
8
+ * Twitter 图片配置
9
+ */
10
+ export const runtime = "nodejs";
11
+ export const alt = siteConfig.name;
12
+ export const size = OG_IMAGE_SIZE;
13
+ export const contentType = "image/png";
14
+
15
+ /**
16
+ * 动态生成 Twitter 图片
17
+ */
18
+ export default async function Image() {
19
+ return createOgImageResponse();
20
+ }
@@ -0,0 +1,20 @@
1
+ import { toNextJsHandler } from "better-auth/next-js";
2
+ import { withApiLogging } from "@/lib/api-logger";
3
+ import { auth } from "@/lib/auth/index";
4
+
5
+ /**
6
+ * Better Auth API 路由处理器
7
+ *
8
+ * 此文件处理所有 /api/auth/* 请求
9
+ * Better Auth 自动处理:
10
+ * - /api/auth/sign-in - 登录
11
+ * - /api/auth/sign-up - 注册
12
+ * - /api/auth/sign-out - 登出
13
+ * - /api/auth/session - 获取会话
14
+ * - /api/auth/callback/* - OAuth 回调
15
+ * - 等等...
16
+ */
17
+ const authHandlers = toNextJsHandler(auth);
18
+
19
+ export const GET = withApiLogging(authHandlers.GET);
20
+ export const POST = withApiLogging(authHandlers.POST);
@@ -0,0 +1,45 @@
1
+ import { withApiLogging } from "@/lib/api-logger";
2
+ import type { HealthCheckResult } from "@/lib/health";
3
+ import {
4
+ createHealthReport,
5
+ probeDatabase,
6
+ runHealthProbe,
7
+ validateDeploymentEnvironment,
8
+ } from "@/lib/health";
9
+
10
+ export const dynamic = "force-dynamic";
11
+ export const runtime = "nodejs";
12
+
13
+ const DATABASE_TIMEOUT_MS = 5_000;
14
+ const PASS: HealthCheckResult = { status: "pass", durationMs: 0 };
15
+ const FAIL: HealthCheckResult = { status: "fail", durationMs: 0 };
16
+
17
+ function getVersion(): string {
18
+ return (
19
+ process.env.APP_VERSION?.trim() ||
20
+ process.env.VERCEL_GIT_COMMIT_SHA?.slice(0, 12) ||
21
+ "unknown"
22
+ );
23
+ }
24
+
25
+ export const GET = withApiLogging(async (request: Request) => {
26
+ const mode = new URL(request.url).searchParams.get("mode");
27
+ const checks: Record<string, HealthCheckResult> = { application: PASS };
28
+
29
+ if (mode !== "live") {
30
+ const configurationValid =
31
+ validateDeploymentEnvironment(process.env).length === 0;
32
+ checks.configuration = configurationValid ? PASS : FAIL;
33
+ checks.database = configurationValid
34
+ ? await runHealthProbe(probeDatabase, {
35
+ timeoutMs: DATABASE_TIMEOUT_MS,
36
+ })
37
+ : FAIL;
38
+ }
39
+
40
+ const report = createHealthReport({ checks, version: getVersion() });
41
+ return Response.json(report, {
42
+ status: report.status === "healthy" ? 200 : 503,
43
+ headers: { "Cache-Control": "no-store" },
44
+ });
45
+ });
@@ -0,0 +1,12 @@
1
+ import { inngestHandlers } from "@/adapters/jobs/inngest";
2
+ import { withApiLogging } from "@/lib/api-logger";
3
+
4
+ /**
5
+ * Inngest API 路由
6
+ *
7
+ * 处理 Inngest 的 webhook 请求
8
+ * 支持开发模式和生产模式
9
+ */
10
+ export const GET = withApiLogging(inngestHandlers.GET);
11
+ export const POST = withApiLogging(inngestHandlers.POST);
12
+ export const PUT = withApiLogging(inngestHandlers.PUT);
@@ -0,0 +1,102 @@
1
+ import { headers } from "next/headers";
2
+ import { NextResponse } from "next/server";
3
+
4
+ import { processExpiredBatches } from "@/features/credits";
5
+ import { withApiLogging } from "@/lib/api-logger";
6
+ import { logError, logWarn } from "@/lib/logger";
7
+
8
+ /**
9
+ * 积分过期处理 Cron Job API
10
+ *
11
+ * 定期调用此 API 来处理过期的积分批次
12
+ * 需要通过 Bearer Token 进行身份验证
13
+ *
14
+ * 配置 Vercel Cron:
15
+ * 在 vercel.json 中添加:
16
+ * {
17
+ * "crons": [{
18
+ * "path": "/api/jobs/credits/expire",
19
+ * "schedule": "0 0 * * *"
20
+ * }]
21
+ * }
22
+ *
23
+ * 或使用外部 Cron 服务调用
24
+ */
25
+
26
+ /**
27
+ * 验证 Cron Job 请求的 Bearer Token
28
+ */
29
+ function validateCronSecret(authHeader: string | null): boolean {
30
+ if (!authHeader) return false;
31
+
32
+ const cronSecret = process.env.CRON_SECRET;
33
+ if (!cronSecret) {
34
+ logWarn("CRON_SECRET environment variable is not set");
35
+ return false;
36
+ }
37
+
38
+ // 支持 Bearer Token 格式
39
+ const token = authHeader.startsWith("Bearer ")
40
+ ? authHeader.slice(7)
41
+ : authHeader;
42
+
43
+ return token === cronSecret;
44
+ }
45
+
46
+ /**
47
+ * POST /api/jobs/credits/expire
48
+ *
49
+ * 处理所有过期的积分批次
50
+ */
51
+ export const POST = withApiLogging(async () => {
52
+ const headersList = await headers();
53
+ const authHeader = headersList.get("authorization");
54
+
55
+ // 验证身份
56
+ if (!validateCronSecret(authHeader)) {
57
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
58
+ }
59
+
60
+ try {
61
+ // 执行过期处理
62
+ const results = await processExpiredBatches();
63
+
64
+ // 返回处理结果
65
+ return NextResponse.json({
66
+ success: true,
67
+ processed: results.length,
68
+ details: results.map((r) => ({
69
+ batchId: r.batchId,
70
+ userId: r.userId,
71
+ expiredAmount: r.expiredAmount,
72
+ })),
73
+ timestamp: new Date().toISOString(),
74
+ });
75
+ } catch (error) {
76
+ logError("Failed to process expired batches", { error: String(error) });
77
+
78
+ return NextResponse.json(
79
+ {
80
+ success: false,
81
+ error: "Failed to process expired batches",
82
+ message: error instanceof Error ? error.message : "Unknown error",
83
+ },
84
+ { status: 500 }
85
+ );
86
+ }
87
+ });
88
+
89
+ /**
90
+ * GET /api/jobs/credits/expire
91
+ *
92
+ * 健康检查端点,用于验证 Cron Job 配置是否正确
93
+ */
94
+ export const GET = withApiLogging(async () => {
95
+ return NextResponse.json({
96
+ status: "ok",
97
+ endpoint: "/api/jobs/credits/expire",
98
+ method: "POST",
99
+ description: "Process expired credit batches",
100
+ authentication: "Bearer token required (CRON_SECRET)",
101
+ });
102
+ });
@@ -0,0 +1,17 @@
1
+ import { createFromSource } from "fumadocs-core/search/server";
2
+ import { withApiLogging } from "@/lib/api-logger";
3
+ import { docsSource } from "@/lib/source";
4
+
5
+ /**
6
+ * Orama 搜索 API
7
+ *
8
+ * 基于 fumadocs-core 的 Orama 搜索实现
9
+ * 自动索引文档内容,支持全文搜索
10
+ */
11
+ const searchHandlers = createFromSource(docsSource, {
12
+ localeMap: {
13
+ zh: "english",
14
+ },
15
+ });
16
+
17
+ export const GET = withApiLogging(searchHandlers.GET);