feat: places (reverse geocoding) + folder browser
Places: - worker/geocode.js: Nominatim reverse geocoder, 1 req/sec rate limit, in-memory cache keyed to 3-decimal-place coordinates - Ingest calls reverseGeocode() for any photo with GPS data; stores place_city and place_country on the photo row - DB migrations add place_city / place_country columns + index on place_city - Stats API returns cities grouped by count - buildFilter() supports ?city= parameter - FilterSidebar shows a Places section with city list + country sublabel Folder browser: - GET /api/fs/browse?path=… lists subdirectories (path-normalized to prevent traversal) - Settings panel: 📁 button opens inline directory browser below the add input — navigate with up arrow / directory clicks, Select fills the input Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
64f7906b22
commit
3429ec1d80
8 files changed
+264
-25
No files matched your search
@@ -0,0 +1,36 @@
|
||||
const cache = new Map();
|
||||
let lastCallMs = 0;
|
||||
|
||||
export async function reverseGeocode(lat, lon) {
|
||||
const key = `${lat.toFixed(3)},${lon.toFixed(3)}`;
|
||||
if (cache.has(key)) return cache.get(key);
|
||||
|
||||
// Nominatim ToS: max 1 request/second
|
||||
const wait = 1100 - (Date.now() - lastCallMs);
|
||||
if (wait > 0) await new Promise(r => setTimeout(r, wait));
|
||||
lastCallMs = Date.now();
|
||||
|
||||
try {
|
||||
const url = `https://nominatim.openstreetmap.org/reverse?lat=${lat}&lon=${lon}&format=json&zoom=10`;
|
||||
const res = await fetch(url, {
|
||||
headers: { 'User-Agent': 'Luma/1.0 self-hosted-photo-manager' },
|
||||
signal: AbortSignal.timeout(8000),
|
||||
});
|
||||
if (!res.ok) throw new Error(res.status);
|
||||
const data = await res.json();
|
||||
const place = {
|
||||
city: data.address?.city
|
||||
?? data.address?.town
|
||||
?? data.address?.village
|
||||
?? data.address?.county
|
||||
?? null,
|
||||
country: data.address?.country ?? null,
|
||||
};
|
||||
cache.set(key, place);
|
||||
return place;
|
||||
} catch (err) {
|
||||
console.warn(`[geocode] ${lat.toFixed(4)},${lon.toFixed(4)}: ${err.message}`);
|
||||
cache.set(key, null);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user