diff --git a/.env b/.env index a427653..8144ca0 100644 --- a/.env +++ b/.env @@ -6,5 +6,7 @@ CRATES_PRO_HOST=http://210.28.134.203:31688 CRATES_PRO_INTERNAL_HOST=http://210.28.134.203:31688 -SECRET_KEY=$YOUR_SECRET_KEY #(not prefixed with NEXT_PUBLIC_ ) +# SECRET_KEY=$YOUR_SECRET_KEY #(not prefixed with NEXT_PUBLIC_ ) + +AUTH_HOST=true \ No newline at end of file diff --git a/app/api/auth/[...nextauth]/route.ts b/app/api/auth/[...nextauth]/route.ts index 370cf94..b698f8c 100644 --- a/app/api/auth/[...nextauth]/route.ts +++ b/app/api/auth/[...nextauth]/route.ts @@ -1,2 +1,67 @@ -import { handlers } from "@/app/auth" // Referring to the auth.ts we just created -export const { GET, POST } = handlers \ No newline at end of file +import { handlers } from "@/app/auth" +import { NextResponse } from 'next/server' +import { NextRequest } from 'next/server' +import { getProxyConfig } from "@/proxy-config" + +const TIMEOUT_DURATION = 60000; + +// 定义超时 Promise 的类型 +type TimeoutPromise = Promise + +export async function GET(request: NextRequest): Promise { + try { + const { isEnabled } = getProxyConfig(); + + if (isEnabled) { + // 使用超时机制 + const timeoutPromise: TimeoutPromise = new Promise((_, reject) => + setTimeout(() => reject(new Error('Request timeout')), TIMEOUT_DURATION) + ); + const response = await Promise.race([handlers.GET(request), timeoutPromise]); + return response instanceof Response ? response : NextResponse.json(response); + } else { + // 不使用超时机制 + const response = await handlers.GET(request); + return response instanceof Response ? response : NextResponse.json(response); + } + } catch (error) { + console.error('Auth GET Error:', error); + return NextResponse.json( + { + error: 'Authentication error', + details: error instanceof Error ? error.message : 'Unknown error', + timestamp: new Date().toISOString() + }, + { status: 500 } + ); + } +} + +export async function POST(request: NextRequest): Promise { + try { + const { isEnabled } = getProxyConfig(); + + if (isEnabled) { + // 使用超时机制 + const timeoutPromise: TimeoutPromise = new Promise((_, reject) => + setTimeout(() => reject(new Error('Request timeout')), TIMEOUT_DURATION) + ); + const response = await Promise.race([handlers.POST(request), timeoutPromise]); + return response instanceof Response ? response : NextResponse.json(response); + } else { + // 不使用超时机制 + const response = await handlers.POST(request); + return response instanceof Response ? response : NextResponse.json(response); + } + } catch (error) { + console.error('Auth POST Error:', error); + return NextResponse.json( + { + error: 'Authentication error', + details: error instanceof Error ? error.message : 'Unknown error', + timestamp: new Date().toISOString() + }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/app/api/profile/route.ts b/app/api/profile/route.ts new file mode 100644 index 0000000..bc37ce4 --- /dev/null +++ b/app/api/profile/route.ts @@ -0,0 +1,31 @@ +import { NextResponse } from 'next/server'; + +export async function POST(request: Request) { + const endpoint = process.env.CRATES_PRO_INTERNAL_HOST; + const apiUrl = `${endpoint}/api/profile`; + const requestBody = await request.json(); + console.log("Request Body:", requestBody); + + try { + const response = await fetch(apiUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', // 确保发送 JSON + }, + body: JSON.stringify({ + requestBody + }), // 发送请求体 + }); + console.log("response in get profile info:", response); + if (!response.ok) { + return NextResponse.json({ error: 'Failed to submit data' }, { status: response.status }); + } + + const result = await response.json(); // 确认返回的是 JSON 格式 + console.log("results:", result); + return NextResponse.json({ message: 'Submission successful', code: 2012, crates: result }); + } catch (error) { + console.log(error); + return NextResponse.json({ error: 'An error occurred while submitting data.' }, { status: 500 }); + } +} \ No newline at end of file diff --git a/app/api/submit/route.ts b/app/api/submitCrate/route.ts similarity index 88% rename from app/api/submit/route.ts rename to app/api/submitCrate/route.ts index f160b1c..a3bb2e7 100644 --- a/app/api/submit/route.ts +++ b/app/api/submitCrate/route.ts @@ -1,7 +1,8 @@ import { NextResponse } from 'next/server'; export async function POST(request: Request) { - const apiUrl = process.env.API_URL; // 读取环境变量 + const endpoint = process.env.CRATES_PRO_INTERNAL_HOST; + const apiUrl = `${endpoint}/api/submitCrate`; const formData = await request.formData(); // 解析请求体 console.log("Request FormData:", formData); diff --git a/app/api/submitUserinfo/route.ts b/app/api/submitUserinfo/route.ts new file mode 100644 index 0000000..6bca83b --- /dev/null +++ b/app/api/submitUserinfo/route.ts @@ -0,0 +1,35 @@ +import { NextResponse } from 'next/server'; + + +export async function POST(request: Request) { + const endpoint = process.env.CRATES_PRO_INTERNAL_HOST; + const apiUrl = `${endpoint}/api/submitUserinfo`; + const requestBody = await request.json(); + + + + console.log("Request Body new!:", requestBody); + try { + const response = await fetch(apiUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', // 确保发送 JSON + 'Accept': 'application/json' + }, + body: JSON.stringify({ + requestBody + }), // 发送 name 字段 + }); + console.log("response:", response); + if (!response.ok) { + return NextResponse.json({ error: 'Failed to submit data' }, { status: response.status }); + } + + const result = await response.json(); // 确认返回的是 JSON 格式 + console.log("results:", result); + return NextResponse.json({ message: 'Submission successful', code: 2012, data: result }); + } catch (error) { + console.log(error); + return NextResponse.json({ error: 'An error occurred while submitting data.' }, { status: 500 }); + } +} \ No newline at end of file diff --git a/app/auth.ts b/app/auth.ts index 2ff2c1a..a47a549 100644 --- a/app/auth.ts +++ b/app/auth.ts @@ -1,7 +1,65 @@ - import NextAuth from "next-auth" import GitHub from "next-auth/providers/github" +import { setupGlobalFetch } from "@/proxy-config" + +// 设置全局代理(如果启用) +setupGlobalFetch(); + +// 打印环境变量(开发环境调试用) +console.log('Environment Config:', { + NEXTAUTH_URL: process.env.NEXTAUTH_URL, + PROXY_ENABLED: !!process.env.HTTPS_PROXY, + PROXY_URL: process.env.HTTPS_PROXY || 'Not configured' +}); + +if (!process.env.AUTH_GITHUB_ID || !process.env.AUTH_GITHUB_SECRET) { + throw new Error('Missing GITHUB_ID or GITHUB_SECRET environment variable') +} export const { handlers, signIn, signOut, auth } = NextAuth({ - providers: [GitHub], -}) \ No newline at end of file + providers: [ + GitHub({ + clientId: process.env.AUTH_GITHUB_ID, + clientSecret: process.env.AUTH_GITHUB_SECRET, + authorization: { + params: { + prompt: "consent", + access_type: "offline", + response_type: "code" + } + } + }) + ], + trustHost: true, + debug: true, + callbacks: { + async signIn({ user, account, profile, email, credentials }) { + console.log('Sign in attempt:', { user, account, profile, email, credentials }); + + return true; + }, + async redirect({ url, baseUrl }) { + console.log('Redirect:', { url, baseUrl }); + return baseUrl; + }, + async session({ session, user, token }) { + console.log('Session:', { session, user, token }); + return session; + }, + async jwt({ token, user, account, profile }) { + console.log('JWT:', { token, user, account, profile }); + return token; + } + }, + events: { + async signIn(message) { console.log('signIn:', message) }, + async signOut(message) { console.log('signOut:', message) }, + }, + pages: { + signIn: '/auth/signin', + error: '/auth/error', + } +}) + + + diff --git a/app/lib/all_interface.ts b/app/lib/all_interface.ts index cd7dac4..32ed1af 100644 --- a/app/lib/all_interface.ts +++ b/app/lib/all_interface.ts @@ -106,4 +106,49 @@ export interface cveListInfo { "end_version": string, }, ] -} \ No newline at end of file +} + +//六、用户github信息接口 +export interface userinfo { + user: { + "email": string, + "image": string, + "name": string, + } + expires: string +} + + +//用户个人主页接口 +export interface profile { + upload_crates: + [ + { + "name": string, + "time": string, + }, + { + "name": string, + "time": string, + }, + ] + +} + +export interface profileResult { + + "data": [ + { + "name": string, + "time": string, + }, + { + "name": string, + "time": string, + }, + ] + + +} + + diff --git a/app/profile/layout.tsx b/app/profile/[username]/layout.tsx similarity index 100% rename from app/profile/layout.tsx rename to app/profile/[username]/layout.tsx diff --git a/app/profile/[username]/page.tsx b/app/profile/[username]/page.tsx new file mode 100644 index 0000000..9c1ffa7 --- /dev/null +++ b/app/profile/[username]/page.tsx @@ -0,0 +1,387 @@ +'use client'; + +import { useSession } from 'next-auth/react'; +import Image from 'next/image'; +import Link from 'next/link'; +import { Tabs } from 'antd'; +import { useState, useEffect } from 'react'; +import { PaperAirplaneIcon } from '@heroicons/react/24/outline'; + +import clsx from 'clsx'; + + +interface Crates { + name: string; + time: string; +} + +export default function ProfilePage() { + const { data: session } = useSession(); + const [activeTab, setActiveTab] = useState('1'); + // const [results, setResults] = useState(null); + const [uploadCrates, setUploadCrates] = useState([]); + + const [isModalOpen, setModalOpen] = useState(false); + const [inputValue, setInputValue] = useState(''); + const [file, setFile] = useState(null); + const [isGithubLink, setIsGithubLink] = useState(true); // 控制输入类型 + //暂定上传数据类型为react表单类型 + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + const formData = new FormData(); + const currentTime = new Date().toISOString(); + const email = session?.user?.email || ''; // 提供默认空字符串 + + if (isGithubLink) { + formData.append('githubLink', inputValue); + } else if (file) { + formData.append('file', file); + } + + formData.append('uploadTime', currentTime); + formData.append('user_email', email); // 现在 email 一定是字符串类型 + try { + //用fetch向服务器发声POST请求,提交用户输入的内容 + const response = await fetch('/api/submitCrate', { // 待替换为服务器API + method: 'POST', + //请求体,将对象转换为json字符串 + body: formData, + }); + //响应处理,根据响应结果显示提示信息,并重置输入框或关闭弹窗 + if (response.ok) { + alert('内容提交成功!');//提交成功后重置输入框的值,并关闭弹窗 + setInputValue(''); + setFile(null); + setModalOpen(false); + } else { + alert('提交失败,请重试。'); + } + } catch (error) { + console.error('提交错误:', error); + alert('提交失败,请检查网络连接。'); + } + }; + + useEffect(() => { + const fetchProfileInfo = async () => { + console.log('session in useEffect:', session); + if (!session) return; + console.log('!!!!!!!!!!!!!!!!'); + try { + const response = await fetch(`/api/profile`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(session.user?.email), + }); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const rep = await response.json(); + + console.log('rep :', rep); + // setResults(rep); + setUploadCrates(rep.crates); + + + } catch (error) { + console.error('Error fetching data:', error); + } + }; + + fetchProfileInfo(); + }, [session]); + + if (!session) { + return ( +
+
+

