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,81 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * 工单类别选项
5
+ */
6
+ export const ticketCategories = [
7
+ { value: "billing", label: "账单问题" },
8
+ { value: "technical", label: "技术支持" },
9
+ { value: "bug", label: "Bug 报告" },
10
+ { value: "feature", label: "功能建议" },
11
+ { value: "other", label: "其他" },
12
+ ] as const;
13
+
14
+ /**
15
+ * 工单优先级选项
16
+ */
17
+ export const ticketPriorities = [
18
+ { value: "low", label: "低", color: "bg-success" },
19
+ { value: "medium", label: "中", color: "bg-warning" },
20
+ { value: "high", label: "高", color: "bg-destructive" },
21
+ ] as const;
22
+
23
+ /**
24
+ * 工单状态选项
25
+ */
26
+ export const ticketStatuses = [
27
+ { value: "open", label: "待处理", color: "bg-primary" },
28
+ { value: "in_progress", label: "处理中", color: "bg-warning" },
29
+ { value: "resolved", label: "已解决", color: "bg-success" },
30
+ { value: "closed", label: "已关闭", color: "bg-muted-foreground" },
31
+ ] as const;
32
+
33
+ /**
34
+ * 创建工单 Schema
35
+ */
36
+ export const createTicketSchema = z.object({
37
+ /** 工单主题 */
38
+ subject: z
39
+ .string()
40
+ .min(5, "主题至少需要5个字符")
41
+ .max(200, "主题最多200个字符"),
42
+ /** 工单类别 */
43
+ category: z.enum(["billing", "technical", "bug", "feature", "other"]),
44
+ /** 优先级 */
45
+ priority: z.enum(["low", "medium", "high"]).default("medium"),
46
+ /** 初始消息内容 */
47
+ message: z
48
+ .string()
49
+ .min(10, "消息内容至少需要10个字符")
50
+ .max(5000, "消息内容最多5000个字符"),
51
+ });
52
+
53
+ /**
54
+ * 添加工单消息 Schema
55
+ */
56
+ export const addTicketMessageSchema = z.object({
57
+ /** 工单 ID */
58
+ ticketId: z.string().min(1, "工单ID不能为空"),
59
+ /** 消息内容 */
60
+ content: z
61
+ .string()
62
+ .min(1, "消息内容不能为空")
63
+ .max(5000, "消息内容最多5000个字符"),
64
+ });
65
+
66
+ /**
67
+ * 更新工单状态 Schema (管理员)
68
+ */
69
+ export const updateTicketStatusSchema = z.object({
70
+ /** 工单 ID */
71
+ ticketId: z.string().min(1, "工单ID不能为空"),
72
+ /** 新状态 */
73
+ status: z.enum(["open", "in_progress", "resolved", "closed"]),
74
+ });
75
+
76
+ /**
77
+ * 类型导出
78
+ */
79
+ export type CreateTicketInput = z.infer<typeof createTicketSchema>;
80
+ export type AddTicketMessageInput = z.infer<typeof addTicketMessageSchema>;
81
+ export type UpdateTicketStatusInput = z.infer<typeof updateTicketStatusSchema>;
@@ -0,0 +1,2 @@
1
+ // Custom React hooks
2
+ export {};
@@ -0,0 +1,12 @@
1
+ /**
2
+ * 国际化模块统一导出
3
+ */
4
+
5
+ export {
6
+ getPathname,
7
+ Link,
8
+ redirect,
9
+ routing,
10
+ usePathname,
11
+ useRouter,
12
+ } from "./routing";
@@ -0,0 +1,24 @@
1
+ import { getRequestConfig } from "next-intl/server";
2
+
3
+ import { routing } from "./routing";
4
+
5
+ /**
6
+ * 请求级别的国际化配置
7
+ *
8
+ * 根据请求的语言加载对应的翻译消息
9
+ */
10
+ export default getRequestConfig(async ({ requestLocale }) => {
11
+ // 获取请求的语言
12
+ let locale = await requestLocale;
13
+
14
+ // 验证语言是否有效,无效则使用默认语言
15
+ if (!locale || !routing.locales.includes(locale as "en" | "zh")) {
16
+ locale = routing.defaultLocale;
17
+ }
18
+
19
+ return {
20
+ locale,
21
+ // 动态加载对应语言的翻译文件
22
+ messages: (await import(`../../messages/${locale}.json`)).default,
23
+ };
24
+ });
@@ -0,0 +1,23 @@
1
+ import { createNavigation } from "next-intl/navigation";
2
+ import { defineRouting } from "next-intl/routing";
3
+
4
+ /**
5
+ * 国际化路由配置
6
+ *
7
+ * 定义支持的语言和默认语言
8
+ */
9
+ export const routing = defineRouting({
10
+ // 支持的语言列表
11
+ locales: ["en", "zh"],
12
+ // 默认语言
13
+ defaultLocale: "en",
14
+ });
15
+
16
+ /**
17
+ * 导出国际化导航组件和钩子
18
+ *
19
+ * 使用这些替代 next/link 和 next/navigation
20
+ * 以确保正确处理语言前缀
21
+ */
22
+ export const { Link, redirect, usePathname, useRouter, getPathname } =
23
+ createNavigation(routing);
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Next.js Instrumentation Hook
3
+ *
4
+ * 用于在服务器启动时执行初始化逻辑
5
+ * https://nextjs.org/docs/app/building-your-application/optimizing/instrumentation
6
+ */
7
+
8
+ export async function register() {
9
+ // 服务端初始化
10
+ if (process.env.NEXT_RUNTIME === "nodejs") {
11
+ // Sentry 服务端初始化
12
+ await import("../sentry.server.config");
13
+ }
14
+
15
+ // Edge Runtime 初始化
16
+ if (process.env.NEXT_RUNTIME === "edge") {
17
+ await import("../sentry.edge.config");
18
+ }
19
+ }
@@ -0,0 +1,16 @@
1
+ import type { AICompletionInput, AIMessage } from "@/core/services";
2
+ import { aiService, getAIModel, getAIProvider } from "@/services/ai";
3
+
4
+ export type { AIMessage };
5
+ export { getAIModel, getAIProvider };
6
+
7
+ export async function chatCompletion(
8
+ messages: readonly AIMessage[],
9
+ options?: Omit<AICompletionInput, "messages">
10
+ ): Promise<string> {
11
+ const result = await aiService.complete({
12
+ messages,
13
+ ...options,
14
+ });
15
+ return result.content;
16
+ }
@@ -0,0 +1,24 @@
1
+ import { logApiResponse, logError } from "@/lib/logger";
2
+
3
+ // biome-ignore lint/suspicious/noExplicitAny: wrapper must accept both Request and NextRequest
4
+ type ApiHandler = (request: any, context?: any) => Promise<Response>;
5
+
6
+ export function withApiLogging<T extends ApiHandler>(handler: T): T {
7
+ const wrapped = async (request: Request, context?: unknown) => {
8
+ const startTime = Date.now();
9
+ try {
10
+ const response = await handler(request, context);
11
+ logApiResponse(request, response, Date.now() - startTime);
12
+ return response;
13
+ } catch (error) {
14
+ logError(error, {
15
+ source: "api",
16
+ method: request.method,
17
+ path: new URL(request.url).pathname,
18
+ duration: Date.now() - startTime,
19
+ });
20
+ throw error;
21
+ }
22
+ };
23
+ return wrapped as T;
24
+ }
@@ -0,0 +1,54 @@
1
+ import { redirect } from "next/navigation";
2
+
3
+ import { getServerSession } from "@/lib/auth/server";
4
+
5
+ /**
6
+ * Admin 权限检查
7
+ *
8
+ * 用于保护管理员路由
9
+ * 如果用户未登录或不是管理员,重定向到首页
10
+ *
11
+ * @returns 当前用户会话 (如果是管理员)
12
+ * @throws Redirect to "/" if not admin
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * // 在 Admin 布局或页面中使用
17
+ * export default async function AdminLayout({ children }) {
18
+ * await checkAdmin();
19
+ * return <>{children}</>;
20
+ * }
21
+ * ```
22
+ */
23
+ export async function checkAdmin() {
24
+ const session = await getServerSession();
25
+
26
+ // 检查用户是否登录
27
+ if (!session || !session.user) {
28
+ redirect("/sign-in");
29
+ }
30
+
31
+ // 检查用户是否是管理员
32
+ if (session.user.role !== "admin") {
33
+ redirect("/");
34
+ }
35
+
36
+ return session;
37
+ }
38
+
39
+ /**
40
+ * 检查当前用户是否是管理员 (不重定向)
41
+ *
42
+ * 用于需要检查管理员权限但不想重定向的场景
43
+ *
44
+ * @returns 是否是管理员
45
+ */
46
+ export async function isAdmin(): Promise<boolean> {
47
+ const session = await getServerSession();
48
+
49
+ if (!session || !session.user) {
50
+ return false;
51
+ }
52
+
53
+ return session.user.role === "admin";
54
+ }
@@ -0,0 +1,17 @@
1
+ import { toNextJsHandler } from "better-auth/next-js";
2
+
3
+ import { auth } from "./index";
4
+
5
+ /**
6
+ * Better Auth API 处理器
7
+ *
8
+ * 用于 API 路由中处理认证请求
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * // src/app/api/auth/[...all]/route.ts
13
+ * import { GET, POST } from "@/lib/auth/api";
14
+ * export { GET, POST };
15
+ * ```
16
+ */
17
+ export const { GET, POST } = toNextJsHandler(auth);
@@ -0,0 +1,227 @@
1
+ "use client";
2
+
3
+ import { createAuthClient } from "better-auth/react";
4
+
5
+ /**
6
+ * Better Auth 客户端配置
7
+ *
8
+ * 此客户端用于在 React 组件中进行认证操作:
9
+ * - 社交登录 (GitHub, Google)
10
+ * - 邮箱密码登录
11
+ * - 会话管理
12
+ * - 登出
13
+ */
14
+ export const authClient = createAuthClient({
15
+ /**
16
+ * 认证 API 基础 URL
17
+ * 默认指向 /api/auth,与 API 路由匹配
18
+ */
19
+ baseURL: process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000",
20
+ });
21
+
22
+ /**
23
+ * 导出常用的认证方法,便于在组件中使用
24
+ */
25
+ export const {
26
+ // 会话相关
27
+ useSession, // Hook: 获取当前会话状态
28
+ getSession, // 函数: 获取会话 (非 Hook)
29
+
30
+ // 登录方法
31
+ signIn, // 登录 (邮箱密码或社交)
32
+
33
+ // 登出方法
34
+ signOut, // 登出当前会话
35
+
36
+ // 注册方法
37
+ signUp, // 注册新用户 (邮箱密码)
38
+ } = authClient;
39
+
40
+ function getResponseErrorMessage(value: unknown): string | undefined {
41
+ if (typeof value !== "object" || value === null || !("message" in value)) {
42
+ return undefined;
43
+ }
44
+ return typeof value.message === "string" ? value.message : undefined;
45
+ }
46
+
47
+ /**
48
+ * 发送密码重置邮件
49
+ * 通过调用 Better Auth API 端点发送重置链接
50
+ * @param email - 用户邮箱
51
+ * @param redirectTo - 重置链接的跳转地址
52
+ */
53
+ export async function forgetPassword(
54
+ email: string,
55
+ redirectTo = "/reset-password"
56
+ ) {
57
+ const baseURL = process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000";
58
+
59
+ const response = await fetch(`${baseURL}/api/auth/forget-password`, {
60
+ method: "POST",
61
+ headers: {
62
+ "Content-Type": "application/json",
63
+ },
64
+ body: JSON.stringify({
65
+ email,
66
+ redirectTo,
67
+ }),
68
+ });
69
+
70
+ if (!response.ok) {
71
+ throw new Error("Failed to send reset email");
72
+ }
73
+
74
+ return response.json();
75
+ }
76
+
77
+ /**
78
+ * 重置密码
79
+ * @param newPassword - 新密码
80
+ * @param token - 重置令牌 (从 URL 获取)
81
+ */
82
+ export async function resetPassword(newPassword: string, token: string) {
83
+ const baseURL = process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000";
84
+
85
+ const response = await fetch(`${baseURL}/api/auth/reset-password`, {
86
+ method: "POST",
87
+ headers: {
88
+ "Content-Type": "application/json",
89
+ },
90
+ body: JSON.stringify({
91
+ newPassword,
92
+ token,
93
+ }),
94
+ });
95
+
96
+ if (!response.ok) {
97
+ throw new Error("Failed to reset password");
98
+ }
99
+
100
+ return response.json();
101
+ }
102
+
103
+ /**
104
+ * 社交登录辅助函数
105
+ */
106
+
107
+ /**
108
+ * 使用 GitHub 登录
109
+ * @param callbackURL - 登录成功后的跳转地址
110
+ */
111
+ export async function signInWithGitHub(callbackURL = "/dashboard") {
112
+ return signIn.social({
113
+ provider: "github",
114
+ callbackURL,
115
+ });
116
+ }
117
+
118
+ /**
119
+ * 使用 Google 登录
120
+ * @param callbackURL - 登录成功后的跳转地址
121
+ */
122
+ export async function signInWithGoogle(callbackURL = "/dashboard") {
123
+ return signIn.social({
124
+ provider: "google",
125
+ callbackURL,
126
+ });
127
+ }
128
+
129
+ /**
130
+ * 邮箱密码登录
131
+ * @param email - 用户邮箱
132
+ * @param password - 用户密码
133
+ * @param callbackURL - 登录成功后的跳转地址
134
+ */
135
+ export async function signInWithEmail(
136
+ email: string,
137
+ password: string,
138
+ callbackURL = "/dashboard"
139
+ ) {
140
+ return signIn.email({
141
+ email,
142
+ password,
143
+ callbackURL,
144
+ });
145
+ }
146
+
147
+ /**
148
+ * 邮箱密码注册
149
+ * @param email - 用户邮箱
150
+ * @param password - 用户密码
151
+ * @param name - 用户名称
152
+ */
153
+ export async function signUpWithEmail(
154
+ email: string,
155
+ password: string,
156
+ name: string
157
+ ) {
158
+ return signUp.email({
159
+ email,
160
+ password,
161
+ name,
162
+ });
163
+ }
164
+
165
+ /**
166
+ * 修改密码(已登录用户)
167
+ * @param currentPassword - 当前密码
168
+ * @param newPassword - 新密码
169
+ */
170
+ export async function changePassword(
171
+ currentPassword: string,
172
+ newPassword: string
173
+ ) {
174
+ const baseURL = process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000";
175
+
176
+ const response = await fetch(`${baseURL}/api/auth/change-password`, {
177
+ method: "POST",
178
+ headers: {
179
+ "Content-Type": "application/json",
180
+ },
181
+ credentials: "include",
182
+ body: JSON.stringify({
183
+ currentPassword,
184
+ newPassword,
185
+ }),
186
+ });
187
+
188
+ const data: unknown = await response.json();
189
+
190
+ if (!response.ok) {
191
+ throw new Error(
192
+ getResponseErrorMessage(data) ?? "Failed to change password"
193
+ );
194
+ }
195
+
196
+ return data;
197
+ }
198
+
199
+ /**
200
+ * 重新发送邮箱验证邮件
201
+ * @param email - 用户邮箱
202
+ */
203
+ export async function resendVerificationEmail(email: string) {
204
+ const baseURL = process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000";
205
+
206
+ const response = await fetch(`${baseURL}/api/auth/send-verification-email`, {
207
+ method: "POST",
208
+ headers: {
209
+ "Content-Type": "application/json",
210
+ },
211
+ body: JSON.stringify({ email }),
212
+ });
213
+
214
+ if (!response.ok) {
215
+ throw new Error("Failed to send verification email");
216
+ }
217
+
218
+ return response.json();
219
+ }
220
+
221
+ /**
222
+ * 刷新会话
223
+ * 用于在会话数据更新后强制刷新客户端状态
224
+ */
225
+ export async function reloadSession() {
226
+ return getSession();
227
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Edge Runtime 认证函数
3
+ *
4
+ * 用于 Middleware 和 Edge Functions 中验证用户会话
5
+ * Edge Runtime 环境下无法使用完整的数据库连接,
6
+ * 因此使用 Cookie 验证方式
7
+ */
8
+
9
+ /**
10
+ * 从请求中获取会话 Token
11
+ *
12
+ * @param request - Next.js Request 对象
13
+ * @returns 会话 Token 或 undefined
14
+ */
15
+ export function getSessionToken(request: Request): string | undefined {
16
+ const cookieHeader = request.headers.get("cookie");
17
+ if (!cookieHeader) return undefined;
18
+
19
+ // Better Auth 使用的 Cookie 名称
20
+ const cookieNames = [
21
+ "better-auth.session_token",
22
+ "__Secure-better-auth.session_token", // HTTPS 环境
23
+ ];
24
+
25
+ for (const name of cookieNames) {
26
+ const match = cookieHeader.match(new RegExp(`${name}=([^;]+)`));
27
+ if (match) {
28
+ return match[1];
29
+ }
30
+ }
31
+
32
+ return undefined;
33
+ }
34
+
35
+ /**
36
+ * 检查请求是否包含有效的会话 Token
37
+ *
38
+ * 注意: 这只是检查 Token 是否存在,不验证 Token 的有效性
39
+ * 完整的验证需要在服务器端进行
40
+ *
41
+ * @param request - Next.js Request 对象
42
+ * @returns boolean - 是否存在会话 Token
43
+ */
44
+ export function hasSessionToken(request: Request): boolean {
45
+ return !!getSessionToken(request);
46
+ }
47
+
48
+ /**
49
+ * 受保护路由的路径模式
50
+ * 用于 Middleware 中判断是否需要认证
51
+ */
52
+ export const protectedRoutes = [
53
+ "/dashboard",
54
+ "/dashboard/:path*",
55
+ "/settings",
56
+ "/settings/:path*",
57
+ "/admin",
58
+ "/admin/:path*",
59
+ ];
60
+
61
+ /**
62
+ * 认证页面的路径模式
63
+ * 已登录用户访问这些页面时应重定向到 Dashboard
64
+ */
65
+ export const authRoutes = [
66
+ "/sign-in",
67
+ "/sign-up",
68
+ "/forgot-password",
69
+ "/reset-password",
70
+ ];
71
+
72
+ /**
73
+ * 公开路由的路径模式
74
+ * 这些路由不需要认证
75
+ */
76
+ export const publicRoutes = [
77
+ "/",
78
+ "/about",
79
+ "/pricing",
80
+ "/blog",
81
+ "/blog/:path*",
82
+ "/docs",
83
+ "/docs/:path*",
84
+ ];
@@ -0,0 +1,99 @@
1
+ /**
2
+ * 认证错误类型
3
+ */
4
+ export enum AuthErrorCode {
5
+ /** 未认证 - 用户未登录 */
6
+ UNAUTHENTICATED = "UNAUTHENTICATED",
7
+ /** 未授权 - 用户无权限访问 */
8
+ UNAUTHORIZED = "UNAUTHORIZED",
9
+ /** 无效凭证 - 邮箱或密码错误 */
10
+ INVALID_CREDENTIALS = "INVALID_CREDENTIALS",
11
+ /** 用户已存在 - 邮箱已被注册 */
12
+ USER_EXISTS = "USER_EXISTS",
13
+ /** 用户不存在 */
14
+ USER_NOT_FOUND = "USER_NOT_FOUND",
15
+ /** 会话过期 */
16
+ SESSION_EXPIRED = "SESSION_EXPIRED",
17
+ /** OAuth 错误 */
18
+ OAUTH_ERROR = "OAUTH_ERROR",
19
+ /** 邮箱未验证 */
20
+ EMAIL_NOT_VERIFIED = "EMAIL_NOT_VERIFIED",
21
+ /** 未知错误 */
22
+ UNKNOWN = "UNKNOWN",
23
+ }
24
+
25
+ /**
26
+ * 认证错误类
27
+ */
28
+ export class AuthError extends Error {
29
+ code: AuthErrorCode;
30
+
31
+ constructor(code: AuthErrorCode, message?: string) {
32
+ super(message || getDefaultMessage(code));
33
+ this.name = "AuthError";
34
+ this.code = code;
35
+ }
36
+ }
37
+
38
+ /**
39
+ * 获取错误码的默认消息
40
+ */
41
+ function getDefaultMessage(code: AuthErrorCode): string {
42
+ const messages: Record<AuthErrorCode, string> = {
43
+ [AuthErrorCode.UNAUTHENTICATED]: "请先登录后再执行此操作",
44
+ [AuthErrorCode.UNAUTHORIZED]: "您没有权限执行此操作",
45
+ [AuthErrorCode.INVALID_CREDENTIALS]: "邮箱或密码错误",
46
+ [AuthErrorCode.USER_EXISTS]: "该邮箱已被注册",
47
+ [AuthErrorCode.USER_NOT_FOUND]: "用户不存在",
48
+ [AuthErrorCode.SESSION_EXPIRED]: "会话已过期,请重新登录",
49
+ [AuthErrorCode.OAUTH_ERROR]: "第三方登录失败,请重试",
50
+ [AuthErrorCode.EMAIL_NOT_VERIFIED]: "请先验证您的邮箱",
51
+ [AuthErrorCode.UNKNOWN]: "发生未知错误,请稍后重试",
52
+ };
53
+
54
+ return messages[code];
55
+ }
56
+
57
+ /**
58
+ * 判断是否为认证错误
59
+ */
60
+ export function isAuthError(error: unknown): error is AuthError {
61
+ return error instanceof AuthError;
62
+ }
63
+
64
+ /**
65
+ * 从未知错误创建 AuthError
66
+ */
67
+ export function toAuthError(error: unknown): AuthError {
68
+ if (isAuthError(error)) {
69
+ return error;
70
+ }
71
+
72
+ if (error instanceof Error) {
73
+ // 尝试从错误消息推断错误类型
74
+ const message = error.message.toLowerCase();
75
+
76
+ if (message.includes("unauthorized") || message.includes("未授权")) {
77
+ return new AuthError(AuthErrorCode.UNAUTHORIZED);
78
+ }
79
+ if (message.includes("unauthenticated") || message.includes("未登录")) {
80
+ return new AuthError(AuthErrorCode.UNAUTHENTICATED);
81
+ }
82
+ if (message.includes("invalid") || message.includes("credentials")) {
83
+ return new AuthError(AuthErrorCode.INVALID_CREDENTIALS);
84
+ }
85
+ if (message.includes("exists") || message.includes("已存在")) {
86
+ return new AuthError(AuthErrorCode.USER_EXISTS);
87
+ }
88
+ if (message.includes("not found") || message.includes("不存在")) {
89
+ return new AuthError(AuthErrorCode.USER_NOT_FOUND);
90
+ }
91
+ if (message.includes("expired") || message.includes("过期")) {
92
+ return new AuthError(AuthErrorCode.SESSION_EXPIRED);
93
+ }
94
+
95
+ return new AuthError(AuthErrorCode.UNKNOWN, error.message);
96
+ }
97
+
98
+ return new AuthError(AuthErrorCode.UNKNOWN);
99
+ }