Add Premium 2.0 purchase repair workflow

This commit is contained in:
thePR0M3TH3AN committed 2026-07-04 15:39:31 -04:00
1 parent 8226e9a9ae
commit d04a24d2e2
2 files changed
+72

No files matched your search

+59
View File
@@ -8389,6 +8389,48 @@
return { attempted: legacyItems.length, migrated: migratedItems.length, failed };
}
async function repairPremiumPurchases({ silent = false } = {}) {
if (!_supportsVault() || !state.community?.myPubkey) {
return { status: 'unavailable', pending: null, migrated: null, checked: 0, ok: 0, missing: 0, unrecoverable: 0 };
}
const pending = await retryPendingVaultWrites({ silent: true });
const migrated = await migrateLegacyVaultPrivateCopies({ silent: true });
const vault = await loadPurchaseVault({ force: true });
let checked = 0;
let ok = 0;
let missing = 0;
let unrecoverable = 0;
for (const item of vault?.items || []) {
if (item.privateCopy) {
checked++;
try {
await _loadPurchaseCopyPayload(item.privateCopy);
ok++;
} catch (err) {
missing++;
if (!item.payload) unrecoverable++;
console.warn('[vault] repair private-copy validation failed', item.privateCopy, err);
}
} else if (item.payload) {
missing++;
} else {
unrecoverable++;
}
}
const status = pending.failed || migrated.failed || unrecoverable ? 'needs_attention' : missing ? 'repairable' : 'clean';
const result = { status, pending, migrated, checked, ok, missing, unrecoverable };
if (!silent) {
if (status === 'clean') {
toastSuccess('Purchase repair found everything clean ✓');
} else if (status === 'repairable') {
toast('Purchase repair found legacy purchases that can still be saved as private copies.');
} else {
toastError(`Purchase repair needs attention: ${unrecoverable} unrecoverable, ${pending.failed + migrated.failed} failed retry/migration.`);
}
}
return result;
}
function _buildVaultItemForUnlock({ row, totalSats, creatorSats, platformSats, creatorPreimage, platformPreimage, payload }) {
return {
a: row.premium?.address || '',
@@ -9349,6 +9391,23 @@
});
actions.appendChild(migrateBtn);
}
const repairBtn = document.createElement('button');
repairBtn.type = 'button';
repairBtn.textContent = 'Repair purchases';
repairBtn.title = 'Check private copies, retry pending saves, and migrate recoverable legacy purchases.';
repairBtn.addEventListener('click', async () => {
repairBtn.disabled = true;
repairBtn.textContent = 'Repairing…';
try {
const result = await repairPremiumPurchases({ silent: false });
await loadPurchasedDesignsIntoWall(wall, { force: true });
console.info('[vault] repair result', result);
} finally {
repairBtn.disabled = false;
repairBtn.textContent = 'Repair purchases';
}
});
actions.appendChild(repairBtn);
const refreshBtn = document.createElement('button');
refreshBtn.type = 'button';
refreshBtn.textContent = '↻ Refresh';
@@ -89,6 +89,19 @@ test('index page migrates legacy inline vault purchases to private copies', asyn
assert.match(html, /await migrateLegacyVaultPrivateCopies\(\{ silent: false \}\)/);
});
test('index page exposes Premium 2.0 purchase repair workflow', async () => {
const html = await indexHtml();
assert.match(html, /async function repairPremiumPurchases/);
assert.match(html, /const pending = await retryPendingVaultWrites\(\{ silent: true \}\)/);
assert.match(html, /const migrated = await migrateLegacyVaultPrivateCopies\(\{ silent: true \}\)/);
assert.match(html, /await _loadPurchaseCopyPayload\(item\.privateCopy\)/);
assert.match(html, /status = pending\.failed \|\| migrated\.failed \|\| unrecoverable \? 'needs_attention' : missing \? 'repairable' : 'clean'/);
assert.match(html, /repairBtn\.textContent = 'Repair purchases'/);
assert.match(html, /const result = await repairPremiumPurchases\(\{ silent: false \}\)/);
assert.match(html, /Purchase repair found everything clean/);
});
test('vault item stubs preserve Premium 2.0 private-copy fields', async () => {
const html = await indexHtml();
const stubStart = html.indexOf('function _vaultItemStub(item)');