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
|
|
@ -111,15 +111,22 @@ def replace_book_data(request, pk):
|
|||
except (json.JSONDecodeError, ValueError):
|
||||
return JsonResponse({'error': 'invalid JSON'}, status=400)
|
||||
|
||||
data_ct = body.get('data_ct', '')
|
||||
data_iv = body.get('data_iv', '')
|
||||
data_ct = body.get('data_ct', '')
|
||||
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]):
|
||||
return JsonResponse({'error': 'data_ct and data_iv required'}, status=400)
|
||||
|
||||
update_fields = ['data_ct', 'data_iv']
|
||||
book.data_ct = data_ct
|
||||
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})
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -2879,11 +2879,43 @@ function repairBook(bookId) {
|
|||
try {
|
||||
const key = await getOrCreateEncKey();
|
||||
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/`, {
|
||||
method: 'POST',
|
||||
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');
|
||||
await _evictCachedBook(bookId);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue