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,406 @@
1
+ @import "fumadocs-ui/style.css";
2
+ @import "tailwindcss";
3
+ @import "tw-animate-css";
4
+
5
+ @custom-variant dark (&:is(.dark *));
6
+
7
+ /* ============================================================
8
+ NextDevTpl · "Engineered Spec-Sheet" 设计系统
9
+ 色板:暖中性底(亮:冷 drafting paper / 暗:暖石墨)+ Spruce 主色
10
+ 字体:Geist Sans(正文/展示)+ Geist Mono(点缀/数据)
11
+ ============================================================ */
12
+
13
+ @theme inline {
14
+ --color-background: var(--background);
15
+ --color-foreground: var(--foreground);
16
+ /* 字体:geist 包注入 --font-geist-sans/mono;后面挂 CJK 回退栈 */
17
+ --font-sans: var(--font-geist-sans), ui-sans-serif, system-ui, -apple-system,
18
+ "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
19
+ --font-mono: var(--font-geist-mono), ui-monospace, "SFMono-Regular",
20
+ "Cascadia Code", Consolas, "Roboto Mono", Menlo, monospace;
21
+ --color-sidebar-ring: var(--sidebar-ring);
22
+ --color-sidebar-border: var(--sidebar-border);
23
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
24
+ --color-sidebar-accent: var(--sidebar-accent);
25
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
26
+ --color-sidebar-primary: var(--sidebar-primary);
27
+ --color-sidebar-foreground: var(--sidebar-foreground);
28
+ --color-sidebar: var(--sidebar);
29
+ --color-chart-5: var(--chart-5);
30
+ --color-chart-4: var(--chart-4);
31
+ --color-chart-3: var(--chart-3);
32
+ --color-chart-2: var(--chart-2);
33
+ --color-chart-1: var(--chart-1);
34
+ --color-ring: var(--ring);
35
+ --color-input: var(--input);
36
+ --color-border: var(--border);
37
+ --color-destructive: var(--destructive);
38
+ --color-success: var(--success);
39
+ --color-warning: var(--warning);
40
+ --color-accent-foreground: var(--accent-foreground);
41
+ --color-accent: var(--accent);
42
+ --color-muted-foreground: var(--muted-foreground);
43
+ --color-muted: var(--muted);
44
+ --color-secondary-foreground: var(--secondary-foreground);
45
+ --color-secondary: var(--secondary);
46
+ --color-primary-foreground: var(--primary-foreground);
47
+ --color-primary: var(--primary);
48
+ --color-popover-foreground: var(--popover-foreground);
49
+ --color-popover: var(--popover);
50
+ --color-card-foreground: var(--card-foreground);
51
+ --color-card: var(--card);
52
+ --radius-sm: calc(var(--radius) - 4px);
53
+ --radius-md: calc(var(--radius) - 2px);
54
+ --radius-lg: var(--radius);
55
+ --radius-xl: calc(var(--radius) + 4px);
56
+ --radius-2xl: calc(var(--radius) + 8px);
57
+ --radius-3xl: calc(var(--radius) + 12px);
58
+ --radius-4xl: calc(var(--radius) + 16px);
59
+ }
60
+
61
+ :root {
62
+ /* 圆角:偏克制的 10px 基准(lg=10 / md=8 / sm=6 / xl=14) */
63
+ --radius: 0.625rem;
64
+
65
+ /* ── 亮色:冷 drafting paper 底 + 暖石墨墨色 ── */
66
+ --background: oklch(0.985 0.003 95);
67
+ --foreground: oklch(0.24 0.006 60);
68
+ --card: oklch(1 0 0);
69
+ --card-foreground: oklch(0.24 0.006 60);
70
+ --popover: oklch(1 0 0);
71
+ --popover-foreground: oklch(0.24 0.006 60);
72
+
73
+ /* 主色:Spruce 深云杉绿(品牌定) */
74
+ --primary: oklch(0.40 0.072 184);
75
+ --primary-foreground: oklch(0.99 0.005 95);
76
+
77
+ --secondary: oklch(0.965 0.005 95);
78
+ --secondary-foreground: oklch(0.24 0.006 60);
79
+
80
+ --muted: oklch(0.965 0.005 95);
81
+ --muted-foreground: oklch(0.50 0.008 60);
82
+
83
+ --accent: oklch(0.965 0.005 95);
84
+ --accent-foreground: oklch(0.24 0.006 60);
85
+
86
+ /* 语义色 */
87
+ --destructive: oklch(0.58 0.20 28);
88
+ --success: oklch(0.52 0.095 165);
89
+ --warning: oklch(0.74 0.13 70);
90
+
91
+ /* 发丝级边框:暖灰 */
92
+ --border: oklch(0.905 0.006 90);
93
+ --input: oklch(0.905 0.006 90);
94
+ --ring: oklch(0.40 0.072 184);
95
+
96
+ /* 氛围层:极淡网格 + 双色光晕 */
97
+ --grid-line: oklch(0.24 0.006 60 / 0.045);
98
+ --glow-1: oklch(0.50 0.09 184 / 0.10);
99
+ --glow-2: oklch(0.45 0.06 250 / 0.05);
100
+
101
+ /* 图表色(与主色成体系) */
102
+ --chart-1: oklch(0.40 0.072 184);
103
+ --chart-2: oklch(0.55 0.10 165);
104
+ --chart-3: oklch(0.72 0.13 70);
105
+ --chart-4: oklch(0.58 0.20 28);
106
+ --chart-5: oklch(0.50 0.12 295);
107
+
108
+ /* 侧栏 */
109
+ --sidebar: oklch(0.975 0.004 95);
110
+ --sidebar-foreground: oklch(0.24 0.006 60);
111
+ --sidebar-primary: oklch(0.40 0.072 184);
112
+ --sidebar-primary-foreground: oklch(0.99 0.005 95);
113
+ --sidebar-accent: oklch(0.95 0.005 95);
114
+ --sidebar-accent-foreground: oklch(0.24 0.006 60);
115
+ --sidebar-border: oklch(0.905 0.006 90);
116
+ --sidebar-ring: oklch(0.40 0.072 184);
117
+
118
+ /* ── Fumadocs 主题对齐 ──
119
+ 把 --color-fd-* 全部映射到设计系统 token,让 docs/blog 不再是
120
+ Fumadocs 默认蓝/灰,而是 Spruce + 暖中性,与全站一致。
121
+ 此处 :root 未分层,优先级高于 Fumadocs 在 @layer theme 里的定义;
122
+ 且值是 var(--token),会随 .dark 自动切换,故只需写一处。 */
123
+ --color-fd-background: var(--background);
124
+ --color-fd-foreground: var(--foreground);
125
+ --color-fd-muted: var(--muted);
126
+ --color-fd-muted-foreground: var(--muted-foreground);
127
+ --color-fd-popover: var(--popover);
128
+ --color-fd-popover-foreground: var(--popover-foreground);
129
+ --color-fd-card: var(--card);
130
+ --color-fd-card-foreground: var(--card-foreground);
131
+ --color-fd-border: var(--border);
132
+ --color-fd-primary: var(--primary);
133
+ --color-fd-primary-foreground: var(--primary-foreground);
134
+ --color-fd-secondary: var(--secondary);
135
+ --color-fd-secondary-foreground: var(--secondary-foreground);
136
+ --color-fd-accent: var(--accent);
137
+ --color-fd-accent-foreground: var(--accent-foreground);
138
+ --color-fd-ring: var(--ring);
139
+ --color-fd-error: var(--destructive);
140
+ --color-fd-success: var(--success);
141
+ --color-fd-warning: var(--warning);
142
+ --color-fd-info: var(--primary);
143
+ --color-fd-idea: var(--success);
144
+ --color-fd-diff-add: var(--success);
145
+ --color-fd-diff-add-symbol: var(--success);
146
+ --color-fd-diff-remove: var(--destructive);
147
+ --color-fd-diff-remove-symbol: var(--destructive);
148
+ --color-fd-overlay: oklch(0.1 0 0 / 0.5);
149
+ }
150
+
151
+ .dark {
152
+ /* ── 暗色:暖石墨(非 zinc 冷灰)+ 更亮的 Spruce ── */
153
+ --background: oklch(0.165 0.004 60);
154
+ --foreground: oklch(0.93 0.004 90);
155
+ --card: oklch(0.205 0.005 60);
156
+ --card-foreground: oklch(0.93 0.004 90);
157
+ --popover: oklch(0.205 0.005 60);
158
+ --popover-foreground: oklch(0.93 0.004 90);
159
+
160
+ --primary: oklch(0.68 0.11 178);
161
+ --primary-foreground: oklch(0.165 0.004 60);
162
+
163
+ --secondary: oklch(0.25 0.005 60);
164
+ --secondary-foreground: oklch(0.93 0.004 90);
165
+
166
+ --muted: oklch(0.23 0.005 60);
167
+ --muted-foreground: oklch(0.66 0.006 70);
168
+
169
+ --accent: oklch(0.25 0.005 60);
170
+ --accent-foreground: oklch(0.93 0.004 90);
171
+
172
+ --destructive: oklch(0.62 0.19 28);
173
+ --success: oklch(0.60 0.10 165);
174
+ --warning: oklch(0.74 0.13 70);
175
+
176
+ --border: oklch(0.30 0.006 60);
177
+ --input: oklch(0.27 0.006 60);
178
+ --ring: oklch(0.68 0.11 178);
179
+
180
+ --grid-line: oklch(0.93 0.004 90 / 0.04);
181
+ --glow-1: oklch(0.68 0.11 178 / 0.14);
182
+ --glow-2: oklch(0.50 0.12 295 / 0.08);
183
+
184
+ --chart-1: oklch(0.68 0.11 178);
185
+ --chart-2: oklch(0.62 0.11 165);
186
+ --chart-3: oklch(0.74 0.13 70);
187
+ --chart-4: oklch(0.62 0.19 28);
188
+ --chart-5: oklch(0.60 0.13 295);
189
+
190
+ --sidebar: oklch(0.14 0.004 60);
191
+ --sidebar-foreground: oklch(0.93 0.004 90);
192
+ --sidebar-primary: oklch(0.68 0.11 178);
193
+ --sidebar-primary-foreground: oklch(0.165 0.004 60);
194
+ --sidebar-accent: oklch(0.23 0.005 60);
195
+ --sidebar-accent-foreground: oklch(0.93 0.004 90);
196
+ --sidebar-border: oklch(0.26 0.006 60);
197
+ --sidebar-ring: oklch(0.68 0.11 178);
198
+ }
199
+
200
+ @layer base {
201
+ * {
202
+ @apply border-border outline-ring/50;
203
+ }
204
+ html {
205
+ scroll-behavior: smooth;
206
+ }
207
+ body {
208
+ @apply bg-background text-foreground;
209
+ /* 数字列对齐:所有 tabular 场景默认受益 */
210
+ font-feature-settings: "cv11", "ss01";
211
+ }
212
+ /* 锚点跳转时考虑 sticky header 的高度 */
213
+ [id] {
214
+ scroll-margin-top: 5rem;
215
+ }
216
+ .container {
217
+ width: 100%;
218
+ margin-left: auto;
219
+ margin-right: auto;
220
+ padding-left: 0.75rem;
221
+ padding-right: 0.75rem;
222
+ max-width: 1280px;
223
+ }
224
+ @media (min-width: 640px) {
225
+ .container {
226
+ padding-left: 1.5rem;
227
+ padding-right: 1.5rem;
228
+ }
229
+ }
230
+ /* 选区用主色 */
231
+ ::selection {
232
+ background-color: var(--primary);
233
+ color: var(--primary-foreground);
234
+ }
235
+ /* 极简滚动条 */
236
+ ::-webkit-scrollbar {
237
+ width: 10px;
238
+ height: 10px;
239
+ }
240
+ ::-webkit-scrollbar-thumb {
241
+ background: color-mix(in srgb, var(--foreground) 14%, transparent);
242
+ border-radius: 999px;
243
+ border: 3px solid transparent;
244
+ background-clip: content-box;
245
+ }
246
+ ::-webkit-scrollbar-thumb:hover {
247
+ background: color-mix(in srgb, var(--foreground) 24%, transparent);
248
+ background-clip: content-box;
249
+ }
250
+ }
251
+
252
+ /* ============================================================
253
+ 组件级工具类:eyebrow 标签 / 网格底纹 / 光晕氛围 / 颗粒噪点
254
+ 都挂 CSS 变量,亮暗 + 主色切换自动跟随
255
+ ============================================================ */
256
+ @layer components {
257
+ /* mono 大写小标签——技术控制台视觉的核心点缀 */
258
+ .eyebrow {
259
+ display: inline-flex;
260
+ align-items: center;
261
+ gap: 0.55rem;
262
+ font-family: var(--font-mono);
263
+ font-size: 0.7rem;
264
+ font-weight: 600;
265
+ letter-spacing: 0.16em;
266
+ text-transform: uppercase;
267
+ color: var(--primary);
268
+ }
269
+ .eyebrow::before {
270
+ content: "";
271
+ width: 0.4rem;
272
+ height: 0.4rem;
273
+ background: var(--primary);
274
+ border-radius: 1px;
275
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 14%, transparent);
276
+ }
277
+
278
+ /* 极淡网格底纹(工程图纸味) */
279
+ .bg-grid {
280
+ background-image: linear-gradient(
281
+ var(--grid-line) 1px,
282
+ transparent 1px
283
+ ),
284
+ linear-gradient(90deg, var(--grid-line) 1px, transparent 1px);
285
+ background-size: 56px 56px;
286
+ background-position: center;
287
+ }
288
+
289
+ /* 双色光晕氛围——用在 hero / cta 等需要深度的区块 */
290
+ .aura {
291
+ position: relative;
292
+ }
293
+ .aura::before {
294
+ content: "";
295
+ position: absolute;
296
+ inset: 0;
297
+ pointer-events: none;
298
+ background: radial-gradient(
299
+ 55% 50% at 78% -4%,
300
+ var(--glow-1),
301
+ transparent 70%
302
+ ),
303
+ radial-gradient(45% 42% at 8% 4%, var(--glow-2), transparent 72%);
304
+ }
305
+ .aura > * {
306
+ position: relative;
307
+ }
308
+
309
+ /* 颗粒噪点叠加(消除塑料感)——挂在需要的区块上 */
310
+ .grain {
311
+ position: relative;
312
+ }
313
+ .grain::after {
314
+ content: "";
315
+ position: absolute;
316
+ inset: 0;
317
+ pointer-events: none;
318
+ opacity: 0.045;
319
+ mix-blend-mode: soft-light;
320
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
321
+ }
322
+
323
+ /* 数据/代码默认等宽 + tabular 数字 */
324
+ .mono-data {
325
+ font-family: var(--font-mono);
326
+ font-variant-numeric: tabular-nums;
327
+ font-feature-settings: "tnum";
328
+ }
329
+
330
+ /* 分层柔性阴影(亮暗各一套) */
331
+ .shadow-soft {
332
+ box-shadow: 0 1px 0 0 color-mix(in srgb, var(--foreground) 4%, transparent),
333
+ 0 1px 2px 0 color-mix(in srgb, var(--foreground) 5%, transparent),
334
+ 0 14px 30px -16px color-mix(in srgb, var(--foreground) 14%, transparent);
335
+ }
336
+ .shadow-lifted {
337
+ box-shadow: 0 1px 0 0 color-mix(in srgb, var(--foreground) 5%, transparent),
338
+ 0 30px 70px -30px color-mix(in srgb, var(--foreground) 22%, transparent);
339
+ }
340
+ }
341
+
342
+ /* ============================================================
343
+ 修复 Fumadocs 样式与 NavigationMenu 下拉菜单的冲突
344
+ (Fumadocs 的 .hidden 会干扰 Radix NavigationMenu 状态)
345
+ ============================================================ */
346
+ [data-slot="navigation-menu"] .hidden,
347
+ [data-slot="navigation-menu-viewport"].hidden,
348
+ [data-slot="navigation-menu-content"].hidden {
349
+ display: revert !important;
350
+ }
351
+ [data-slot="navigation-menu-viewport-wrapper"] {
352
+ display: flex !important;
353
+ position: absolute !important;
354
+ top: 100% !important;
355
+ left: 0 !important;
356
+ z-index: 50 !important;
357
+ justify-content: center !important;
358
+ }
359
+ [data-slot="navigation-menu-viewport"] {
360
+ z-index: 100 !important;
361
+ }
362
+ [data-slot="navigation-menu-content"] {
363
+ width: auto !important;
364
+ }
365
+ [data-slot="navigation-menu-item"] {
366
+ position: relative !important;
367
+ }
368
+
369
+ /* 修复 Fumadocs 搜索对话框位置 - 确保垂直居中 */
370
+ [role="dialog"][data-state="open"] {
371
+ top: 50% !important;
372
+ --tw-translate-y: -50% !important;
373
+ }
374
+
375
+ /* 闪卡翻转动画 - 使用 scale 过渡模拟翻转 */
376
+ .flip-card {
377
+ perspective: 800px;
378
+ }
379
+ .flip-card-face {
380
+ transition: transform 0.3s ease, opacity 0.3s ease;
381
+ }
382
+ .flip-card-face-hidden {
383
+ transform: rotateY(90deg);
384
+ opacity: 0;
385
+ position: absolute;
386
+ inset: 0;
387
+ }
388
+ .flip-card-face-visible {
389
+ transform: rotateY(0deg);
390
+ opacity: 1;
391
+ position: relative;
392
+ }
393
+
394
+ /* 尊重「减少动态」偏好 */
395
+ @media (prefers-reduced-motion: reduce) {
396
+ html {
397
+ scroll-behavior: auto;
398
+ }
399
+ *,
400
+ *::before,
401
+ *::after {
402
+ animation-duration: 0.001ms !important;
403
+ animation-iteration-count: 1 !important;
404
+ transition-duration: 0.001ms !important;
405
+ }
406
+ }
@@ -0,0 +1,6 @@
1
+ <svg width="32" height="32" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
2
+ <rect width="24" height="24" rx="5" fill="#0F5C57" />
3
+ <path d="M3 17 L9 7 L13 14 L21 4" fill="none" stroke="#FFFFFF" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" />
4
+ <circle cx="9" cy="7" r="1.6" fill="#FFFFFF" />
5
+ <circle cx="21" cy="4" r="1.6" fill="#FFFFFF" />
6
+ </svg>
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Image Proxy 路由
3
+ *
4
+ * 安全地代理访问存储中的图片
5
+ * 路由: /image-proxy/{bucket}/{key}
6
+ *
7
+ * 示例: /image-proxy/avatars/user-123/1234567890.jpg
8
+ */
9
+
10
+ import { type NextRequest, NextResponse } from "next/server";
11
+
12
+ import { DEFAULT_SIGNED_URL_EXPIRES } from "@/features/storage/types";
13
+ import { storageService } from "@/services/storage";
14
+
15
+ // ============================================
16
+ // 配置
17
+ // ============================================
18
+
19
+ /**
20
+ * 获取允许的存储桶白名单
21
+ *
22
+ * 安全措施:只允许代理访问预定义的存储桶
23
+ */
24
+ function getAllowedBuckets(): string[] {
25
+ const avatarsBucket = process.env.NEXT_PUBLIC_AVATARS_BUCKET_NAME;
26
+ const uploadsBucket = process.env.STORAGE_BUCKET_NAME ?? "nextdevtpl-uploads";
27
+
28
+ const buckets = new Set<string>([uploadsBucket]);
29
+
30
+ if (avatarsBucket) {
31
+ buckets.add(avatarsBucket);
32
+ }
33
+
34
+ return [...buckets];
35
+ }
36
+
37
+ /**
38
+ * 缓存时间 (秒)
39
+ *
40
+ * 与签名 URL 有效期保持一致
41
+ */
42
+ const CACHE_MAX_AGE = DEFAULT_SIGNED_URL_EXPIRES;
43
+
44
+ // ============================================
45
+ // 路由处理器
46
+ // ============================================
47
+
48
+ /**
49
+ * GET /image-proxy/[...path]
50
+ *
51
+ * 代理图片请求,生成签名 URL 并重定向
52
+ *
53
+ * @param _request - Next.js 请求对象 (未使用)
54
+ * @param params - 路由参数 (path 数组)
55
+ */
56
+ export async function GET(
57
+ _request: NextRequest,
58
+ { params }: { params: Promise<{ path: string[] }> }
59
+ ) {
60
+ try {
61
+ const { path } = await params;
62
+
63
+ // 解析路径: [bucket, ...keyParts]
64
+ if (!path || path.length < 2) {
65
+ return NextResponse.json(
66
+ { error: "Invalid path: expected /image-proxy/{bucket}/{key}" },
67
+ { status: 400 }
68
+ );
69
+ }
70
+
71
+ const [bucket, ...keyParts] = path;
72
+ const key = keyParts.join("/");
73
+
74
+ // 安全检查:验证存储桶在白名单中
75
+ const allowedBuckets = getAllowedBuckets();
76
+ if (!bucket || !allowedBuckets.includes(bucket)) {
77
+ console.warn(`[Image Proxy] 拒绝访问未授权的存储桶: ${bucket}`);
78
+ return NextResponse.json(
79
+ { error: "Bucket not allowed" },
80
+ { status: 403 }
81
+ );
82
+ }
83
+
84
+ // 验证文件键名不为空
85
+ if (!key) {
86
+ return NextResponse.json(
87
+ { error: "File key is required" },
88
+ { status: 400 }
89
+ );
90
+ }
91
+
92
+ // 生成签名 URL
93
+ const signedUrl = await storageService.createDownloadUrl({
94
+ key,
95
+ bucket,
96
+ expiresIn: CACHE_MAX_AGE,
97
+ });
98
+
99
+ // 重定向到签名 URL
100
+ // 设置缓存头,让浏览器/CDN 缓存响应
101
+ return NextResponse.redirect(signedUrl, {
102
+ status: 302,
103
+ headers: {
104
+ "Cache-Control": `public, max-age=${CACHE_MAX_AGE}, s-maxage=${CACHE_MAX_AGE}`,
105
+ // 告诉 CDN 可以缓存
106
+ "CDN-Cache-Control": `public, max-age=${CACHE_MAX_AGE}`,
107
+ },
108
+ });
109
+ } catch (error) {
110
+ console.error("[Image Proxy] 错误:", error);
111
+
112
+ // 返回错误响应
113
+ return NextResponse.json(
114
+ { error: "Failed to generate signed URL" },
115
+ { status: 500 }
116
+ );
117
+ }
118
+ }
119
+
120
+ // ============================================
121
+ // 路由配置
122
+ // ============================================
123
+
124
+ /**
125
+ * 运行时配置
126
+ *
127
+ * 使用 Edge Runtime 以获得更好的性能
128
+ * 注意:如果 S3 客户端不支持 Edge,可以切换回 nodejs
129
+ */
130
+ // export const runtime = "edge";
131
+
132
+ /**
133
+ * 动态路由配置
134
+ *
135
+ * 强制动态渲染,因为签名 URL 需要实时生成
136
+ */
137
+ export const dynamic = "force-dynamic";
@@ -0,0 +1,73 @@
1
+ import { GeistMono } from "geist/font/mono";
2
+ import { GeistSans } from "geist/font/sans";
3
+ import type { Metadata } from "next";
4
+ import { cookies } from "next/headers";
5
+
6
+ import { siteConfig } from "@/config";
7
+
8
+ import "./globals.css";
9
+
10
+ /**
11
+ * 全局元数据配置
12
+ * 使用 siteConfig 集中管理
13
+ */
14
+ export const metadata: Metadata = {
15
+ title: {
16
+ default: siteConfig.name,
17
+ template: `%s | ${siteConfig.name}`,
18
+ },
19
+ description: siteConfig.description,
20
+ keywords: [...siteConfig.keywords],
21
+ authors: [{ name: siteConfig.author.name, url: siteConfig.author.url }],
22
+ creator: siteConfig.author.name,
23
+ metadataBase: new URL(siteConfig.url),
24
+ openGraph: {
25
+ type: "website",
26
+ locale: "en_US",
27
+ url: siteConfig.url,
28
+ title: siteConfig.name,
29
+ description: siteConfig.description,
30
+ siteName: siteConfig.name,
31
+ images: [
32
+ {
33
+ url: siteConfig.ogImage,
34
+ width: 1200,
35
+ height: 630,
36
+ alt: siteConfig.name,
37
+ },
38
+ ],
39
+ },
40
+ twitter: {
41
+ card: "summary_large_image",
42
+ title: siteConfig.name,
43
+ description: siteConfig.description,
44
+ images: [siteConfig.ogImage],
45
+ creator: "@nextdevtpl",
46
+ },
47
+ manifest: "/site.webmanifest",
48
+ };
49
+
50
+ /**
51
+ * 根布局
52
+ *
53
+ * 提供基础 HTML 结构和字体配置
54
+ * 实际内容由 [locale]/layout.tsx 处理
55
+ */
56
+ export default async function RootLayout({
57
+ children,
58
+ }: Readonly<{
59
+ children: React.ReactNode;
60
+ }>) {
61
+ const cookieStore = await cookies();
62
+ const locale = cookieStore.get("NEXT_LOCALE")?.value ?? "en";
63
+
64
+ return (
65
+ <html lang={locale} suppressHydrationWarning>
66
+ <body
67
+ className={`${GeistSans.variable} ${GeistMono.variable} font-sans antialiased`}
68
+ >
69
+ {children}
70
+ </body>
71
+ </html>
72
+ );
73
+ }
@@ -0,0 +1,29 @@
1
+ import type { MetadataRoute } from "next";
2
+
3
+ import { siteConfig } from "@/config";
4
+
5
+ /**
6
+ * 动态生成 robots.txt
7
+ *
8
+ * 允许所有搜索引擎爬虫访问
9
+ * 指向 sitemap.xml
10
+ */
11
+ export default function robots(): MetadataRoute.Robots {
12
+ return {
13
+ rules: [
14
+ {
15
+ userAgent: "*",
16
+ allow: "/",
17
+ disallow: [
18
+ "/api/",
19
+ "/dashboard/",
20
+ "/sign-in",
21
+ "/sign-up",
22
+ "/forgot-password",
23
+ "/reset-password",
24
+ ],
25
+ },
26
+ ],
27
+ sitemap: `${siteConfig.url}/sitemap.xml`,
28
+ };
29
+ }