@wzyjs/cli 0.3.31 → 0.3.36

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 (106) hide show
  1. package/dist/index.js +350 -35
  2. package/package.json +6 -4
  3. package/template/web/base/Dockerfile +37 -0
  4. package/template/web/base/_env +2 -0
  5. package/template/web/base/eslint.config.js +94 -0
  6. package/template/web/base/next.config.js +29 -0
  7. package/template/web/base/package.json +53 -0
  8. package/template/web/base/postcss.config.js +5 -0
  9. package/template/web/base/prisma/models/demo/DemoItem.zmodel +8 -0
  10. package/template/web/base/prisma/schema.prisma +29 -0
  11. package/template/web/base/prisma/schema.zmodel +1 -0
  12. package/template/web/base/src/app/api/trpc/[trpc]/route.ts +24 -0
  13. package/template/web/base/src/app/auth/disabled/page.tsx +29 -0
  14. package/template/web/base/src/app/auth/error/page.tsx +47 -0
  15. package/template/web/base/src/app/auth/signin/page.tsx +53 -0
  16. package/template/web/base/src/app/auth/unauthorized/page.tsx +60 -0
  17. package/template/web/base/src/app/demo/page.tsx +59 -0
  18. package/template/web/base/src/app/layout.tsx +32 -0
  19. package/template/web/base/src/app/not-found.tsx +21 -0
  20. package/template/web/base/src/app/page.tsx +92 -0
  21. package/template/web/base/src/components/base/Layout/Header/context.tsx +56 -0
  22. package/template/web/base/src/components/base/Layout/Header/index.tsx +32 -0
  23. package/template/web/base/src/components/base/Layout/index.tsx +27 -0
  24. package/template/web/base/src/components/base/Providers/TRPCReactProvider.tsx +25 -0
  25. package/template/web/base/src/components/base/Providers/index.tsx +31 -0
  26. package/template/web/base/src/components/base/display/DisplayProvider.tsx +44 -0
  27. package/template/web/base/src/components/base/display/consts.ts +10 -0
  28. package/template/web/base/src/components/base/display/context.ts +6 -0
  29. package/template/web/base/src/components/base/display/index.ts +4 -0
  30. package/template/web/base/src/components/base/display/types.ts +12 -0
  31. package/template/web/base/src/components/base/display/useDisplay.ts +9 -0
  32. package/template/web/base/src/components/base/display/utils.ts +18 -0
  33. package/template/web/base/src/components/base/theme/ThemeProvider.tsx +83 -0
  34. package/template/web/base/src/components/base/theme/ThemeToggle.tsx +26 -0
  35. package/template/web/base/src/components/base/theme/consts.tsx +59 -0
  36. package/template/web/base/src/components/base/theme/context.ts +14 -0
  37. package/template/web/base/src/components/base/theme/useAppTheme.ts +15 -0
  38. package/template/web/base/src/components/base/theme/utils.ts +17 -0
  39. package/template/web/base/src/components/index.ts +1 -0
  40. package/template/web/base/src/consts/index.ts +6 -0
  41. package/template/web/base/src/enums/index.ts +1 -0
  42. package/template/web/base/src/env.js +44 -0
  43. package/template/web/base/src/hooks/index.ts +1 -0
  44. package/template/web/base/src/server/db/client.ts +19 -0
  45. package/template/web/base/src/server/routers/index.ts +3 -0
  46. package/template/web/base/src/server/trpc/context.ts +14 -0
  47. package/template/web/base/src/server/trpc/index.ts +1 -0
  48. package/template/web/base/src/server/trpc/init.ts +27 -0
  49. package/template/web/base/src/server/trpc/procedures.ts +5 -0
  50. package/template/web/base/src/server/trpc/router.ts +11 -0
  51. package/template/web/base/src/server/utils/index.ts +1 -0
  52. package/template/web/base/src/styles/globals.css +3 -0
  53. package/template/web/base/src/types/index.ts +1 -0
  54. package/template/web/base/src/utils/index.ts +4 -0
  55. package/template/web/base/src/utils/query-client/index.ts +31 -0
  56. package/template/web/base/src/utils/trpc/index.ts +23 -0
  57. package/template/web/base/src/utils/trpc/utils.ts +61 -0
  58. package/template/web/base/tailwind.config.cjs +7 -0
  59. package/template/web/base/tsconfig.json +46 -0
  60. package/template/web/google/_env +8 -0
  61. package/template/web/google/package.json +55 -0
  62. package/template/web/google/prisma/models/auth/Account.zmodel +26 -0
  63. package/template/web/google/prisma/models/auth/Session.zmodel +17 -0
  64. package/template/web/google/prisma/models/auth/User.zmodel +22 -0
  65. package/template/web/google/prisma/schema.zmodel +2 -0
  66. package/template/web/google/src/app/api/auth/[...nextauth]/route.ts +7 -0
  67. package/template/web/google/src/app/auth/error/page.tsx +56 -0
  68. package/template/web/google/src/app/auth/signin/page.tsx +83 -0
  69. package/template/web/google/src/app/layout.tsx +35 -0
  70. package/template/web/google/src/components/base/AuthGuard/Loading.tsx +19 -0
  71. package/template/web/google/src/components/base/AuthGuard/index.tsx +45 -0
  72. package/template/web/google/src/components/base/Layout/Header/UserAuthStatus.tsx +93 -0
  73. package/template/web/google/src/components/base/Layout/Header/index.tsx +34 -0
  74. package/template/web/google/src/components/base/Providers/index.tsx +34 -0
  75. package/template/web/google/src/env.js +52 -0
  76. package/template/web/google/src/server/auth/next-auth/adapter.ts +77 -0
  77. package/template/web/google/src/server/auth/next-auth/options.ts +53 -0
  78. package/template/web/google/src/server/trpc/context.ts +21 -0
  79. package/template/web/google/src/server/trpc/procedures.ts +16 -0
  80. package/template/web/middle/_env +10 -0
  81. package/template/web/middle/package.json +55 -0
  82. package/template/web/middle/src/app/api/auth/[...nextauth]/route.ts +7 -0
  83. package/template/web/middle/src/app/auth/disabled/page.tsx +71 -0
  84. package/template/web/middle/src/app/auth/error/page.tsx +56 -0
  85. package/template/web/middle/src/app/auth/signin/page.tsx +91 -0
  86. package/template/web/middle/src/app/auth/unauthorized/page.tsx +88 -0
  87. package/template/web/middle/src/app/layout.tsx +35 -0
  88. package/template/web/middle/src/app/middle/page.tsx +10 -0
  89. package/template/web/middle/src/app/page.tsx +20 -0
  90. package/template/web/middle/src/components/base/AuthGuard/Loading.tsx +19 -0
  91. package/template/web/middle/src/components/base/AuthGuard/index.tsx +45 -0
  92. package/template/web/middle/src/components/base/Layout/Header/UserAuthStatus.tsx +93 -0
  93. package/template/web/middle/src/components/base/Layout/Header/index.tsx +34 -0
  94. package/template/web/middle/src/components/base/Layout/Sidebar/index.tsx +88 -0
  95. package/template/web/middle/src/components/base/Layout/index.tsx +35 -0
  96. package/template/web/middle/src/components/base/Providers/MiddleProvider.tsx +33 -0
  97. package/template/web/middle/src/components/base/Providers/index.tsx +37 -0
  98. package/template/web/middle/src/env.js +57 -0
  99. package/template/web/middle/src/server/auth/next-auth/options.ts +26 -0
  100. package/template/web/middle/src/server/trpc/context.ts +22 -0
  101. package/template/web/middle/src/server/trpc/procedures.ts +16 -0
  102. package/template/web/middle/src/server/utils/index.ts +2 -0
  103. package/template/web/middle/src/server/utils/middle.ts +34 -0
  104. package/template/web/middle/src/types/index.ts +3 -0
  105. package/template/web/middle/src/utils/index.ts +5 -0
  106. package/template/web/middle/src/utils/middle.ts +28 -0
