's'
This commit is contained in:
66
lib/api.ts
Normal file
66
lib/api.ts
Normal 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
52
lib/auth.ts
Normal 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
114
lib/github.ts
Normal 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
114
lib/icons.ts
Normal 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
8
lib/polyfills.ts
Normal 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
29
lib/session.ts
Normal 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
7
lib/utils.ts
Normal 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))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user