mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-11-13 07:40:59 +00:00
d17457de56
Leaving every replaced cover, place image and deleted journey photo to the nightly pass kept them publicly fetchable by URL for a week at least, and for good while deletion was off. Owners now hand the values a delete or a replace left behind to StorageReleaseService once their write committed. It asks every registered reference source, with the same parser as the nightly pass, and deletes at once what no row names any more: an object a trip copy, a journey or a saved place still shows stays, which the old inline deletes got wrong. The release holds the pass's guards (reclaimable categories at their root only, never an owner marker, never while a restore runs, never with a required source missing or an overlapping location), never fails the request and logs at debug. It runs for trip covers (update, upload, trip delete), collection covers and saved-place images, place images (update, bulk update, delete), journey covers and journey or gallery photos (their thumbnail goes with a file that went), avatars (change, delete, account deletion), trip files (permanent delete, empty trash) and collab note and chat attachments. A trip file row now goes before its bytes, so a failing backend no longer fails the delete. The objects of a deleted trip's places and files are left to the nightly pass: their rows are other domains'. PlacesService drops the collection places repository nothing read any more. Size, warning and boundary baselines follow the shrunk files.
60 lines
2.6 KiB
TypeScript
60 lines
2.6 KiB
TypeScript
/**
|
|
* Stand-ins for StorageReleaseService in suites that hand-build an owner
|
|
* service. The release itself (asking every reference source, the ledger's
|
|
* guards) has its own suites: storage-release.service.test.ts and the
|
|
* integration suite over the full app.
|
|
*
|
|
* - `releaseNothing()` releases nothing and records the calls;
|
|
* - `releaseDirect(storage)` deletes every root-level object the values name,
|
|
* as a suite with no second row naming them would see the real one do.
|
|
*/
|
|
import { inReclaimScope } from '../../src/nest/storage/storage-reclaim.ledger';
|
|
import {
|
|
storageKeysIn,
|
|
type StorageKeyOptions,
|
|
type StorageKeyRef,
|
|
} from '../../src/nest/storage/storage-reclaim.types';
|
|
import type { StorageReleaseService } from '../../src/nest/storage/storage-release.service';
|
|
import type { StorageService } from '../../src/nest/storage/storage.service';
|
|
import type { ServedCategory } from '../../src/nest/storage/storage.types';
|
|
|
|
import { vi } from 'vitest';
|
|
|
|
export function releaseNothing() {
|
|
const releaseValues = vi.fn(async (): Promise<StorageKeyRef[]> => []);
|
|
const releaseIfUnreferenced = vi.fn(async (): Promise<string[]> => []);
|
|
return { releaseValues, releaseIfUnreferenced } as unknown as StorageReleaseService & {
|
|
releaseValues: typeof releaseValues;
|
|
releaseIfUnreferenced: typeof releaseIfUnreferenced;
|
|
};
|
|
}
|
|
|
|
export function releaseDirect(storage: StorageService): StorageReleaseService {
|
|
const releaseIfUnreferenced = async (category: ServedCategory, keys: readonly string[]): Promise<string[]> => {
|
|
const present = await Promise.all(
|
|
[...new Set(keys)].map(async (key) =>
|
|
inReclaimScope(key) && (await storage.stat(category, key).catch(() => null)) !== null ? key : null,
|
|
),
|
|
);
|
|
const gone = present.filter((key): key is string => key !== null);
|
|
await Promise.all(gone.map((key) => storage.delete(category, key)));
|
|
return gone;
|
|
};
|
|
const releaseValues = async (
|
|
values: ReadonlyArray<string | null | undefined>,
|
|
options: StorageKeyOptions = {},
|
|
): Promise<StorageKeyRef[]> => {
|
|
const byCategory = new Map<string, Set<string>>();
|
|
for (const ref of values.flatMap((value) => storageKeysIn(value, options))) {
|
|
byCategory.set(ref.category, (byCategory.get(ref.category) ?? new Set<string>()).add(ref.key));
|
|
}
|
|
const released = await Promise.all(
|
|
[...byCategory].map(async ([category, keys]) =>
|
|
(await releaseIfUnreferenced(category as ServedCategory, [...keys])).map((key) => ({ category, key })),
|
|
),
|
|
);
|
|
return released.flat();
|
|
};
|
|
return { releaseValues, releaseIfUnreferenced } as unknown as StorageReleaseService;
|
|
}
|