Books: repair flow also re-encrypts metadata from file
replace-data endpoint now optionally accepts meta_ct/meta_iv. The client extracts title/author from the uploaded file (same as normal upload), re-encrypts metadata with the current key, and sends both data and meta together — so broken books are fully restored including their displayed title and author. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
4b47f6e67a
commit
fe91000e7c
2 changed files with 44 additions and 5 deletions
|
|
@ -113,13 +113,20 @@ def replace_book_data(request, pk):
|
||||||
|
|
||||||
data_ct = body.get('data_ct', '')
|
data_ct = body.get('data_ct', '')
|
||||||
data_iv = body.get('data_iv', '')
|
data_iv = body.get('data_iv', '')
|
||||||
|
meta_ct = body.get('meta_ct', '')
|
||||||
|
meta_iv = body.get('meta_iv', '')
|
||||||
|
|
||||||
if not all([data_ct, data_iv]):
|
if not all([data_ct, data_iv]):
|
||||||
return JsonResponse({'error': 'data_ct and data_iv required'}, status=400)
|
return JsonResponse({'error': 'data_ct and data_iv required'}, status=400)
|
||||||
|
|
||||||
|
update_fields = ['data_ct', 'data_iv']
|
||||||
book.data_ct = data_ct
|
book.data_ct = data_ct
|
||||||
book.data_iv = data_iv
|
book.data_iv = data_iv
|
||||||
book.save(update_fields=['data_ct', 'data_iv'])
|
if meta_ct and meta_iv:
|
||||||
|
book.meta_ct = meta_ct
|
||||||
|
book.meta_iv = meta_iv
|
||||||
|
update_fields += ['meta_ct', 'meta_iv']
|
||||||
|
book.save(update_fields=update_fields)
|
||||||
return JsonResponse({'ok': True})
|
return JsonResponse({'ok': True})
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2879,11 +2879,43 @@ function repairBook(bookId) {
|
||||||
try {
|
try {
|
||||||
const key = await getOrCreateEncKey();
|
const key = await getOrCreateEncKey();
|
||||||
const buf = await file.arrayBuffer();
|
const buf = await file.arrayBuffer();
|
||||||
const enc = await encryptBytes(key, buf);
|
const isPdf = /\.pdf$/i.test(file.name);
|
||||||
|
const type = isPdf ? 'pdf' : 'epub';
|
||||||
|
|
||||||
|
let title = file.name.replace(/\.(epub|pdf)$/i, ''), author = '';
|
||||||
|
try {
|
||||||
|
if (isPdf) {
|
||||||
|
const pdfDoc = await pdfjsLib.getDocument({data: new Uint8Array(buf.slice(0))}).promise;
|
||||||
|
const meta = await pdfDoc.getMetadata();
|
||||||
|
title = meta.info?.Title?.trim() || title;
|
||||||
|
author = meta.info?.Author?.trim() || '';
|
||||||
|
} else {
|
||||||
|
const zip = await JSZip.loadAsync(buf.slice(0));
|
||||||
|
const containerXml = await zip.file('META-INF/container.xml').async('text');
|
||||||
|
const opfPath = new DOMParser()
|
||||||
|
.parseFromString(containerXml, 'application/xml')
|
||||||
|
.querySelector('rootfile')?.getAttribute('full-path');
|
||||||
|
if (opfPath) {
|
||||||
|
const opfDoc = new DOMParser().parseFromString(
|
||||||
|
await zip.file(opfPath).async('text'), 'application/xml');
|
||||||
|
title = opfDoc.querySelector('metadata > title, metadata > *|title')?.textContent?.trim() || title;
|
||||||
|
author = opfDoc.querySelector('metadata > creator, metadata > *|creator')?.textContent?.trim() || '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) { /* keep filename as title */ }
|
||||||
|
|
||||||
|
const metaJson = new TextEncoder().encode(JSON.stringify({title, author, filename: file.name, type}));
|
||||||
|
const [metaEnc, dataEnc] = await Promise.all([
|
||||||
|
encryptBytes(key, metaJson),
|
||||||
|
encryptBytes(key, buf),
|
||||||
|
]);
|
||||||
const res = await fetch(`/books/${bookId}/replace-data/`, {
|
const res = await fetch(`/books/${bookId}/replace-data/`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {'Content-Type': 'application/json', 'X-CSRFToken': getCsrfToken()},
|
headers: {'Content-Type': 'application/json', 'X-CSRFToken': getCsrfToken()},
|
||||||
body: JSON.stringify({data_ct: enc.ciphertext, data_iv: enc.iv}),
|
body: JSON.stringify({
|
||||||
|
data_ct: dataEnc.ciphertext, data_iv: dataEnc.iv,
|
||||||
|
meta_ct: metaEnc.ciphertext, meta_iv: metaEnc.iv,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
if (!(await res.json()).ok) throw new Error('Server error');
|
if (!(await res.json()).ok) throw new Error('Server error');
|
||||||
await _evictCachedBook(bookId);
|
await _evictCachedBook(bookId);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue