feat(places): backend for the photo map (geo clusters API)
Phase 1 server side, gated on OXICLOUD_ENABLE_PLACES (off by default): - migration: partial index on storage.file_metadata(longitude, latitude). - FileBlobReadRepository::list_geo_clusters — plain-SQL grid aggregation (no PostGIS) scoped to the caller's own non-trashed photos, returning a centroid, count and a representative file id per non-empty cell. - PlacesService (caller_id-scoped; user-scoped data needs no authz check, mirroring RecentService) with a zoom→cell-size mapping. - GET /api/photos/geo?bbox=w,s,e,n&zoom=N returning GeoCluster[]. The route is mounted only when the Places service is present, and is registered in the OpenAPI path list. The map frontend (PMTiles serving + MapLibre module) is deferred pending the basemap-sourcing and vendoring decision. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JW6ghFMDtnRYuYNzZhb47M
This commit is contained in:
@@ -17,6 +17,7 @@ use crate::application::services::folder_service::FolderService;
|
||||
use crate::application::services::i18n_application_service::I18nApplicationService;
|
||||
use crate::application::services::nextcloud_file_id_service::NextcloudFileIdService;
|
||||
use crate::application::services::nextcloud_login_flow_service::NextcloudLoginFlowService;
|
||||
use crate::application::services::places_service::PlacesService;
|
||||
use crate::application::services::recent_service::RecentService;
|
||||
use crate::application::services::search_service::SearchService;
|
||||
use crate::application::services::share_browse_service::ShareBrowseService;
|
||||
@@ -798,6 +799,17 @@ impl AppServiceFactory {
|
||||
service
|
||||
}
|
||||
|
||||
/// Creates the Places (photo map) service. Reuses the existing file-read
|
||||
/// repository — the data is the caller's own geotagged photos.
|
||||
pub fn create_places_service(
|
||||
&self,
|
||||
file_read: &Arc<FileBlobReadRepository>,
|
||||
) -> Arc<PlacesService> {
|
||||
let service = Arc::new(PlacesService::new(file_read.clone()));
|
||||
tracing::info!("Places service initialized");
|
||||
service
|
||||
}
|
||||
|
||||
/// Preloads translations for every locale in the registry. Build
|
||||
/// the registry at startup via `LocaleRegistry::discover` and pass
|
||||
/// the resulting list here.
|
||||
@@ -1005,6 +1017,7 @@ impl AppServiceFactory {
|
||||
// 6. Database-dependent services (PgPool always available in blob model)
|
||||
let favorites_service: Option<Arc<FavoritesService>>;
|
||||
let recent_service: Option<Arc<RecentService>>;
|
||||
let places_service: Option<Arc<PlacesService>>;
|
||||
let storage_usage_service: Option<Arc<StorageUsageService>>;
|
||||
let mut auth_services: Option<crate::common::di::AuthServices> = None;
|
||||
let mut nextcloud_services: Option<NextcloudServices> = None;
|
||||
@@ -1027,6 +1040,12 @@ impl AppServiceFactory {
|
||||
recent_service = Some(recent.clone());
|
||||
apps.recent_service = Some(recent);
|
||||
|
||||
places_service = if core.config.features.enable_places {
|
||||
Some(self.create_places_service(&repos.file_read_repository))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
storage_usage_service = Some(storage_usage.clone());
|
||||
|
||||
self.start_tree_etag_flush_job(&maintenance_pool);
|
||||
@@ -1253,6 +1272,7 @@ impl AppServiceFactory {
|
||||
share_browse_service,
|
||||
favorites_service,
|
||||
recent_service,
|
||||
places_service,
|
||||
storage_usage_service,
|
||||
calendar_service: None,
|
||||
contact_service: None,
|
||||
@@ -1699,6 +1719,7 @@ pub struct AppState {
|
||||
pub share_browse_service: Option<Arc<ShareBrowseService>>,
|
||||
pub favorites_service: Option<Arc<FavoritesService>>,
|
||||
pub recent_service: Option<Arc<RecentService>>,
|
||||
pub places_service: Option<Arc<PlacesService>>,
|
||||
pub storage_usage_service: Option<Arc<StorageUsageService>>,
|
||||
pub calendar_service: Option<Arc<CalendarService>>,
|
||||
pub contact_service: Option<Arc<ContactStorageAdapter>>,
|
||||
|
||||
Reference in New Issue
Block a user