Please login first

+
+
+ ); + } + console.log('uploadCrates :', uploadCrates); + return ( +
+ {/* Top bar: Return Home button on the left, Profile Info on the right */} +
+
+ {/* Left: Return Home Button */} +
+ + + +
+ + + {isModalOpen && ( +
+
+

Submit Crates

+ {/* 表单元素,包裹输入控件和提交按钮 */} +
{/* 将表单的提交事件绑定到handleSubmit函数,处理用户提交逻辑 */} +
+ + +
+ + {isGithubLink ? ( + setInputValue(e.target.value)} + className="w-full p-2 border rounded-md" + placeholder="Enter GitHub URL..." + required + /> + ) : ( +
+ {/**/} + { + const selectedFile = e.target.files?.[0]; // 使用可选链操作符检查 files 是否为 null + if (selectedFile) { + setFile(selectedFile); + } else { + setFile(null); // 如果没有文件选择,清空文件状态 + } + }} + className="w-full p-2 border rounded-md" + required + /> +
+ )} + +
+ + +
+
+
+
+ )} + + {/* Right: Profile Info */} +
+
+ Profile +
+
+

{session.user?.name}

+

{session.user?.email}

+ +
+
+
+
+ + {/* Main Content Area (Tabs) */} +
+ , + }, + { + key: '2', + label: 'My Favorites', + children: , + }, + { + key: '3', + label: 'Uploads', + children: , + }, + { + key: '4', + label: 'Analysis History', + children: , + }, + ]} + /> +
+
+ ); +} + +// Overview Tab component +function OverviewTab() { + return ( +
+ {/* Activity Stats */} +
+

