feat: delete remote images from Lsky when record/attachment is deleted (keys stored at upload, legacy URLs resolved via pathname)
This commit is contained in:
@@ -92,6 +92,8 @@ CREATE TABLE IF NOT EXISTS attachment (
|
|||||||
thumb_rel_path TEXT,
|
thumb_rel_path TEXT,
|
||||||
remote_url TEXT,
|
remote_url TEXT,
|
||||||
remote_thumb TEXT,
|
remote_thumb TEXT,
|
||||||
|
remote_key TEXT,
|
||||||
|
remote_thumb_key TEXT,
|
||||||
taken_at TEXT,
|
taken_at TEXT,
|
||||||
deleted_at TEXT,
|
deleted_at TEXT,
|
||||||
origin TEXT,
|
origin TEXT,
|
||||||
|
|||||||
+3
-3
@@ -125,7 +125,7 @@ function preMigrateColumns(d) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
for (const t of SYNC_TABLES) addCols(t, SYNC_COLS);
|
for (const t of SYNC_TABLES) addCols(t, SYNC_COLS);
|
||||||
addCols('attachment', [['sha256', 'TEXT'], ['thumb_rel_path', 'TEXT'], ['remote_url', 'TEXT'], ['remote_thumb', 'TEXT']]);
|
addCols('attachment', [['sha256', 'TEXT'], ['thumb_rel_path', 'TEXT'], ['remote_url', 'TEXT'], ['remote_thumb', 'TEXT'], ['remote_key', 'TEXT'], ['remote_thumb_key', 'TEXT']]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function migrateSync(d) {
|
export function migrateSync(d) {
|
||||||
@@ -1084,8 +1084,8 @@ function listFootprints(d, query) {
|
|||||||
if (!row) throw new Error('附件不存在');
|
if (!row) throw new Error('附件不存在');
|
||||||
withTx(d, () => {
|
withTx(d, () => {
|
||||||
const h = hlcNext(d);
|
const h = hlcNext(d);
|
||||||
d.prepare('UPDATE attachment SET remote_url=COALESCE(?, remote_url), remote_thumb=COALESCE(?, remote_thumb), hlc=? WHERE id=?')
|
d.prepare('UPDATE attachment SET remote_url=COALESCE(?, remote_url), remote_thumb=COALESCE(?, remote_thumb), remote_key=COALESCE(?, remote_key), remote_thumb_key=COALESCE(?, remote_thumb_key), hlc=? WHERE id=?')
|
||||||
.run(payload.remote_url ?? null, payload.remote_thumb ?? null, h, row.id);
|
.run(payload.remote_url ?? null, payload.remote_thumb ?? null, payload.remote_key ?? null, payload.remote_thumb_key ?? null, h, row.id);
|
||||||
touchOplog(d, 'attachment', row.uuid, 'upsert', h);
|
touchOplog(d, 'attachment', row.uuid, 'upsert', h);
|
||||||
});
|
});
|
||||||
return { ok: true };
|
return { ok: true };
|
||||||
|
|||||||
@@ -27,6 +27,53 @@ async function lskyUpload(buf, fileName, mime) {
|
|||||||
if (!j || j.status !== true || !url) throw new Error('LSKY ' + r.status + ': ' + (j?.message || 'no url'));
|
if (!j || j.status !== true || !url) throw new Error('LSKY ' + r.status + ': ' + (j?.message || 'no url'));
|
||||||
return { url, thumb_url: sanitizeImageUrl(j?.data?.thumbnail_url) || undefined, key: j.data.key };
|
return { url, thumb_url: sanitizeImageUrl(j?.data?.thumbnail_url) || undefined, key: j.data.key };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function lskyDelete(key) {
|
||||||
|
const r = await fetch(LSKY_API + '/images/' + encodeURIComponent(key), {
|
||||||
|
method: 'DELETE', headers: { authorization: 'Bearer ' + LSKY_TOKEN, accept: 'application/json' },
|
||||||
|
signal: AbortSignal.timeout(30_000),
|
||||||
|
});
|
||||||
|
const j = await r.json().catch(() => null);
|
||||||
|
if (!j || j.status !== true) throw new Error('LSKY delete ' + r.status + ': ' + (j?.message || ''));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 历史 URL 反查 key:按 pathname 在图床相册里匹配(最多翻 5 页)
|
||||||
|
async function lskyFindKeyByUrl(url) {
|
||||||
|
try {
|
||||||
|
const pathname = new URL(url).pathname.replace(/^\/i\//, '');
|
||||||
|
for (let page = 1; page <= 5; page++) {
|
||||||
|
const r = await fetch(LSKY_API + '/images?per_page=100&page=' + page, {
|
||||||
|
headers: { authorization: 'Bearer ' + LSKY_TOKEN, accept: 'application/json' },
|
||||||
|
signal: AbortSignal.timeout(30_000),
|
||||||
|
});
|
||||||
|
const j = await r.json().catch(() => null);
|
||||||
|
const items = j?.data?.data || [];
|
||||||
|
const hit = items.find(x => x.pathname === pathname);
|
||||||
|
if (hit) return hit.key;
|
||||||
|
if (page >= (j?.data?.last_page || 1)) break;
|
||||||
|
}
|
||||||
|
} catch (e) { console.log('[lsky] 反查 key 失败:', e.message); }
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除联动:记录/附件删除后清理图床(后台异步)
|
||||||
|
function lskyCleanupRemote(items) {
|
||||||
|
if (!LSKY_API || !LSKY_TOKEN) return;
|
||||||
|
(async () => {
|
||||||
|
for (const it of items) {
|
||||||
|
try {
|
||||||
|
let k = it.remote_key;
|
||||||
|
if (!k && it.remote_url) k = await lskyFindKeyByUrl(it.remote_url);
|
||||||
|
if (k) await lskyDelete(k);
|
||||||
|
let tk = it.remote_thumb_key;
|
||||||
|
if (!tk && it.remote_thumb) tk = await lskyFindKeyByUrl(it.remote_thumb);
|
||||||
|
if (tk) await lskyDelete(tk);
|
||||||
|
if (k || tk) console.log('[lsky] 图床已清理:', it.file_name || it.id);
|
||||||
|
} catch (e) { console.log('[lsky] 图床清理失败:', it.file_name || it.id, e.message); }
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import { getDb } from './db.mjs';
|
import { getDb } from './db.mjs';
|
||||||
@@ -207,7 +254,7 @@ const server = http.createServer(async (req, res) => {
|
|||||||
const tUp = await lskyUpload(Buffer.from(td.data_b64, 'base64'), 'thumb-' + row.file_name + '.webp', td.mime || 'image/webp');
|
const tUp = await lskyUpload(Buffer.from(td.data_b64, 'base64'), 'thumb-' + row.file_name + '.webp', td.mime || 'image/webp');
|
||||||
thumbUrl = tUp.thumb_url || tUp.url;
|
thumbUrl = tUp.thumb_url || tUp.url;
|
||||||
}
|
}
|
||||||
cmds.handleCommand('attachment_set_remote', { id: row.id, remote_url: up.url, remote_thumb: thumbUrl });
|
cmds.handleCommand('attachment_set_remote', { id: row.id, remote_url: up.url, remote_thumb: thumbUrl, remote_key: up.key, remote_thumb_key: thumbKey });
|
||||||
done++;
|
done++;
|
||||||
} catch (e) { failed++; console.log('[lsky-migrate]', row.id, e.message); }
|
} catch (e) { failed++; console.log('[lsky-migrate]', row.id, e.message); }
|
||||||
}
|
}
|
||||||
@@ -296,12 +343,13 @@ const server = http.createServer(async (req, res) => {
|
|||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
const up = await lskyUpload(buf, name, mime);
|
const up = await lskyUpload(buf, name, mime);
|
||||||
let thumbUrl = null;
|
let thumbUrl = null, thumbKey = null;
|
||||||
if (thumbB64) {
|
if (thumbB64) {
|
||||||
const tUp = await lskyUpload(Buffer.from(thumbB64, 'base64'), 'thumb-' + name + '.webp', 'image/webp');
|
const tUp = await lskyUpload(Buffer.from(thumbB64, 'base64'), 'thumb-' + name + '.webp', 'image/webp');
|
||||||
thumbUrl = tUp.thumb_url || tUp.url;
|
thumbUrl = tUp.thumb_url || tUp.url;
|
||||||
|
thumbKey = tUp.key;
|
||||||
}
|
}
|
||||||
cmds.handleCommand('attachment_set_remote', { id: itemId, remote_url: up.url, remote_thumb: thumbUrl });
|
cmds.handleCommand('attachment_set_remote', { id: itemId, remote_url: up.url, remote_thumb: thumbUrl, remote_key: up.key, remote_thumb_key: thumbKey });
|
||||||
console.log('[lsky] 附件 #' + itemId + ' 已上图床');
|
console.log('[lsky] 附件 #' + itemId + ' 已上图床');
|
||||||
} catch (e) { console.log('[lsky] 附件 #' + itemId + ' 后台上传失败(可用 sync-lsky 补传):', e.message); }
|
} catch (e) { console.log('[lsky] 附件 #' + itemId + ' 后台上传失败(可用 sync-lsky 补传):', e.message); }
|
||||||
})();
|
})();
|
||||||
@@ -309,6 +357,26 @@ const server = http.createServer(async (req, res) => {
|
|||||||
return send(200, true, data);
|
return send(200, true, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 附件删除:本地清理 + 图床异步清理
|
||||||
|
if (req.method === 'POST' && route === '/api/attachment_delete') {
|
||||||
|
const body = await readJson(req);
|
||||||
|
const cmds = commandsFor(auth.user_id);
|
||||||
|
const row = cmds.getDb().prepare('SELECT id, file_name, remote_url, remote_thumb, remote_key, remote_thumb_key FROM attachment WHERE id=?').get(Number(body.id));
|
||||||
|
const data = cmds.handleCommand('attachment_delete', body);
|
||||||
|
if (row && (row.remote_key || row.remote_url || row.remote_thumb)) lskyCleanupRemote([row]);
|
||||||
|
return send(200, true, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录删除:级联清理附件 + 图床
|
||||||
|
if (req.method === 'POST' && route === '/api/footprint_delete') {
|
||||||
|
const body = await readJson(req);
|
||||||
|
const cmds = commandsFor(auth.user_id);
|
||||||
|
const atts = cmds.getDb().prepare("SELECT id, file_name, remote_url, remote_thumb, remote_key, remote_thumb_key FROM attachment WHERE owner_type='footprint' AND owner_id=? AND deleted_at IS NULL AND (remote_url IS NOT NULL OR remote_key IS NOT NULL)").all(Number(body.id));
|
||||||
|
const data = cmds.handleCommand('footprint_delete', body);
|
||||||
|
if (atts.length) lskyCleanupRemote(atts);
|
||||||
|
return send(200, true, data);
|
||||||
|
}
|
||||||
|
|
||||||
// 附件直传图床(兼容旧 base64 通道):同样异步
|
// 附件直传图床(兼容旧 base64 通道):同样异步
|
||||||
if (req.method === 'POST' && route === '/api/attachment_add') {
|
if (req.method === 'POST' && route === '/api/attachment_add') {
|
||||||
const body = await readJson(req);
|
const body = await readJson(req);
|
||||||
@@ -319,13 +387,13 @@ const server = http.createServer(async (req, res) => {
|
|||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
const up = await lskyUpload(orig, body.file_name || ('file-' + itemId), body.mime);
|
const up = await lskyUpload(orig, body.file_name || ('file-' + itemId), body.mime);
|
||||||
let thumbUrl = null;
|
let thumbUrl = null, thumbKey = null;
|
||||||
if (body.thumb_b64) {
|
if (body.thumb_b64) {
|
||||||
const tUp = await lskyUpload(Buffer.from(body.thumb_b64, 'base64'), 'thumb-' + (body.file_name || 'img') + '.webp', 'image/webp');
|
const tUp = await lskyUpload(Buffer.from(body.thumb_b64, 'base64'), 'thumb-' + (body.file_name || 'img') + '.webp', 'image/webp');
|
||||||
thumbUrl = tUp.thumb_url || tUp.url;
|
thumbUrl = tUp.thumb_url || tUp.url;
|
||||||
|
thumbKey = tUp.key;
|
||||||
}
|
}
|
||||||
cmds = null;
|
commandsFor(auth.user_id).handleCommand('attachment_set_remote', { id: itemId, remote_url: up.url, remote_thumb: thumbUrl, remote_key: up.key, remote_thumb_key: thumbKey });
|
||||||
commandsFor(auth.user_id).handleCommand('attachment_set_remote', { id: itemId, remote_url: up.url, remote_thumb: thumbUrl });
|
|
||||||
console.log('[lsky] 附件 #' + itemId + ' 已上图床');
|
console.log('[lsky] 附件 #' + itemId + ' 已上图床');
|
||||||
} catch (e) { console.log('[lsky] 附件 #' + itemId + ' 后台上传失败:', e.message); }
|
} catch (e) { console.log('[lsky] 附件 #' + itemId + ' 后台上传失败:', e.message); }
|
||||||
})();
|
})();
|
||||||
|
|||||||
Reference in New Issue
Block a user