Files
NextEdu/src/app/api/files/[id]/route.ts
SpecialX 3b6272c99d feat: 完成 P1 全部功能 + 修复 proxy 导出 + 切换 MySQL 端口至 14013
## 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
  完整记录所有新增表、模块、路由、权限、依赖关系
2026-06-17 13:44:37 +08:00

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 })
}
}