File size: 4,519 Bytes
c7d34c1
 
96bdf6c
 
 
3e8ea5d
c7d34c1
 
 
 
 
 
 
 
 
 
 
96bdf6c
 
 
c7d34c1
 
 
 
 
 
 
 
3e8ea5d
c7d34c1
 
 
 
32151b6
 
c7d34c1
 
32151b6
 
c7d34c1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96bdf6c
c7d34c1
 
 
 
 
 
 
 
96bdf6c
 
 
 
 
 
c7d34c1
 
 
 
 
 
e4e0afe
c7d34c1
 
 
 
 
96bdf6c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e4e0afe
96bdf6c
 
 
 
 
e4e0afe
96bdf6c
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
import { appLogger } from '@/lib/app-logger';
import { isValidImageFilename } from '@/lib/image-request-utils';
import { resolveImageOutputDir, verifyPasswordHash } from '@/lib/server-runtime';
import { withWebuiImageFilenameLock } from '@/lib/webui-image-retention-lock';
import { getWebuiImageRetentionStore } from '@/lib/webui-image-retention-store';
import fs from 'fs/promises';
import { NextRequest, NextResponse } from 'next/server';
import path from 'path';

type DeleteRequestBody = {
    filenames: string[];
    passwordHash?: string;
};

type FileDeletionResult = {
    filename: string;
    success: boolean;
    fileDeleted?: boolean;
    fileAbsent?: boolean;
    markerRemoved?: boolean;
    error?: string;
};

export async function POST(request: NextRequest) {
    let requestBody: DeleteRequestBody;
    try {
        requestBody = await request.json();

        const appPassword = process.env.APP_PASSWORD?.trim();
        if (appPassword) {
            const clientPasswordHash = requestBody.passwordHash;

            if (!clientPasswordHash) {
                appLogger.error('删除操作缺少访问码哈希。');
                return NextResponse.json({ error: '未授权:缺少访问码哈希。' }, { status: 401 });
            }
            if (!verifyPasswordHash(clientPasswordHash, appPassword)) {
                appLogger.error('删除操作的访问码哈希无效。');
                return NextResponse.json({ error: '未授权:访问码无效。' }, { status: 401 });
            }
        }
    } catch (e) {
        appLogger.error('解析 /api/image-delete 请求正文失败:', e);
        return NextResponse.json({ error: '请求正文无效:必须是 JSON。' }, { status: 400 });
    }

    const { filenames } = requestBody;

    if (!Array.isArray(filenames) || filenames.some((fn) => typeof fn !== 'string')) {
        return NextResponse.json({ error: 'filenames 无效:必须是字符串数组。' }, { status: 400 });
    }

    if (filenames.length === 0) {
        return NextResponse.json({ message: '未提供要删除的文件名。', results: [] }, { status: 200 });
    }

    const outputDir = resolveImageOutputDir();
    const deletionResults: FileDeletionResult[] = [];

    for (const filename of filenames) {
        if (!isValidImageFilename(filename)) {
            appLogger.warn(`删除操作收到无效文件名:${filename}`);
            deletionResults.push({ filename, success: false, error: '文件名格式无效。' });
            continue;
        }
        deletionResults.push(
            await withWebuiImageFilenameLock(
                filename,
                async () => await deleteImageAndRetentionMarker(filename, outputDir)
            )
        );
    }

    const allSucceeded = deletionResults.every((r) => r.success);

    return NextResponse.json(
        {
            message: allSucceeded ? '所有文件已删除。' : '部分文件未能完整删除或清理自动清理保护。',
            results: deletionResults
        },
        { status: allSucceeded ? 200 : 207 } // 部分失败时返回 207 Multi-Status。
    );
}

async function deleteImageAndRetentionMarker(filename: string, outputDir: string): Promise<FileDeletionResult> {
    const filepath = path.join(outputDir, filename);
    let fileDeleted = false;

    try {
        await fs.unlink(filepath);
        fileDeleted = true;
    } catch (error) {
        if (!isMissingFileError(error)) {
            appLogger.error(`删除图片失败 ${filepath}:`, error);
            return { filename, success: false, error: '删除文件失败。' };
        }
    }

    try {
        await (await getWebuiImageRetentionStore()).remove([filename]);
    } catch (error) {
        appLogger.error('删除图片后清理自动清理保护失败。', error);
        return {
            filename,
            success: false,
            ...(fileDeleted ? { fileDeleted: true } : { fileAbsent: true }),
            markerRemoved: false,
            error: fileDeleted ? '图片已删除,但自动清理保护未能清理。' : '图片已不存在,但自动清理保护未能清理。'
        };
    }

    if (fileDeleted) return { filename, success: true };
    return { filename, success: false, fileAbsent: true, markerRemoved: true, error: '文件不存在。' };
}

function isMissingFileError(error: unknown): boolean {
    return typeof error === 'object' && error !== null && 'code' in error && error.code === 'ENOENT';
}