@wzyjs/cli 0.3.32 → 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.
- package/dist/index.js +350 -35
- package/package.json +6 -4
- package/template/web/base/Dockerfile +37 -0
- package/template/web/base/_env +2 -0
- package/template/web/base/eslint.config.js +94 -0
- package/template/web/base/next.config.js +29 -0
- package/template/web/base/package.json +53 -0
- package/template/web/base/postcss.config.js +5 -0
- package/template/web/base/prisma/models/demo/DemoItem.zmodel +8 -0
- package/template/web/base/prisma/schema.prisma +29 -0
- package/template/web/base/prisma/schema.zmodel +1 -0
- package/template/web/base/src/app/api/trpc/[trpc]/route.ts +24 -0
- package/template/web/base/src/app/auth/disabled/page.tsx +29 -0
- package/template/web/base/src/app/auth/error/page.tsx +47 -0
- package/template/web/base/src/app/auth/signin/page.tsx +53 -0
- package/template/web/base/src/app/auth/unauthorized/page.tsx +60 -0
- package/template/web/base/src/app/demo/page.tsx +59 -0
- package/template/web/base/src/app/layout.tsx +32 -0
- package/template/web/base/src/app/not-found.tsx +21 -0
- package/template/web/base/src/app/page.tsx +92 -0
- package/template/web/base/src/components/base/Layout/Header/context.tsx +56 -0
- package/template/web/base/src/components/base/Layout/Header/index.tsx +32 -0
- package/template/web/base/src/components/base/Layout/index.tsx +27 -0
- package/template/web/base/src/components/base/Providers/TRPCReactProvider.tsx +25 -0
- package/template/web/base/src/components/base/Providers/index.tsx +31 -0
- package/template/web/base/src/components/base/display/DisplayProvider.tsx +44 -0
- package/template/web/base/src/components/base/display/consts.ts +10 -0
- package/template/web/base/src/components/base/display/context.ts +6 -0
- package/template/web/base/src/components/base/display/index.ts +4 -0
- package/template/web/base/src/components/base/display/types.ts +12 -0
- package/template/web/base/src/components/base/display/useDisplay.ts +9 -0
- package/template/web/base/src/components/base/display/utils.ts +18 -0
- package/template/web/base/src/components/base/theme/ThemeProvider.tsx +83 -0
- package/template/web/base/src/components/base/theme/ThemeToggle.tsx +26 -0
- package/template/web/base/src/components/base/theme/consts.tsx +59 -0
- package/template/web/base/src/components/base/theme/context.ts +14 -0
- package/template/web/base/src/components/base/theme/useAppTheme.ts +15 -0
- package/template/web/base/src/components/base/theme/utils.ts +17 -0
- package/template/web/base/src/components/index.ts +1 -0
- package/template/web/base/src/consts/index.ts +6 -0
- package/template/web/base/src/enums/index.ts +1 -0
- package/template/web/base/src/env.js +44 -0
- package/template/web/base/src/hooks/index.ts +1 -0
- package/template/web/base/src/server/db/client.ts +19 -0
- package/template/web/base/src/server/routers/index.ts +3 -0
- package/template/web/base/src/server/trpc/context.ts +14 -0
- package/template/web/base/src/server/trpc/index.ts +1 -0
- package/template/web/base/src/server/trpc/init.ts +27 -0
- package/template/web/base/src/server/trpc/procedures.ts +5 -0
- package/template/web/base/src/server/trpc/router.ts +11 -0
- package/template/web/base/src/server/utils/index.ts +1 -0
- package/template/web/base/src/styles/globals.css +3 -0
- package/template/web/base/src/types/index.ts +1 -0
- package/template/web/base/src/utils/index.ts +4 -0
- package/template/web/base/src/utils/query-client/index.ts +31 -0
- package/template/web/base/src/utils/trpc/index.ts +23 -0
- package/template/web/base/src/utils/trpc/utils.ts +61 -0
- package/template/web/base/tailwind.config.cjs +7 -0
- package/template/web/base/tsconfig.json +46 -0
- package/template/web/google/_env +8 -0
- package/template/web/google/package.json +55 -0
- package/template/web/google/prisma/models/auth/Account.zmodel +26 -0
- package/template/web/google/prisma/models/auth/Session.zmodel +17 -0
- package/template/web/google/prisma/models/auth/User.zmodel +22 -0
- package/template/web/google/prisma/schema.zmodel +2 -0
- package/template/web/google/src/app/api/auth/[...nextauth]/route.ts +7 -0
- package/template/web/google/src/app/auth/error/page.tsx +56 -0
- package/template/web/google/src/app/auth/signin/page.tsx +83 -0
- package/template/web/google/src/app/layout.tsx +35 -0
- package/template/web/google/src/components/base/AuthGuard/Loading.tsx +19 -0
- package/template/web/google/src/components/base/AuthGuard/index.tsx +45 -0
- package/template/web/google/src/components/base/Layout/Header/UserAuthStatus.tsx +93 -0
- package/template/web/google/src/components/base/Layout/Header/index.tsx +34 -0
- package/template/web/google/src/components/base/Providers/index.tsx +34 -0
- package/template/web/google/src/env.js +52 -0
- package/template/web/google/src/server/auth/next-auth/adapter.ts +77 -0
- package/template/web/google/src/server/auth/next-auth/options.ts +53 -0
- package/template/web/google/src/server/trpc/context.ts +21 -0
- package/template/web/google/src/server/trpc/procedures.ts +16 -0
- package/template/web/middle/_env +10 -0
- package/template/web/middle/package.json +55 -0
- package/template/web/middle/src/app/api/auth/[...nextauth]/route.ts +7 -0
- package/template/web/middle/src/app/auth/disabled/page.tsx +71 -0
- package/template/web/middle/src/app/auth/error/page.tsx +56 -0
- package/template/web/middle/src/app/auth/signin/page.tsx +91 -0
- package/template/web/middle/src/app/auth/unauthorized/page.tsx +88 -0
- package/template/web/middle/src/app/layout.tsx +35 -0
- package/template/web/middle/src/app/middle/page.tsx +10 -0
- package/template/web/middle/src/app/page.tsx +20 -0
- package/template/web/middle/src/components/base/AuthGuard/Loading.tsx +19 -0
- package/template/web/middle/src/components/base/AuthGuard/index.tsx +45 -0
- package/template/web/middle/src/components/base/Layout/Header/UserAuthStatus.tsx +93 -0
- package/template/web/middle/src/components/base/Layout/Header/index.tsx +34 -0
- package/template/web/middle/src/components/base/Layout/Sidebar/index.tsx +88 -0
- package/template/web/middle/src/components/base/Layout/index.tsx +35 -0
- package/template/web/middle/src/components/base/Providers/MiddleProvider.tsx +33 -0
- package/template/web/middle/src/components/base/Providers/index.tsx +37 -0
- package/template/web/middle/src/env.js +57 -0
- package/template/web/middle/src/server/auth/next-auth/options.ts +26 -0
- package/template/web/middle/src/server/trpc/context.ts +22 -0
- package/template/web/middle/src/server/trpc/procedures.ts +16 -0
- package/template/web/middle/src/server/utils/index.ts +2 -0
- package/template/web/middle/src/server/utils/middle.ts +34 -0
- package/template/web/middle/src/types/index.ts +3 -0
- package/template/web/middle/src/utils/index.ts +5 -0
- 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,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,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
|
+
})
|