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 完整记录所有新增表、模块、路由、权限、依赖关系
This commit is contained in:
87
src/app/api/files/[id]/route.ts
Normal file
87
src/app/api/files/[id]/route.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
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 })
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user