Activity Stats

+
+
+ Analysis Count + 123 +
+
+ Favorites Count + 45 +
+
+ Crate Uploads + 12 +
+
+
+ + {/* Recent Activity */} +
+

Recent Activity

+
+
+
+

Analyzed tokio crate

+

2 hours ago

+
+
+
+
+

Favorited serde crate

+

1 day ago

+
+
+
+
+
+ ); +} + +// My Favorites Tab component +function FavoritesTab() { + return ( +
+
+

My Favorites

+ {/* Favorites list */} +
+ {/* Sample favorite item */} +
+
+
+

serde

+

+ A generic serialization/deserialization framework +

+
+ +
+
+ {/* More favorite items... */} +
+
+
+ ); +} + +// Uploads Tab component +function UploadsTab({ uploadCrates }: { uploadCrates: Crates[] }) { + return ( +
+
+

Uploads

+
+ {uploadCrates.map((crate: Crates, index: number) => ( +
+
+
+

{crate.name}

+

Upload Time: {crate.time}

+
+ +
+
+ ))} +
+
+
+ ); +} + +// Analysis History Tab component +function AnalysisHistoryTab() { + return ( +
+
+

Analysis History

+ {/* Analysis history list */} +
+ {/* Sample analysis item */} +
+
+
+

tokio (v1.36.0)

