This commit is contained in:
eric
2025-11-13 05:05:53 +08:00
parent cbdc65f56e
commit 891af61146
691 changed files with 83952 additions and 0 deletions

66
lib/api.ts Normal file
View File

@@ -0,0 +1,66 @@
import { NavigationItem } from '@/types/navigation'
// 删除导航
export async function deleteNavigation(id: string) {
try {
const response = await fetch(`/api/navigation/${id}`, {
method: 'DELETE'
})
if (!response.ok) {
const errorData = await response.json()
throw new Error(errorData.message || '删除导航失败')
}
return await response.json()
} catch (error) {
console.error('删除导航错误:', error)
throw error
}
}
// 更新导航
export async function updateNavigation(id: string, data: Partial<NavigationItem>) {
try {
const response = await fetch(`/api/navigation/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
})
if (!response.ok) {
const errorData = await response.json()
throw new Error(errorData.message || '更新导航失败')
}
return await response.json()
} catch (error) {
console.error('更新导航错误:', error)
throw error
}
}
// 更新导航顺序
export async function updateNavigationOrder(items: NavigationItem[]) {
try {
const response = await fetch('/api/navigation/reorder', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(items)
})
if (!response.ok) {
const errorData = await response.json()
throw new Error(errorData.message || '更新导航顺序失败')
}
return await response.json()
} catch (error) {
console.error('更新导航顺序错误:', error)
throw error
}
}

52
lib/auth.ts Normal file
View File

@@ -0,0 +1,52 @@
import NextAuth from 'next-auth'
import GithubProvider from 'next-auth/providers/github'
import type { DefaultSession, NextAuthConfig } from 'next-auth'
declare module 'next-auth' {
interface Session {
user: {
accessToken?: string
} & DefaultSession['user']
}
interface JWT {
accessToken?: string
}
interface User {
accessToken?: string
}
}
const config = {
providers: [
GithubProvider({
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_SECRET!,
authorization: {
params: { scope: 'repo' }
}
})
],
callbacks: {
async jwt({ token, account }) {
if (account?.access_token) {
token.accessToken = account.access_token
}
return token
},
async session({ session, token }) {
if (session?.user) {
session.user.accessToken = token.accessToken as string
}
return session
}
},
pages: {
signIn: '/auth/signin'
},
secret: process.env.GITHUB_SECRET
} satisfies NextAuthConfig
const handler = NextAuth(config)
export const auth = handler.auth
export const { handlers: { GET, POST } } = handler

114
lib/github.ts Normal file
View File

@@ -0,0 +1,114 @@
import { auth } from '@/lib/auth'
export async function getFileContent(path: string) {
const owner = process.env.GITHUB_OWNER!
const repo = process.env.GITHUB_REPO!
const branch = process.env.GITHUB_BRANCH || 'main'
try {
const session = await auth()
const token = session?.user?.accessToken
const apiUrl = `https://api.github.com/repos/${owner}/${repo}/contents/${path}?ref=${branch}`
const response = await fetch(apiUrl, {
headers: {
Accept: 'application/vnd.github.v3.raw',
Authorization: token ? `token ${token}` : '',
'User-Agent': 'NavSphere',
},
})
if (response.status === 404) {
console.log(`File not found: ${path}, returning default data`)
if (path.includes('navigation.json')) {
return { navigationItems: [] }
}
return {}
}
if (!response.ok) {
throw new Error(`GitHub API error: ${response.statusText}`)
}
const data = await response.json()
return data
} catch (error) {
console.error('Error fetching file:', error)
if (path.includes('navigation.json')) {
return { navigationItems: [] }
}
return {}
}
}
export async function commitFile(
path: string,
content: string,
message: string,
token: string,
retryCount = 3
) {
const owner = process.env.GITHUB_OWNER!
const repo = process.env.GITHUB_REPO!
const branch = process.env.GITHUB_BRANCH || 'main'
const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
for (let attempt = 1; attempt <= retryCount; attempt++) {
try {
// 1. 获取当前文件信息(如果存在)
const currentFileUrl = `https://api.github.com/repos/${owner}/${repo}/contents/${path}?ref=${branch}`
const currentFileResponse = await fetch(currentFileUrl, {
headers: {
Authorization: `token ${token}`,
Accept: 'application/vnd.github.v3+json',
'User-Agent': 'NavSphere',
},
cache: 'no-store', // 禁用缓存,确保获取最新的文件信息
})
let sha = undefined
if (currentFileResponse.ok) {
const currentFile = await currentFileResponse.json()
sha = currentFile.sha
}
// 2. 创建或更新文件
const updateUrl = `https://api.github.com/repos/${owner}/${repo}/contents/${path}`
const response = await fetch(updateUrl, {
method: 'PUT',
headers: {
Authorization: `token ${token}`,
Accept: 'application/vnd.github.v3+json',
'Content-Type': 'application/json',
'User-Agent': 'NavSphere',
},
body: JSON.stringify({
message,
content: Buffer.from(content).toString('base64'),
sha,
branch,
}),
})
if (!response.ok) {
const error = await response.json()
if (attempt < retryCount && error.message?.includes('sha')) {
console.log(`Attempt ${attempt} failed, retrying after delay...`)
await delay(1000 * attempt) // 指数退避
continue
}
throw new Error(`Failed to commit file: ${error.message}`)
}
return await response.json()
} catch (error) {
if (attempt === retryCount) {
console.error('Error in commitFile:', error)
throw error
}
console.log(`Attempt ${attempt} failed, retrying...`)
await delay(1000 * attempt)
}
}
}

114
lib/icons.ts Normal file
View File

@@ -0,0 +1,114 @@
import {
Folder,
FolderOpen,
Home,
Settings,
LayoutGrid,
LayoutList,
FileText,
Files,
Edit,
Book,
BookOpen,
Brain,
Network,
Box,
Mail,
Globe,
Image,
Video,
Music,
User,
Users,
Shield,
Code,
Database,
Cloud,
Building,
BarChart,
Star,
Bell,
Calendar,
Map,
Search,
Flag,
Target,
Briefcase,
Laptop,
MessageSquare,
Link,
Zap,
Heart,
Coffee,
Palette
} from 'lucide-react'
export const navigationIcons = {
// 文件夹
Folder,
FolderOpen,
// 基础导航
Home,
Settings,
// 布局
LayoutGrid,
LayoutList,
// 文件
FileText,
Files,
Edit,
// 知识与学习
Book,
BookOpen,
Brain,
// 技术
Network,
Box,
Code,
Database,
Cloud,
Laptop,
// 通信
Mail,
MessageSquare,
Link,
// 全球化
Globe,
Map,
Flag,
// 媒体
Image,
Video,
Music,
Palette,
// 用户
User,
Users,
Shield,
// 商业
Building,
Briefcase,
BarChart,
Target,
// 其他
Star,
Bell,
Calendar,
Search,
Zap,
Heart,
Coffee
}
export type IconType = keyof typeof navigationIcons

8
lib/polyfills.ts Normal file
View File

@@ -0,0 +1,8 @@
if (typeof window === 'undefined') {
global.fetch = fetch
global.Headers = Headers
global.Request = Request
global.Response = Response
}
export {}

29
lib/session.ts Normal file
View File

@@ -0,0 +1,29 @@
import { getIronSession } from 'iron-session'
import { cookies } from 'next/headers'
import type { SessionData } from '@/types/session'
if (!process.env.SESSION_SECRET) {
throw new Error('SESSION_SECRET is not defined')
}
const sessionOptions = {
password: process.env.SESSION_SECRET,
cookieName: 'navsphere-session',
cookieOptions: {
secure: process.env.NODE_ENV === 'production',
httpOnly: true,
sameSite: 'lax' as const,
},
}
export async function getSession() {
const cookieStore = await cookies()
const session = await getIronSession<SessionData>(cookieStore, sessionOptions)
// 初始化会话数据
if (!session.isLoggedIn) {
session.isLoggedIn = false
}
return session
}

7
lib/utils.ts Normal file
View File

@@ -0,0 +1,7 @@
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}