feat(ext/sw): vault helpers for attachment add/remove
addAttachmentToItem appends an AttachmentRef + re-syncs the manifest entry's attachment_summaries. removeAttachmentsFromItem returns the removed refs so the caller can deleteBlob() the underlying bytes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
import type { SessionHandle } from '../../wasm/relicario_wasm';
|
import type { SessionHandle } from '../../wasm/relicario_wasm';
|
||||||
import type { GitHost } from './git-host';
|
import type { GitHost } from './git-host';
|
||||||
import type { Item, ItemId, Manifest, ManifestEntry, VaultSettings } from '../shared/types';
|
import type { AttachmentRef, Item, ItemId, Manifest, ManifestEntry, VaultSettings } from '../shared/types';
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
let wasm: any = null;
|
let wasm: any = null;
|
||||||
@@ -140,3 +140,83 @@ export function findByHostname(
|
|||||||
.filter(([, e]) => e.trashed_at === undefined)
|
.filter(([, e]) => e.trashed_at === undefined)
|
||||||
.filter(([, e]) => (e.icon_hint ?? '').toLowerCase() === h);
|
.filter(([, e]) => (e.icon_hint ?? '').toLowerCase() === h);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Attachment helpers ---
|
||||||
|
|
||||||
|
/// Sync the manifest entry's attachment_summaries for a single item.
|
||||||
|
/// Reads the current manifest + item; if either is missing, does nothing.
|
||||||
|
async function syncManifestAttachments(
|
||||||
|
git: GitHost,
|
||||||
|
handle: SessionHandle,
|
||||||
|
itemId: ItemId,
|
||||||
|
item: Item,
|
||||||
|
message: string,
|
||||||
|
): Promise<void> {
|
||||||
|
const manifest = await fetchAndDecryptManifest(git, handle);
|
||||||
|
const entry = manifest.items[itemId];
|
||||||
|
if (!entry) return;
|
||||||
|
entry.attachment_summaries = item.attachments.map((a) => ({
|
||||||
|
id: a.id,
|
||||||
|
filename: a.filename,
|
||||||
|
mime_type: a.mime_type,
|
||||||
|
size: a.size,
|
||||||
|
}));
|
||||||
|
await encryptAndWriteManifest(git, handle, manifest, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add an AttachmentRef to an existing item.
|
||||||
|
/// Reads + decrypts the item, appends to attachments[], re-encrypts and
|
||||||
|
/// writes both items/<id>.enc and the manifest entry's attachment_summaries.
|
||||||
|
/// Throws if the item is not found.
|
||||||
|
export async function addAttachmentToItem(
|
||||||
|
git: GitHost,
|
||||||
|
handle: SessionHandle,
|
||||||
|
itemId: ItemId,
|
||||||
|
ref: AttachmentRef,
|
||||||
|
): Promise<void> {
|
||||||
|
let item: Item;
|
||||||
|
try {
|
||||||
|
item = await fetchAndDecryptItem(git, handle, itemId);
|
||||||
|
} catch {
|
||||||
|
throw new Error(`addAttachmentToItem: item ${itemId} not found`);
|
||||||
|
}
|
||||||
|
item.attachments = [...item.attachments, ref];
|
||||||
|
item.modified = Math.floor(Date.now() / 1000);
|
||||||
|
const commitMsg = `attach ${ref.filename} to item ${itemId}`;
|
||||||
|
await encryptAndWriteItem(git, handle, itemId, item, commitMsg);
|
||||||
|
await syncManifestAttachments(git, handle, itemId, item, `manifest: sync attachments for ${itemId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove attachments matching the given IDs from the item.
|
||||||
|
/// Returns the removed AttachmentRefs (so the caller can deleteBlob the
|
||||||
|
/// underlying bytes). IDs not present in the item's attachments are
|
||||||
|
/// silently ignored.
|
||||||
|
export async function removeAttachmentsFromItem(
|
||||||
|
git: GitHost,
|
||||||
|
handle: SessionHandle,
|
||||||
|
itemId: ItemId,
|
||||||
|
idsToRemove: string[],
|
||||||
|
): Promise<AttachmentRef[]> {
|
||||||
|
let item: Item;
|
||||||
|
try {
|
||||||
|
item = await fetchAndDecryptItem(git, handle, itemId);
|
||||||
|
} catch {
|
||||||
|
throw new Error(`removeAttachmentsFromItem: item ${itemId} not found`);
|
||||||
|
}
|
||||||
|
const removeSet = new Set(idsToRemove);
|
||||||
|
const removed: AttachmentRef[] = [];
|
||||||
|
const kept: AttachmentRef[] = [];
|
||||||
|
for (const att of item.attachments) {
|
||||||
|
if (removeSet.has(att.id)) {
|
||||||
|
removed.push(att);
|
||||||
|
} else {
|
||||||
|
kept.push(att);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item.attachments = kept;
|
||||||
|
item.modified = Math.floor(Date.now() / 1000);
|
||||||
|
const commitMsg = `remove ${removed.length} attachment(s) from item ${itemId}`;
|
||||||
|
await encryptAndWriteItem(git, handle, itemId, item, commitMsg);
|
||||||
|
await syncManifestAttachments(git, handle, itemId, item, `manifest: sync attachments for ${itemId}`);
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user