+

Analysis Time: 2024-03-21

+
+ +
+
+ {/* More analysis items... */} +
+
+
+ ); +} \ No newline at end of file diff --git a/app/profile/page.tsx b/app/profile/page.tsx deleted file mode 100644 index 8efb3c3..0000000 --- a/app/profile/page.tsx +++ /dev/null @@ -1,237 +0,0 @@ -'use client'; - -import { useSession } from 'next-auth/react'; -import Image from 'next/image'; -import Link from 'next/link'; -import { Tabs } from 'antd'; -import { useState, useEffect } from 'react'; - -export default function ProfilePage() { - const { data: session } = useSession(); - const [activeTab, setActiveTab] = useState('1'); - - useEffect(() => { - if (session) { - // 提交 session 到后端 - fetch('http://210.28.134.203:31688', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(session), - }) - .then(response => response.json()) - .then(data => { - console.log('成功提交 session:', data); - }) - .catch(error => { - console.error('提交 session 失败:', error); - }); - } - }, [session]); // 依赖于 session - - if (!session) { - return ( -
-
-

Please login first

-
-
- ); - } - console.log('session in profile page !!!!!!!!', session); - return ( -
- {/* Top bar: Return Home button on the left, Profile Info on the right */} -
-
- {/* Left: Return Home Button */} -
- - - -
- {/* Right: Profile Info */} -
-
- Profile -
-
-

{session.user?.name}

-

{session.user?.email}

- -
-
-
-
- - {/* Main Content Area (Tabs) */} -
- , - }, - { - key: '2', - label: 'My Favorites', - children: , - }, - { - key: '3', - label: 'Uploads', - children: , - }, - { - key: '4', - label: 'Analysis History', - children: , - }, - ]} - /> -
-
- ); -} - -// Overview Tab component -function OverviewTab() { - return ( -
- {/* Activity Stats */} -
-

