## P1 功能(20 项) - 站内消息系统、家长仪表盘、学生考勤管理 - Excel 导入导出、用户批量导入、成绩导出 - 排课规则+自动排课+课表调整 - 成绩趋势+对比分析、密码安全策略、速率限制 - 数据变更日志、文件预览+存储策略、全文检索 - 依赖审计集成 CI、数据库定时备份、E2E 测试完善 - 通知偏好管理 ## 基础设施修复 - src/proxy.ts: 将 middleware 导出重命名为 proxy(Next.js 16 要求) - .env: MySQL 端口从 13002 切换至 14013 - scripts/create-db.ts: 新增数据库初始化脚本 ## 架构文档同步 - 004_architecture_impact_map.md 和 005_architecture_data.json 完整记录所有新增表、模块、路由、权限、依赖关系
88 lines
2.4 KiB
TypeScript
88 lines
2.4 KiB
TypeScript
import { NextResponse } from "next/server"
|
|
import { unlink } from "fs/promises"
|
|
import path from "path"
|
|
|
|
import { requireAuth, requirePermission, PermissionDeniedError } from "@/shared/lib/auth-guard"
|
|
import { Permissions } from "@/shared/types/permissions"
|
|
import {
|
|
deleteFileAttachment,
|
|
getFileAttachment,
|
|
} from "@/modules/files/data-access"
|
|
|
|
export const dynamic = "force-dynamic"
|
|
|
|
interface RouteContext {
|
|
params: Promise<{ id: string }>
|
|
}
|
|
|
|
/**
|
|
* GET /api/files/[id]
|
|
* 获取文件信息(需要登录)
|
|
*/
|
|
export async function GET(_req: Request, ctx: RouteContext) {
|
|
try {
|
|
await requireAuth()
|
|
const { id } = await ctx.params
|
|
|
|
const file = await getFileAttachment(id)
|
|
if (!file) {
|
|
return NextResponse.json(
|
|
{ success: false, message: "File not found" },
|
|
{ status: 404 }
|
|
)
|
|
}
|
|
|
|
return NextResponse.json({ success: true, file })
|
|
} catch (e) {
|
|
const message = e instanceof Error ? e.message : "Failed to fetch file"
|
|
const status = message.includes("auth_required") ? 401 : 500
|
|
return NextResponse.json({ success: false, message }, { status })
|
|
}
|
|
}
|
|
|
|
/**
|
|
* DELETE /api/files/[id]
|
|
* 删除文件(需要 FILE_DELETE 权限)
|
|
*/
|
|
export async function DELETE(_req: Request, ctx: RouteContext) {
|
|
try {
|
|
await requirePermission(Permissions.FILE_DELETE)
|
|
const { id } = await ctx.params
|
|
|
|
const file = await getFileAttachment(id)
|
|
if (!file) {
|
|
return NextResponse.json(
|
|
{ success: false, message: "File not found" },
|
|
{ status: 404 }
|
|
)
|
|
}
|
|
|
|
// 删除磁盘文件(静默失败,记录仍会被删除)
|
|
const absolutePath = path.join(process.cwd(), "public", file.storagePath)
|
|
try {
|
|
await unlink(absolutePath)
|
|
} catch {
|
|
// 文件可能已不存在,忽略错误
|
|
}
|
|
|
|
const ok = await deleteFileAttachment(id)
|
|
if (!ok) {
|
|
return NextResponse.json(
|
|
{ success: false, message: "Failed to delete file record" },
|
|
{ status: 500 }
|
|
)
|
|
}
|
|
|
|
return NextResponse.json({ success: true, message: "File deleted" })
|
|
} catch (e) {
|
|
if (e instanceof PermissionDeniedError) {
|
|
return NextResponse.json(
|
|
{ success: false, message: e.message },
|
|
{ status: 403 }
|
|
)
|
|
}
|
|
const message = e instanceof Error ? e.message : "Failed to delete file"
|
|
return NextResponse.json({ success: false, message }, { status: 500 })
|
|
}
|
|
}
|