All checks were successful
ci/woodpecker/push/build-image Pipeline was successful
169 lines
6.0 KiB
Markdown
169 lines
6.0 KiB
Markdown
# Postgres Repository
|
|
|
|
This document describes the outbound read adapter under
|
|
`internal/adapters/outbound/postgres`. It documents repository behavior and
|
|
storage assumptions without duplicating full schema documentation.
|
|
|
|
## Purpose
|
|
|
|
The Postgres repository implements `internal/app.Repository` against
|
|
weatherfeeder-owned tables. It reconstructs latest weather resources from SQL
|
|
rows and returns canonical weatherfeeder model values or application read
|
|
models.
|
|
|
|
## Inputs and Outputs
|
|
|
|
Inputs:
|
|
|
|
- a primary `*sql.DB` selected by runtime composition;
|
|
- query contexts from application service calls;
|
|
- weatherfeeder-populated Postgres rows.
|
|
|
|
Outputs:
|
|
|
|
- latest observation, forecast, discussion, weather story, alert, convective
|
|
outlook, and current conditions read models;
|
|
- nil data with nil error when the latest resource does not exist;
|
|
- contextual errors for query, scan, iteration, and JSON decode failures.
|
|
|
|
## Boundaries
|
|
|
|
The repository may:
|
|
|
|
- own SQL text and query ordering;
|
|
- scan rows into adapter-local row structs;
|
|
- map SQL nulls to pointers or omitted zero values;
|
|
- normalize timestamps to UTC;
|
|
- reconstruct child slices in database order.
|
|
|
|
The repository must not:
|
|
|
|
- bind HTTP query parameters;
|
|
- perform unit conversion or response rounding;
|
|
- convert timestamps to requested presentation timezones;
|
|
- render templates;
|
|
- create or migrate weatherfeeder tables.
|
|
|
|
## Config Fields Used
|
|
|
|
The repository does not read config directly. Runtime composition supplies the
|
|
primary `*sql.DB` selected from the first configured database entry.
|
|
|
|
## External Adapters Used
|
|
|
|
- `database/sql` for query execution and nullable scan types.
|
|
- `github.com/lib/pq` through runtime driver registration.
|
|
- `weatherfeeder/model` for canonical return values.
|
|
- `internal/app` for the repository interface and current-conditions model.
|
|
|
|
## State
|
|
|
|
The repository stores only a database handle. It owns no durable data, no
|
|
migrations, no polling state, and no retry queue.
|
|
|
|
## Repository Contract
|
|
|
|
`Repository` stores a `*sql.DB` and satisfies `app.Repository`.
|
|
|
|
Every public method first checks that the repository and database are
|
|
configured. A nil repository or nil database returns `postgres repository is not
|
|
configured`.
|
|
|
|
Missing parent rows map to `nil, nil`. This is how HTTP endpoints can return
|
|
successful responses with `data: null`.
|
|
|
|
## Route-to-Query Mapping
|
|
|
|
- `LatestObservation`: latest row from `observations`, then present-weather
|
|
rows from `observation_present_weather`.
|
|
- `CurrentConditions`: reads an aggregate row from recent `observations` using
|
|
the application-provided observation window, then reads the latest
|
|
condition-code candidate per `event_source` in the same window.
|
|
- `LatestAlertRun`: latest row from `alert_runs`, then child `alerts` and
|
|
`alert_references`. This is the latest stored alert snapshot. The repository
|
|
maps both `ends` and `expires`; active-time filtering is performed by the
|
|
application service.
|
|
- `LatestHourlyForecast`: latest `forecasts` row where `product = 'hourly'`,
|
|
then child `forecast_periods`.
|
|
- `LatestNarrativeForecast`: latest `forecasts` row where
|
|
`product = 'narrative'`, then child `forecast_periods`.
|
|
- `LatestForecastDiscussion`: latest row from `forecast_discussions`, then
|
|
child `forecast_discussion_key_messages`.
|
|
- `LatestWeatherStoryRun`: latest row from `weather_story_runs`, then child
|
|
`weather_stories`.
|
|
- `LatestWeatherStory`: latest individual row from `weather_stories`.
|
|
- `LatestConvectiveOutlookRun`: latest row from `outlook_runs`, then child
|
|
`outlooks` and `outlook_discussions`.
|
|
|
|
Latest parent rows are selected by descending weather timestamp and
|
|
`event_emitted_at` where that tie-breaker is available in the query.
|
|
|
|
## Child Loading and Ordering
|
|
|
|
Child queries preserve stored order:
|
|
|
|
- observation present weather by `weather_index`;
|
|
- alerts by `alert_index`;
|
|
- alert references by `alert_index`, then `reference_index`;
|
|
- forecast periods by `period_index`;
|
|
- discussion key messages by `message_index`;
|
|
- weather stories by `story_index`;
|
|
- outlooks by `outlook_index`;
|
|
- outlook discussions by `discussion_index`.
|
|
|
|
Alert references are attached after both alert and reference rows are loaded.
|
|
References are grouped by alert index and attached to their corresponding alert.
|
|
|
|
## Null and Timestamp Policy
|
|
|
|
Row structs use `sql.Null*` types for nullable columns. Mapper helpers convert:
|
|
|
|
- invalid strings to empty strings;
|
|
- invalid booleans, floats, times, and WMO codes to nil pointers;
|
|
- valid times to UTC.
|
|
|
|
Required parent timestamps are normalized to UTC directly in mappers. Optional
|
|
times go through `timePtr`, which also normalizes to UTC.
|
|
|
|
Current conditions return nil when the aggregate sample count is zero.
|
|
|
|
## JSON Decode Behavior
|
|
|
|
Observation present-weather rows store raw JSON text. Empty or null text maps
|
|
to an empty present-weather value. Invalid JSON returns a contextual decode
|
|
error with the weather index.
|
|
|
|
Outlook rows store `geometry_json` as compact GeoJSON text. The repository
|
|
validates and copies the JSON bytes into `json.RawMessage` without parsing or
|
|
reserializing the geometry.
|
|
|
|
## Failure Behavior
|
|
|
|
Repository methods wrap failures with operation context, for example:
|
|
|
|
- `query latest observation`
|
|
- `query forecast periods`
|
|
- `scan forecast period row`
|
|
- `iterate weather story rows`
|
|
- `decode observation present weather row`
|
|
|
|
This context should be preserved when adding new reads so operator logs and
|
|
tests identify the failing operation.
|
|
|
|
## Tests to Inspect Before Changing
|
|
|
|
- `internal/adapters/outbound/postgres/repository_test.go`
|
|
- `internal/app/service_test.go` for repository port expectations
|
|
- endpoint tests when no-data behavior or returned model shape affects HTTP
|
|
responses
|
|
|
|
## Invariants
|
|
|
|
- Keep SQL and row structs in the Postgres adapter.
|
|
- Keep weatherfeeder schema ownership outside `weatherapi`.
|
|
- Preserve `nil, nil` no-data behavior for missing latest parent rows.
|
|
- Preserve UTC normalization at the repository boundary.
|
|
- Preserve child ordering from stored index columns.
|
|
- Preserve contextual error wrapping for query, scan, iteration, and decode
|
|
failures.
|