Activity Stats

-
-
- Analysis Count - 123 -
-
- Favorites Count - 45 -
-
- Crate Uploads - 12 -
-
-
- - {/* Recent Activity */} -
-

Recent Activity

-
-
-
-

Analyzed tokio crate

-

2 hours ago

-
-
-
-
-

Favorited serde crate

-

1 day ago

-
-
-
-
-
- ); -} - -// My Favorites Tab component -function FavoritesTab() { - return ( -
-
-

My Favorites

- {/* Favorites list */} -
- {/* Sample favorite item */} -
-
-
-

serde

-

- A generic serialization/deserialization framework -

-
- -
-
- {/* More favorite items... */} -
-
-
- ); -} - -// Uploads Tab component -function UploadsTab() { - return ( -
-
-

Uploads

- {/* Uploads list */} -
- {/* Sample upload item */} -
-
-
-

my-crate

-

Upload Time: 2024-03-20

-
- -
-
- {/* More upload items... */} -
-
-
- ); -} - -// Analysis History Tab component -function AnalysisHistoryTab() { - return ( -
-
-

Analysis History

- {/* Analysis history list */} -
- {/* Sample analysis item */} -
-
-
-

tokio (v1.36.0)

-

Analysis Time: 2024-03-21

-
- -
-
- {/* More analysis items... */} -
-
-
- ); -} \ No newline at end of file diff --git a/auth.config.ts b/auth.config.ts new file mode 100644 index 0000000..98e1a34 --- /dev/null +++ b/auth.config.ts @@ -0,0 +1,9 @@ +import type { NextAuthConfig } from 'next-auth'; + +export const authConfig = { + pages: { + signIn: '/signin', + }, + trustHost: true, + providers: [], +} satisfies NextAuthConfig; \ No newline at end of file diff --git a/components/sign-in.tsx b/components/sign-in.tsx index b24c6ac..b04e578 100644 --- a/components/sign-in.tsx +++ b/components/sign-in.tsx @@ -1,5 +1,6 @@ 'use client'; +import { useEffect } from 'react'; import { signIn, signOut, useSession } from 'next-auth/react'; import Image from 'next/image'; import Link from 'next/link'; @@ -9,6 +10,28 @@ import type { MenuProps } from 'antd'; export default function SignInButton() { const { data: session, status } = useSession(); + useEffect(() => { + if (session) { + // 提交 session 到后端 + fetch('api/submitUserinfo', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + session + }), + }) + .then(response => response.json()) + .then(data => { + console.log('成功提交 session:', data); + }) + .catch(error => { + console.error('提交 session 失败:', error); + }); + } + }, [session]); // 依赖于 session + if (status === 'loading') { return (