# Module: DataAccess/Repositories/TileRepository ## Purpose Dapper-based repository for the `tiles` table. Handles CRUD operations and spatial queries for satellite tile records. ## Public Interface ### ITileRepository (interface) - `GetByIdAsync(Guid id) → Task` - `GetByTileCoordinatesAsync(int tileZoom, int tileX, int tileY) → Task`: finds the most-recent tile across all sources for the given slippy coordinates. Selection rule: `ORDER BY captured_at DESC, updated_at DESC, id DESC LIMIT 1` (AZ-484 v1.0.0 contract). - `GetTilesByRegionAsync(double lat, double lon, double sizeMeters, int zoomLevel) → Task>`: spatial bounding box query (expanded by 2 × tile size to cover edges); applies `DISTINCT ON (latitude, longitude, tile_zoom, tile_size_meters)` per AZ-484 to return at most one row per cell — the most-recent across sources — preserving the historical caller-facing order `latitude DESC, longitude ASC`. - `InsertAsync(TileEntity tile) → Task`: per-source UPSERT — `ON CONFLICT (latitude, longitude, tile_zoom, tile_size_meters, source) DO UPDATE file_path, tile_x, tile_y, captured_at, updated_at` (AZ-484 5-column unique key). - `UpdateAsync(TileEntity tile) → Task`: full row update by `id` including `source` and `captured_at`. - `DeleteAsync(Guid id) → Task` ### TileRepository (implementation) Constructs a new `NpgsqlConnection` per method call (no connection pooling at the repository level; Npgsql pools connections internally). Logs a `Slow GetTilesByRegionAsync` warning when the region query exceeds 500 ms. ## Internal Logic - `GetTilesByRegionAsync` calculates a bounding box by expanding the requested region by 2 × tile size to ensure edge tiles are included. Uses meters-to-degrees approximation via `GeoUtils` (post-AZ-377 — single source of truth for Earth constants). - `InsertAsync` uses a per-source UPSERT pattern keyed on the 5-column unique index `idx_tiles_unique_location_source` (created by migration 013). Two producers (e.g., `google_maps` + `uav`) coexist for the same cell; same-source re-insert refreshes `captured_at` and `updated_at`. - `GetByTileCoordinatesAsync` and `GetTilesByRegionAsync` apply the AZ-484 selection rule: most-recent across sources, deterministic tie-break on `(captured_at DESC, updated_at DESC, id DESC)`. - `TileEntity.Source` is a plain `string` storing the snake_case wire value (`'google_maps'` | `'uav'`); enum<->wire conversion happens via `SatelliteProvider.Common.Enums.TileSourceConverter`. This avoids Dapper issue #259 (TypeHandler bypass for enum reads — see `_docs/LESSONS.md` L-001). - `FindExistingTileAsync` was removed by AZ-376 (see `_docs/04_refactoring/03-code-quality-refactoring/`). ## Dependencies - NuGet: `Dapper`, `Npgsql` - `SatelliteProvider.DataAccess.Models.TileEntity` - `SatelliteProvider.Common.GeoUtils` (Earth constants / meters-to-degrees) - `Microsoft.Extensions.Logging` ## Contract Implements the frozen v1.0.0 contract `_docs/02_document/contracts/data-access/tile-storage.md`. Schema invariants Inv-1..Inv-5 are enforced here (UPSERT key, selection rule, source value space). ## Consumers - `TileService` — all read/write operations - `Program.cs` (ServeTile, GetTileByLatLon handlers) — `GetByTileCoordinatesAsync`, `InsertAsync` ## Data Models Operates on `TileEntity`. ## Configuration Receives connection string via constructor. ## External Integrations PostgreSQL — SQL queries via Dapper + Npgsql. ## Security None. ## Tests No dedicated repository tests.