@@ -0,0 +1,19 @@
1
+ 'use client'
2
+
3
+ import { Spin, Typography } from '@/components'
4
+
5
+ export const LayoutLoading = () => {
6
+ return (
7
+ <div className='flex min-h-screen items-center justify-center bg-slate-50 p-6 text-slate-950 transition-colors dark:bg-[#14171c] dark:text-[#e8edf5]'>
8
+ <div className='w-full max-w-md rounded-lg border border-slate-200 bg-white p-8 text-center shadow-sm dark:border-[#303846] dark:bg-[#1b2028]'>
9
+ <Spin size='large' />
10
+ <Typography.Title level={4} className='!mb-0 !mt-6 !text-xl !font-semibold !text-slate-950 dark:!text-[#e8edf5]'>
11
+ 正在加载
12
+ </Typography.Title>
13
+ <Typography.Text className='mt-3 text-sm leading-6 text-slate-500 dark:text-slate-300'>
14
+ 请稍候,正在准备页面内容。
15
+ </Typography.Text>
16
+ </div>
17
+ </div>
18
+ )
19
+ }
@@ -0,0 +1,45 @@
1
+ 'use client'
2
+
3
+ import { type ReactNode, useEffect } from 'react'
4
+ import { useSession } from 'next-auth/react'
5
+ import { usePathname, useRouter } from 'next/navigation'
6
+
7
+ import { authExemptPaths } from '@/consts'
8
+
9
+ import { LayoutLoading } from './Loading'
10
+
11
+ interface AuthGuardProps {
12
+ children: ReactNode
13
+ }
14
+
15
+ export const AuthGuard = (props: AuthGuardProps) => {
16
+ const { children } = props
17
+
18
+ const { status } = useSession()
19
+ const pathname = usePathname()
20
+ const router = useRouter()
21
+
22
+ const isAuthExemptPath = pathname ? authExemptPaths.includes(pathname) : false
23
+
24
+ useEffect(() => {
25
+ if (!pathname || status !== 'unauthenticated' || isAuthExemptPath) {
26
+ return
27
+ }
28
+
29
+ router.replace(`/auth/signin?callbackUrl=${encodeURIComponent(pathname)}`)
30
+ }, [isAuthExemptPath, pathname, router, status])
31
+
32
+ if (status === 'loading') {
33
+ return <LayoutLoading />
34
+ }
35
+
36
+ if (isAuthExemptPath) {
37
+ return children
38
+ }
39
+
40
+ if (status === 'unauthenticated') {
41
+ return null
42
+ }
43
+
44
+ return children
45
+ }
@@ -0,0 +1,93 @@
1
+ 'use client'
2
+
3
+ import { signIn, signOut, useSession } from 'next-auth/react'
4
+
5
+ import {
6
+ App,
7
+ Avatar,
8
+ Button,
9
+ Dropdown,
10
+ LoginOutlined,
11
+ LogoutOutlined,
12
+ Space,
13
+ UserOutlined,
14
+ type MenuProps,
15
+ } from '@/components'
16
+
17
+ export const UserAuthStatus = () => {
18
+ const { message } = App.useApp()
19
+ const { data: session, status } = useSession()
20
+
21
+ const onLogin = async () => {
22
+ try {
23
+ await signIn('middle')
24
+ } catch (error) {
25
+ console.error(666, '登录失败:', error)
26
+ void message.error('登录失败,请稍后重试')
27
+ }
28
+ }
29
+
30
+ const onLogout = async () => {
31
+ try {
32
+ await signOut({ callbackUrl: '/auth/signin' })
33
+ } catch (error) {
34
+ console.error(666, '退出登录失败:', error)
35
+ void message.error('退出登录失败,请稍后重试')
36
+ }
37
+ }
38
+
39
+ if (status === 'loading') {
40
+ return (
41
+ <div className='px-2 text-sm text-slate-400 dark:text-slate-500'>
42
+ 加载中...
43
+ </div>
44
+ )
45
+ }
46
+
47
+ if (status === 'unauthenticated') {
48
+ return (
49
+ <Button
50
+ type='text'
51
+ size='small'
52
+ className='flex items-center text-slate-700 dark:!text-slate-200'
53
+ icon={<LoginOutlined />}
54
+ onClick={onLogin}
55
+ >
56
+ 登录
57
+ </Button>
58
+ )
59
+ }
60
+
61
+ const items: MenuProps['items'] = [
62
+ {
63
+ key: 'profile',
64
+ label: (
65
+ <div className='max-w-[220px] p-2'>
66
+ <div className='truncate font-medium'>{session?.user?.name}</div>
67
+ <div className='truncate text-xs text-slate-500'>{session?.user?.email}</div>
68
+ </div>
69
+ ),
70
+ },
71
+ {
72
+ key: 'logout',
73
+ label: '退出登录',
74
+ icon: <LogoutOutlined />,
75
+ onClick: onLogout,
76
+ },
77
+ ]
78
+
79
+ return (
80
+ <Dropdown menu={{ items }} placement='bottomRight'>
81
+ <Space className='flex cursor-pointer items-center rounded-lg px-2 py-1 transition-colors hover:bg-slate-100 dark:hover:bg-slate-800'>
82
+ <Avatar
83
+ size='small'
84
+ src={session?.user?.image || undefined}
85
+ icon={!session?.user?.image ? <UserOutlined /> : undefined}
86
+ />
87
+ <span className='hidden max-w-[140px] overflow-hidden text-ellipsis whitespace-nowrap text-sm text-slate-700 dark:text-[#e8edf5] sm:inline'>
88
+ {session?.user?.name || session?.user?.email || '已登录用户'}
89
+ </span>
90
+ </Space>
91
+ </Dropdown>
92
+ )
93
+ }
@@ -0,0 +1,34 @@
1
+ 'use client'
2
+
3
+ import { Button, MenuOutlined } from '@/components'
4
+
5
+ import { UserAuthStatus } from './UserAuthStatus'
6
+ import { useLayoutHeader } from './context'
7
+ import { ThemeToggle } from '../../theme/ThemeToggle'
8
+
9
+ export const LayoutHeader = () => {
10
+ const { leftContent } = useLayoutHeader()
11
+
12
+ return (
13
+ <header className='flex h-[72px] shrink-0 items-center justify-between border-b border-slate-200/80 bg-white/95 px-5 backdrop-blur transition-colors dark:border-[#303846] dark:bg-[#1b2028]/95'>
14
+ <div className='min-w-0 flex-1'>
15
+ {leftContent ?? (
16
+ <div className='flex min-w-0 items-center gap-3'>
17
+ <Button
18
+ type='primary'
19
+ icon={<MenuOutlined />}
20
+ className='flex h-9 w-9 items-center justify-center rounded-lg shadow-sm'
21
+ />
22
+ <div className='truncate text-xl font-semibold tracking-normal text-slate-950 dark:text-[#e8edf5]'>
23
+ __WZYJS_APP_DISPLAY_NAME__
24
+ </div>
25
+ </div>
26
+ )}
27
+ </div>
28
+ <div className='ml-4 flex shrink-0 items-center gap-3'>
29
+ <UserAuthStatus />
30
+ <ThemeToggle />
31
+ </div>
32
+ </header>
33
+ )
34
+ }
@@ -0,0 +1,88 @@
1
+ 'use client'
2
+
3
+ import Link from 'next/link'
4
+ import { useSession } from 'next-auth/react'
5
+ import { usePathname, useSearchParams } from 'next/navigation'
6
+ import { resolveMiddleMenuPages, type MiddleResolvedPage } from '@wzyjs/middle-sdk/page'
7
+
8
+ import {
9
+ AppstoreOutlined,
10
+ LinkOutlined,
11
+ Typography,
12
+ getIconNode,
13
+ } from '@/components'
14
+
15
+ const menuItemClassName = (isActive: boolean) => [
16
+ 'flex h-10 items-center gap-2 rounded-md px-3 text-sm font-medium transition-colors',
17
+ isActive
18
+ ? 'bg-blue-50 text-blue-700 dark:bg-blue-500/15 dark:text-blue-200'
19
+ : 'text-slate-600 hover:bg-slate-100 hover:text-slate-950 dark:text-[#aab4c2] dark:hover:bg-[#202632] dark:hover:text-[#e8edf5]',
20
+ ].join(' ')
21
+
22
+ const getPageIcon = (item: MiddleResolvedPage) => (
23
+ getIconNode(item.page.icon) ?? (item.isExternal ? <LinkOutlined /> : <AppstoreOutlined />)
24
+ )
25
+
26
+ const MenuItemContent = (props: { item: MiddleResolvedPage }) => {
27
+ const { item } = props
28
+
29
+ return (
30
+ <>
31
+ <span className='text-base leading-none'>{getPageIcon(item)}</span>
32
+ <span className='truncate'>{item.page.name}</span>
33
+ </>
34
+ )
35
+ }
36
+
37
+ export const LayoutSidebar = () => {
38
+ const { data: session } = useSession()
39
+ const pathname = usePathname()
40
+ const searchParams = useSearchParams()
41
+
42
+ const menuItems = resolveMiddleMenuPages(session?.pages ?? [], {
43
+ iframePagePath: '/middle',
44
+ pathname,
45
+ iframeUrl: searchParams.get('url'),
46
+ })
47
+
48
+ return (
49
+ <aside className='hidden w-56 shrink-0 border-r border-slate-200 bg-white/90 transition-colors dark:border-[#303846] dark:bg-[#171c23] md:block'>
50
+ <nav className='p-3' aria-label='主菜单'>
51
+ <div className='flex flex-col gap-1'>
52
+ {menuItems.length ? menuItems.map(item => {
53
+ if (item.isBlank) {
54
+ return (
55
+ <a
56
+ key={item.page.path}
57
+ href={item.href}
58
+ target={item.target}
59
+ rel={item.rel}
60
+ className={menuItemClassName(false)}
61
+ >
62
+ <MenuItemContent item={item} />
63
+ </a>
64
+ )
65
+ }
66
+
67
+ return (
68
+ <Link
69
+ key={item.page.path}
70
+ href={item.href}
71
+ aria-current={item.isActive ? 'page' : undefined}
72
+ className={menuItemClassName(item.isActive)}
73
+ >
74
+ <MenuItemContent item={item} />
75
+ </Link>
76
+ )
77
+ }) : (
78
+ <div className='px-3 py-2 text-sm leading-6'>
79
+ <Typography.Text type='secondary'>
80
+ 暂未获取到中台菜单,请重新登录或检查当前账号是否已启用应用访问。
81
+ </Typography.Text>
82
+ </div>
83
+ )}
84
+ </div>
85
+ </nav>
86
+ </aside>
87
+ )
88
+ }
@@ -0,0 +1,35 @@
1
+ 'use client'
2
+
3
+ import { type ReactNode } from 'react'
4
+ import { usePathname } from 'next/navigation'
5
+
6
+ import { LayoutHeader } from './Header'
7
+ import { LayoutHeaderProvider } from './Header/context'
8
+ import { LayoutSidebar } from './Sidebar'
9
+ import { useDisplay } from '../display'
10
+
11
+ interface LayoutProps {
12
+ children: ReactNode
13
+ }
14
+
15
+ export const Layout = (props: LayoutProps) => {
16
+ const { children } = props
17
+ const { isEmbedded } = useDisplay()
18
+ const pathname = usePathname()
19
+
20
+ const shouldShowSidebar = !isEmbedded && !pathname?.startsWith('/auth/')
21
+
22
+ return (
23
+ <div className='flex h-screen flex-col overflow-hidden bg-slate-50 text-slate-950 transition-colors dark:bg-[#14171c] dark:text-[#e8edf5]'>
24
+ <LayoutHeaderProvider>
25
+ {isEmbedded ? null : <LayoutHeader />}
26
+ <div className='flex min-h-0 flex-1'>
27
+ {shouldShowSidebar ? <LayoutSidebar /> : null}
28
+ <main className='min-w-0 flex-1 overflow-auto'>
29
+ {children}
30
+ </main>
31
+ </div>
32
+ </LayoutHeaderProvider>
33
+ </div>
34
+ )
35
+ }
@@ -0,0 +1,33 @@
1
+ 'use client'
2
+
3
+ import { type ReactNode, useMemo, useState } from 'react'
4
+ import { getSession } from 'next-auth/react'
5
+ import { middle } from '@wzyjs/middle-sdk/api/react'
6
+
7
+ import { env } from '@/env'
8
+ import { getQueryClient } from '@/utils'
9
+
10
+ interface MiddleProviderProps {
11
+ children: ReactNode
12
+ }
13
+
14
+ export const MiddleProvider = (props: MiddleProviderProps) => {
15
+ const { children } = props
16
+
17
+ const [queryClient] = useState(getQueryClient)
18
+
19
+ const accessToken = useMemo(
20
+ () => async () => (await getSession())?.middleAccessToken,
21
+ [],
22
+ )
23
+
24
+ return (
25
+ <middle.Provider
26
+ accessToken={accessToken}
27
+ baseUrl={env.NEXT_PUBLIC_MIDDLE_API_URL}
28
+ queryClient={queryClient}
29
+ >
30
+ {children}
31
+ </middle.Provider>
32
+ )
33
+ }
@@ -0,0 +1,37 @@
1
+ 'use client'
2
+
3
+ import { type ReactNode } from 'react'
4
+ import { SessionProvider } from 'next-auth/react'
5
+
6
+ import { App, AntdRegistry } from '@/components'
7
+
8
+ import { TRPCReactProvider } from './TRPCReactProvider'
9
+ import { MiddleProvider } from './MiddleProvider'
10
+ import { DisplayProvider } from '../display'
11
+ import { ThemeProvider } from '../theme/ThemeProvider'
12
+
13
+ export interface ProvidersProps {
14
+ children: ReactNode
15
+ }
16
+
17
+ export const Providers = (props: ProvidersProps) => {
18
+ const { children } = props
19
+
20
+ return (
21
+ <TRPCReactProvider>
22
+ <SessionProvider>
23
+ <DisplayProvider>
24
+ <MiddleProvider>
25
+ <AntdRegistry>
26
+ <ThemeProvider>
27
+ <App>
28
+ {children}
29
+ </App>
30
+ </ThemeProvider>
31
+ </AntdRegistry>
32
+ </MiddleProvider>
33
+ </DisplayProvider>
34
+ </SessionProvider>
35
+ </TRPCReactProvider>
36
+ )
37
+ }
@@ -0,0 +1,57 @@
1
+ import { z } from 'zod'
2
+ import { createEnv } from '@t3-oss/env-nextjs'
3
+
4
+ export const env = createEnv({
5
+ /**
6
+ * 在此处指定服务器端环境变量模式。
7
+ * 这样可以确保应用程序不会使用无效的环境变量构建。
8
+ */
9
+ server: {
10
+ NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
11
+ // 数据库
12
+ DATABASE_URL: z.string().url(),
13
+ NEXTAUTH_URL: z.string().url(),
14
+ NEXTAUTH_SECRET: z.string().min(1),
15
+ MIDDLE_OIDC_CLIENT_ID: z.string().min(1),
16
+ MIDDLE_OIDC_CLIENT_SECRET: z.string().min(1),
17
+ },
18
+
19
+ /**
20
+ * 在此处指定客户端环境变量模式。
21
+ * 这样可以确保应用程序不会使用无效的环境变量构建。
22
+ * 要将它们暴露给客户端,请使用 `NEXT_PUBLIC_` 前缀。
23
+ */
24
+ client: {
25
+ NEXT_PUBLIC_MIDDLE_API_URL: z.string().url(),
26
+ NEXT_PUBLIC_MIDDLE_APPID: z.string().min(1),
27
+ },
28
+
29
+ /**
30
+ * 在这里显式传入运行时环境变量的实际值。
31
+ * `server` / `client` 负责声明校验规则,
32
+ * `runtimeEnv` 负责把 `process.env` 中对应的值提供给 `createEnv`。
33
+ */
34
+ runtimeEnv: {
35
+ NODE_ENV: process.env.NODE_ENV,
36
+ // 数据库
37
+ DATABASE_URL: process.env.DATABASE_URL,
38
+ NEXTAUTH_URL: process.env.NEXTAUTH_URL,
39
+ NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET,
40
+ NEXT_PUBLIC_MIDDLE_API_URL: process.env.NEXT_PUBLIC_MIDDLE_API_URL,
41
+ NEXT_PUBLIC_MIDDLE_APPID: process.env.NEXT_PUBLIC_MIDDLE_APPID,
42
+ MIDDLE_OIDC_CLIENT_ID: process.env.MIDDLE_OIDC_CLIENT_ID,
43
+ MIDDLE_OIDC_CLIENT_SECRET: process.env.MIDDLE_OIDC_CLIENT_SECRET,
44
+ },
45
+
46
+ /**
47
+ * 使用 `SKIP_ENV_VALIDATION` 运行 `build` 或 `dev` 可以跳过环境变量验证。
48
+ * 这在 Docker 构建时特别有用。
49
+ */
50
+ skipValidation: !!process.env.SKIP_ENV_VALIDATION,
51
+
52
+ /**
53
+ * 使空字符串被视为未定义。
54
+ * 例如:`SOME_VAR: z.string()` 和 `SOME_VAR=''` 将抛出错误。
55
+ */
56
+ emptyStringAsUndefined: true,
57
+ })
@@ -0,0 +1,26 @@
1
+ import { createMiddleNextAuthOptions, type MiddleNextAuthSessionFields } from '@wzyjs/middle-sdk/login'
2
+
3
+ import { env } from '@/env'
4
+
5
+ declare module 'next-auth' {
6
+ interface Session extends MiddleNextAuthSessionFields {
7
+ user: {
8
+ id: string
9
+ name?: string | null
10
+ email?: string | null
11
+ image?: string | null
12
+ }
13
+ }
14
+ }
15
+
16
+ export const auth = createMiddleNextAuthOptions({
17
+ cookiePrefix: '__WZYJS_APP_PACKAGE_NAME__',
18
+ clientId: env.MIDDLE_OIDC_CLIENT_ID,
19
+ clientSecret: env.MIDDLE_OIDC_CLIENT_SECRET,
20
+ secret: env.NEXTAUTH_SECRET,
21
+ issuer: env.NEXT_PUBLIC_MIDDLE_API_URL,
22
+ pages: {
23
+ signIn: '/auth/signin',
24
+ error: '/auth/error',
25
+ },
26
+ })
@@ -0,0 +1,22 @@
1
+ import { getServerSession } from 'next-auth'
2
+
3
+ import { auth } from '@/server/auth/next-auth/options'
4
+ import { db } from '@/server/db/client'
5
+
6
+ import { enhance } from '../generated/enhancer/enhance'
7
+
8
+ export const createTRPCContext = async (opts?: { req?: Request }) => {
9
+ const session = await getServerSession(auth)
10
+
11
+ return {
12
+ // db, 严禁使用这个模块,而应该使用 prisma
13
+ session,
14
+ req: opts?.req,
15
+ headers: opts?.req?.headers,
16
+ prisma: enhance(db, {
17
+ user: session?.user?.id ? { id: session.user.id } : undefined,
18
+ }),
19
+ }
20
+ }
21
+
22
+ export type TRPCContext = Awaited<ReturnType<typeof createTRPCContext>>
@@ -0,0 +1,16 @@
1
+ import { TRPCError } from '@trpc/server'
2
+
3
+ import { createTRPCProcedure } from './init'
4
+
5
+ export const publicProcedure = createTRPCProcedure
6
+
7
+ export const procedure = createTRPCProcedure.use(({ ctx, next }) => {
8
+ if (!ctx.session?.user?.id) {
9
+ throw new TRPCError({
10
+ code: 'UNAUTHORIZED',
11
+ message: '请先登录',
12
+ })
13
+ }
14
+
15
+ return next()
16
+ })
@@ -0,0 +1,2 @@
1
+ export * from '@wzyjs/utils/node'
2
+ export * from './middle'
@@ -0,0 +1,34 @@
1
+ import 'server-only'
2
+
3
+ import { createMiddleApi } from '@wzyjs/middle-sdk/api/node'
4
+ import { createMiddleProxyUrlFactory } from '@wzyjs/middle-sdk/proxy'
5
+ import { createMiddleUploadFileFactory } from '@wzyjs/middle-sdk/upload'
6
+ import { getServerSession } from 'next-auth'
7
+
8
+ import { env } from '@/env'
9
+ import { auth } from '@/server/auth/next-auth/options'
10
+
11
+ const getMiddleAccessToken = async () => {
12
+ const session = await getServerSession(auth)
13
+ const accessToken = session?.middleAccessToken
14
+
15
+ if (!accessToken) {
16
+ throw new Error('暂未获取到中台登录态,请重新登录后再试')
17
+ }
18
+ return accessToken
19
+ }
20
+
21
+ export const middle = createMiddleApi({
22
+ baseUrl: env.NEXT_PUBLIC_MIDDLE_API_URL,
23
+ accessToken: getMiddleAccessToken,
24
+ })
25
+
26
+ export const createMiddleProxyUrl = createMiddleProxyUrlFactory({
27
+ appid: env.NEXT_PUBLIC_MIDDLE_APPID,
28
+ baseUrl: env.NEXT_PUBLIC_MIDDLE_API_URL,
29
+ })
30
+
31
+ export const uploadMiddleFile = createMiddleUploadFileFactory({
32
+ accessToken: getMiddleAccessToken,
33
+ baseUrl: env.NEXT_PUBLIC_MIDDLE_API_URL,
34
+ })
@@ -0,0 +1,3 @@
1
+ export type { MiddleAppInfo } from '@wzyjs/middle-sdk/login'
2
+ export type { MiddlePage } from '@wzyjs/middle-sdk/page'
3
+ export type { ApiRouterInput, ApiRouterOutput } from '@/server/trpc'
@@ -0,0 +1,5 @@
1
+ export * from '@wzyjs/utils/web'
2
+
3
+ export { getQueryClient } from './query-client'
4
+ export { api, createApiClient } from './trpc'
5
+ export * from './middle'
@@ -0,0 +1,28 @@
1
+ import { createMiddleProxyUrlFactory } from '@wzyjs/middle-sdk/proxy'
2
+ import { createMiddleUploadFileFactory } from '@wzyjs/middle-sdk/upload'
3
+ import { getSession } from 'next-auth/react'
4
+
5
+ import { env } from '@/env'
6
+
7
+ export { middle } from '@wzyjs/middle-sdk/api/react'
8
+
9
+ const getMiddleAccessToken = async () => {
10
+ const session = await getSession()
11
+ const accessToken = session?.middleAccessToken
12
+
13
+ if (!accessToken) {
14
+ throw new Error('暂未获取到中台登录态,请重新登录后再试')
15
+ }
16
+
17
+ return accessToken
18
+ }
19
+
20
+ export const createMiddleProxyUrl = createMiddleProxyUrlFactory({
21
+ appid: env.NEXT_PUBLIC_MIDDLE_APPID,
22
+ baseUrl: env.NEXT_PUBLIC_MIDDLE_API_URL,
23
+ })
24
+
25
+ export const uploadMiddleFile = createMiddleUploadFileFactory({
26
+ accessToken: getMiddleAccessToken,
27
+ baseUrl: env.NEXT_PUBLIC_MIDDLE_API_URL,
28
+ })