Add endpoint for deleting raw packets of decrypted messages

This commit is contained in:
Jack Kingsman
2026-02-21 00:10:29 -08:00
parent 6d0505ade6
commit 11f07f3501
6 changed files with 135 additions and 13 deletions
+9 -2
View File
@@ -183,10 +183,17 @@ export const api = {
method: 'POST',
body: JSON.stringify(params),
}),
runMaintenance: (pruneUndecryptedDays: number) =>
runMaintenance: (options: { pruneUndecryptedDays?: number; purgeLinkedRawPackets?: boolean }) =>
fetchJson<MaintenanceResult>('/packets/maintenance', {
method: 'POST',
body: JSON.stringify({ prune_undecrypted_days: pruneUndecryptedDays }),
body: JSON.stringify({
...(options.pruneUndecryptedDays !== undefined && {
prune_undecrypted_days: options.pruneUndecryptedDays,
}),
...(options.purgeLinkedRawPackets !== undefined && {
purge_linked_raw_packets: options.purgeLinkedRawPackets,
}),
}),
}),
// Read State
+42 -1
View File
@@ -145,6 +145,7 @@ export function SettingsModal(props: SettingsModalProps) {
// Database maintenance state
const [retentionDays, setRetentionDays] = useState('14');
const [cleaning, setCleaning] = useState(false);
const [purgingDecryptedRaw, setPurgingDecryptedRaw] = useState(false);
const [autoDecryptOnAdvert, setAutoDecryptOnAdvert] = useState(false);
const [reopenLastConversation, setReopenLastConversation] = useState(
getReopenLastConversationEnabled
@@ -509,7 +510,7 @@ export function SettingsModal(props: SettingsModalProps) {
setCleaning(true);
try {
const result = await api.runMaintenance(days);
const result = await api.runMaintenance({ pruneUndecryptedDays: days });
toast.success('Database cleanup complete', {
description: `Deleted ${result.packets_deleted} old packet${result.packets_deleted === 1 ? '' : 's'}`,
});
@@ -524,6 +525,25 @@ export function SettingsModal(props: SettingsModalProps) {
}
};
const handlePurgeDecryptedRawPackets = async () => {
setPurgingDecryptedRaw(true);
try {
const result = await api.runMaintenance({ purgeLinkedRawPackets: true });
toast.success('Decrypted raw packets purged', {
description: `Deleted ${result.packets_deleted} raw packet${result.packets_deleted === 1 ? '' : 's'}`,
});
await onHealthRefresh();
} catch (err) {
console.error('Failed to purge decrypted raw packets:', err);
toast.error('Failed to purge decrypted raw packets', {
description: err instanceof Error ? err.message : 'Unknown error',
});
} finally {
setPurgingDecryptedRaw(false);
}
};
const handleSaveDatabaseSettings = async () => {
setBusySection('database');
setSectionError(null);
@@ -1046,6 +1066,27 @@ export function SettingsModal(props: SettingsModalProps) {
<Separator />
<div className="space-y-3">
<Label>Purge Decrypted Raw Packets</Label>
<p className="text-xs text-muted-foreground">
Deletes raw packet bytes for messages already stored in chat history. No displayed
message data is lost. Only use this if you do not plan to run manual analytics or
parsing on original packet bytes.
</p>
<Button
variant="outline"
onClick={handlePurgeDecryptedRawPackets}
disabled={purgingDecryptedRaw}
className="w-full border-red-500/50 text-red-400 hover:bg-red-500/10"
>
{purgingDecryptedRaw
? 'Purging Decrypted Raw Packets...'
: 'Purge Decrypted Raw Packets'}
</Button>
</div>
<Separator />
<div className="space-y-3">
<Label>DM Decryption</Label>
<label className="flex items-center gap-3 cursor-pointer">
+17
View File
@@ -15,6 +15,7 @@ import {
LAST_VIEWED_CONVERSATION_KEY,
REOPEN_LAST_CONVERSATION_KEY,
} from '../utils/lastViewedConversation';
import { api } from '../api';
const baseConfig: RadioConfig = {
public_key: 'aa'.repeat(32),
@@ -321,6 +322,22 @@ describe('SettingsModal', () => {
expect(localStorage.getItem(LAST_VIEWED_CONVERSATION_KEY)).toBeNull();
});
it('purges decrypted raw packets via maintenance endpoint action', async () => {
const runMaintenanceSpy = vi.spyOn(api, 'runMaintenance').mockResolvedValue({
packets_deleted: 12,
vacuumed: true,
});
renderModal();
openDatabaseSection();
fireEvent.click(screen.getByRole('button', { name: 'Purge Decrypted Raw Packets' }));
await waitFor(() => {
expect(runMaintenanceSpy).toHaveBeenCalledWith({ purgeLinkedRawPackets: true });
});
});
it('renders statistics section with fetched data', async () => {
const mockStats: StatisticsResponse = {
busiest_channels_24h: [