@ -1,11 +1,22 @@
import fs from "node:fs/promises" ;
import path from "node:path" ;
import { dbGlobal } from "drizzle-pkg/lib/db" ;
import { dbGlobal } from "drizzle-pkg/lib/db" ;
import { userExportTasks } from "drizzle-pkg/lib/schema/export" ;
import { userExportTasks } from "drizzle-pkg/lib/schema/export" ;
import { and , desc , eq , or } from "drizzle-orm" ;
import { and , desc , eq , or } from "drizzle-orm" ;
import { createError } from "h3" ;
import { nextIntegerId } from "../../utils/sqlite-id" ;
import { nextIntegerId } from "../../utils/sqlite-id" ;
type ExportMaskPolicy = "masked" | "raw" ;
type ExportMaskPolicy = "masked" | "raw" ;
function exportRootDir ( ) : string {
return path . resolve ( process . cwd ( ) , ".tmp" , "exports" ) ;
}
function isPathUnderExportRoot ( dir : string ) : boolean {
const root = exportRootDir ( ) ;
const resolved = path . resolve ( dir ) ;
return resolved === root || resolved . startsWith ( ` ${ root } ${ path . sep } ` ) ;
}
async function getExportTaskById ( taskId : number ) {
async function getExportTaskById ( taskId : number ) {
const [ row ] = await dbGlobal
const [ row ] = await dbGlobal
. select ( )
. select ( )
@ -35,7 +46,7 @@ export async function createExportTask(params: { userId: number; maskPolicy: Exp
)
)
. limit ( 1 ) ;
. limit ( 1 ) ;
if ( activeTask ) {
if ( activeTask ) {
throw createError ( { statusCode : 409 , statusMessage : "已有导出任务在处理中,请稍后再试" } ) ;
throw { statusCode : 409 , statusMessage : "已有导出任务在处理中,请稍后再试" } ;
}
}
const id = await nextIntegerId ( userExportTasks , userExportTasks . id ) ;
const id = await nextIntegerId ( userExportTasks , userExportTasks . id ) ;
await dbGlobal . insert ( userExportTasks ) . values ( {
await dbGlobal . insert ( userExportTasks ) . values ( {
@ -145,6 +156,18 @@ export async function markExportTaskFailed(
return row ;
return row ;
}
}
export async function markExportTaskExpired ( taskId : number , message : string ) {
await dbGlobal
. update ( userExportTasks )
. set ( {
status : "expired" ,
errorCode : "EXPORT_EXPIRED" ,
errorMessage : message ,
} )
. where ( eq ( userExportTasks . id , taskId ) ) ;
return getRequiredExportTaskById ( taskId ) ;
}
export async function getExportTaskForUser ( taskId : number , userId : number ) {
export async function getExportTaskForUser ( taskId : number , userId : number ) {
const [ row ] = await dbGlobal
const [ row ] = await dbGlobal
. select ( )
. select ( )
@ -153,3 +176,25 @@ export async function getExportTaskForUser(taskId: number, userId: number) {
. limit ( 1 ) ;
. limit ( 1 ) ;
return row ? ? null ;
return row ? ? null ;
}
}
export async function deleteExportTaskForUser ( taskId : number , userId : number ) {
const task = await getExportTaskForUser ( taskId , userId ) ;
if ( ! task ) {
throw { statusCode : 404 , statusMessage : "导出任务不存在" } ;
}
if ( task . status === "running" ) {
throw { statusCode : 409 , statusMessage : "任务处理中,暂不可删除" } ;
}
if ( task . outputDir && isPathUnderExportRoot ( task . outputDir ) ) {
try {
await fs . rm ( task . outputDir , { recursive : true , force : true } ) ;
} catch {
// ignore fs cleanup failures to keep deletion resilient
}
}
await dbGlobal
. delete ( userExportTasks )
. where ( and ( eq ( userExportTasks . id , taskId ) , eq ( userExportTasks . userId , userId ) ) ) ;
}