diff --git a/admin/app/controllers/maps_controller.ts b/admin/app/controllers/maps_controller.ts index 3ce6e18..451271f 100644 --- a/admin/app/controllers/maps_controller.ts +++ b/admin/app/controllers/maps_controller.ts @@ -13,7 +13,7 @@ export default class MapsController { constructor(private mapService: MapService) {} async index({ inertia }: HttpContext) { - const baseAssetsCheck = await this.mapService.checkBaseAssetsExist() + const baseAssetsCheck = await this.mapService.ensureBaseAssets() const regionFiles = await this.mapService.listRegions() return inertia.render('maps', { maps: { @@ -23,11 +23,6 @@ export default class MapsController { }) } - async checkBaseAssets({}: HttpContext) { - const exists = await this.mapService.checkBaseAssetsExist() - return { exists } - } - async downloadBaseAssets({ request }: HttpContext) { const payload = await request.validateUsing(remoteDownloadValidatorOptional) await this.mapService.downloadBaseAssets(payload.url) @@ -75,6 +70,15 @@ export default class MapsController { } async styles({ response }: HttpContext) { + // Automatically ensure base assets are present before generating styles + const baseAssetsExist = await this.mapService.ensureBaseAssets() + if (!baseAssetsExist) { + return response.status(500).send({ + message: + 'Base map assets are missing and could not be downloaded. Please check your connection and try again.', + }) + } + const styles = await this.mapService.generateStylesJSON() return response.json(styles) } diff --git a/admin/app/controllers/settings_controller.ts b/admin/app/controllers/settings_controller.ts index 3defcc0..ea734b6 100644 --- a/admin/app/controllers/settings_controller.ts +++ b/admin/app/controllers/settings_controller.ts @@ -37,7 +37,7 @@ export default class SettingsController { } async maps({ inertia }: HttpContext) { - const baseAssetsCheck = await this.mapService.checkBaseAssetsExist(); + const baseAssetsCheck = await this.mapService.ensureBaseAssets(); const regionFiles = await this.mapService.listRegions(); return inertia.render('settings/maps', { maps: { diff --git a/admin/app/services/map_service.ts b/admin/app/services/map_service.ts index 1e0a8e6..54e9420 100644 --- a/admin/app/services/map_service.ts +++ b/admin/app/services/map_service.ts @@ -9,7 +9,6 @@ import { extract } from 'tar' import env from '#start/env' import { listDirectoryContentsRecursive, - listDirectoryContents, getFileStatsIfExists, deleteFileIfExists, getFile, @@ -50,6 +49,7 @@ export class MapService implements IMapService { private readonly basemapsAssetsDir = 'basemaps-assets' private readonly baseAssetsTarFile = 'base-assets.tar.gz' private readonly baseDirPath = join(process.cwd(), this.mapStoragePath) + private baseAssetsExistCache: boolean | null = null async listRegions() { const files = (await this.listAllMapStorageItems()).filter( @@ -93,6 +93,9 @@ export class MapService implements IMapService { await deleteFileIfExists(tempTarPath) + // Invalidate cache since we just downloaded new assets + this.baseAssetsExistCache = true + return true } @@ -170,6 +173,15 @@ export class MapService implements IMapService { const filepath = join(process.cwd(), this.mapStoragePath, 'pmtiles', filename) + + // First, ensure base assets are present - regions depend on them + const baseAssetsExist = await this.ensureBaseAssets() + if (!baseAssetsExist) { + throw new Error( + 'Base map assets are missing and could not be downloaded. Please check your connection and try again.' + ) + } + // Dispatch background job const result = await RunDownloadJob.dispatch({ url, @@ -250,18 +262,6 @@ export class MapService implements IMapService { return styles } - async checkBaseAssetsExist() { - const storageContents = await this.listMapStorageItems() - const baseStyleItem = storageContents.find( - (item) => item.type === 'file' && item.name === this.baseStylesFile - ) - const basemapsAssetsItem = storageContents.find( - (item) => item.type === 'directory' && item.name === this.basemapsAssetsDir - ) - - return !!baseStyleItem && !!basemapsAssetsItem - } - async listCuratedCollections(): Promise { const collections = await CuratedCollection.query().where('type', 'map').preload('resources') return collections.map((collection) => ({ @@ -303,9 +303,37 @@ export class MapService implements IMapService { } } - private async listMapStorageItems(): Promise { + async ensureBaseAssets(): Promise { + const exists = await this.checkBaseAssetsExist() + if (exists) { + return true + } + + return await this.downloadBaseAssets() + } + + private async checkBaseAssetsExist(useCache: boolean = true): Promise { + // Return cached result if available and caching is enabled + if (useCache && this.baseAssetsExistCache !== null) { + return this.baseAssetsExistCache + } + await ensureDirectoryExists(this.baseDirPath) - return await listDirectoryContents(this.baseDirPath) + + const baseStylePath = join(this.baseDirPath, this.baseStylesFile) + const basemapsAssetsPath = join(this.baseDirPath, this.basemapsAssetsDir) + + const [baseStyleExists, basemapsAssetsExists] = await Promise.all([ + getFileStatsIfExists(baseStylePath), + getFileStatsIfExists(basemapsAssetsPath), + ]) + + const exists = !!baseStyleExists && !!basemapsAssetsExists + + // update cache + this.baseAssetsExistCache = exists + + return exists } private async listAllMapStorageItems(): Promise { diff --git a/admin/start/routes.ts b/admin/start/routes.ts index 87401d4..ac0aac7 100644 --- a/admin/start/routes.ts +++ b/admin/start/routes.ts @@ -60,7 +60,6 @@ router .group(() => { router.get('/regions', [MapsController, 'listRegions']) router.get('/styles', [MapsController, 'styles']) - router.get('/preflight', [MapsController, 'checkBaseAssets']) router.get('/curated-collections', [MapsController, 'listCuratedCollections']) router.post('/fetch-latest-collections', [MapsController, 'fetchLatestCollections']) router.post('/download-base-assets', [MapsController, 'downloadBaseAssets'])