refactor(server): make the place-photo cache a provider (#1833)

The last file in be-places-helpers-fold, and the one that had to be a provider
rather than a move: it carries three in-memory structures that only do their job
if the process has exactly one of them.

  recentFailures  stops a screenful of markers hammering an API that is down
  inFlight        stops a stampede when several requests miss the same placeId
  knownOnDisk     saves an existsSync per cache hit

A second instance gives the stampede guard its own inFlight map, so two
simultaneous requests for the same uncached place both call the provider. That
is why the scheduler takes this one from the container instead of constructing
it: SchedulerDeps gains a placePhotos entry alongside backups, filled from
app.get() in index.ts — the shape PR #1827 was built for.

The module-scope mkdirSync becomes onModuleInit, and GOOGLE_PHOTO_DIR is read
per call through RuntimeEnvService rather than frozen at import. That is not
cosmetic: TREK_PLACE_PHOTO_DIR is the seam the tests use to keep off the real
uploads tree, and its own test used to have to import the module dynamically to
set the variable in time. The __dirname fallback moved a level with the file and
is corrected accordingly.

Consumers: maps, places and share inject it; places.helpers.reclaimPhotoCache
takes it as a parameter, since it is a free function.

Six test seams follow. Four path mocks become injected stubs with the same mock
functions behind them; the collections case keeps the REAL implementation,
because what it asserts is that removeIfUnreferenced counts collection_places as
a reference (#1081) — a stub there would assert nothing. The share e2e overrides
the provider in its container.

services/ is down to 14 top-level files, from 44 when this started.
This commit is contained in:
Maurice
2026-08-07 20:27:19 +02:00
committed by GitHub
parent 4e6b45016a
commit c7fed4b1f8
20 changed files with 419 additions and 309 deletions
@@ -53,10 +53,15 @@ import { DatabaseService } from '../../../src/nest/database/database.service';
import { PermissionsService } from '../../../src/nest/permissions/permissions.service';
import { RealtimeService } from '../../../src/nest/realtime/realtime.service';
import { CollectionsService } from '../../../src/nest/collections/collections.service';
import { removeIfUnreferenced } from '../../../src/services/placePhotoCache';
import { PlacePhotoCacheService } from '../../../src/nest/place-photos/place-photo-cache.service';
import { RuntimeEnvService } from '../../../src/nest/app-config/runtime-env.service';
import { PLACE_IMAGES_DIR } from '../../../src/nest/places/place-image';
const svc = new CollectionsService(new DatabaseService(testDb), new PermissionsService(new DatabaseService(testDb)), new RealtimeService());
// The real cache: these cases assert what removeIfUnreferenced actually does
// about collection_places (#1081), so a stub would assert nothing.
const photoCache = new PlacePhotoCacheService(new DatabaseService(testDb), new RuntimeEnvService());
const removeIfUnreferenced = (id: string) => photoCache.removeIfUnreferenced(id);
function clearCollections() {
testDb.exec(`