Add integration and development documentation
This commit is contained in:
138
docs/integrations/feedapi.md
Normal file
138
docs/integrations/feedapi.md
Normal file
@@ -0,0 +1,138 @@
|
||||
# Feedapi Runtime Contract
|
||||
|
||||
`weatherapi` uses feedapi as its generic HTTP runtime and configuration layer.
|
||||
This document describes the feedapi behavior that `weatherapi` relies on.
|
||||
|
||||
## Version
|
||||
|
||||
`go.mod` depends on:
|
||||
|
||||
- `gitea.maximumdirect.net/ejr/feedapi v0.1.0`
|
||||
|
||||
Only feedapi behavior used by `weatherapi` is documented here.
|
||||
|
||||
## Packages Used
|
||||
|
||||
Runtime composition imports:
|
||||
|
||||
- `feedapi/app`
|
||||
- `feedapi/config`
|
||||
- `feedapi/db`
|
||||
|
||||
The HTTP adapter imports:
|
||||
|
||||
- `feedapi/bind`
|
||||
- `feedapi/endpoint`
|
||||
- `feedapi/errors`
|
||||
- `feedapi/render`
|
||||
- `feedapi/response`
|
||||
|
||||
Tests also use:
|
||||
|
||||
- `feedapi/templates`
|
||||
- `feedapi/transport/httpx`
|
||||
|
||||
## Config Ownership
|
||||
|
||||
Feedapi owns loading and validating the YAML config used by `weatherapi`.
|
||||
`weatherapi` adds one local runtime check: `databases` must contain at least
|
||||
one entry.
|
||||
|
||||
The implemented config areas used by `weatherapi` are:
|
||||
|
||||
- `server`: HTTP listen/default format/timeouts;
|
||||
- `databases`: named database handles opened into a registry;
|
||||
- `templates`: base directory for text templates.
|
||||
|
||||
The canonical config reference is [`docs/config.md`](../config.md).
|
||||
|
||||
## Database Registry
|
||||
|
||||
`cmd/weatherapi` calls feedapi `db.OpenAll` with configured databases and passes
|
||||
the resulting registry into `feedapi/app.New`. It also selects the first
|
||||
configured database name from the registry as the primary weather store.
|
||||
|
||||
Feedapi owns opening and closing database handles. `weatherapi` owns choosing
|
||||
which opened handle is used by the Postgres repository.
|
||||
|
||||
## Endpoint Registry
|
||||
|
||||
`weatherapi` builds endpoint definitions with `httpapi.Definitions` and passes
|
||||
them to feedapi through `feedapi/app.WithEndpoints`.
|
||||
|
||||
Feedapi owns:
|
||||
|
||||
- route adaptation;
|
||||
- HTTP method/path matching;
|
||||
- invoking endpoint binders;
|
||||
- invoking endpoint handlers;
|
||||
- rendering handler results.
|
||||
|
||||
Endpoint definitions remain owned by `internal/adapters/inbound/httpapi`.
|
||||
|
||||
## Renderers and Templates
|
||||
|
||||
Each implemented endpoint declares JSON, XML, and text output through feedapi
|
||||
render formats. Text endpoints also name a template file.
|
||||
|
||||
Feedapi owns:
|
||||
|
||||
- renderer registration;
|
||||
- format negotiation;
|
||||
- template loading from `templates.base_dir`;
|
||||
- applying templates to response envelopes.
|
||||
|
||||
`weatherapi` owns the template files under `templates/` and presenter output
|
||||
shapes consumed by those templates.
|
||||
|
||||
## Content Negotiation
|
||||
|
||||
`weatherapi` relies on feedapi's negotiation order:
|
||||
|
||||
1. `format` query parameter;
|
||||
2. `Accept` header;
|
||||
3. configured default format.
|
||||
|
||||
Unsupported formats are exposed as structured API errors. See
|
||||
[`docs/api.md`](../api.md) for the public HTTP contract.
|
||||
|
||||
## Success and Error Envelopes
|
||||
|
||||
Endpoint handlers return `response.Envelope{Data: ...}` for successful
|
||||
responses. Nil data is rendered as `data: null`.
|
||||
|
||||
Feedapi error handling exposes structured error envelopes with:
|
||||
|
||||
- `error.code`;
|
||||
- `error.message`.
|
||||
|
||||
`weatherapi` relies on feedapi invalid-parameter and unsupported-format errors
|
||||
for request validation and negotiation failures.
|
||||
|
||||
## Middleware and Shutdown
|
||||
|
||||
Feedapi owns generic HTTP middleware and server lifecycle. The architecture
|
||||
policy records that recovery, request ID, and timing middleware are installed by
|
||||
default.
|
||||
|
||||
`weatherapi` supplies a signal-cancelable context to feedapi startup. Feedapi
|
||||
owns graceful HTTP shutdown after that context is canceled.
|
||||
|
||||
## Upgrade Checklist
|
||||
|
||||
Before upgrading feedapi:
|
||||
|
||||
- verify config field names and defaults still match [`docs/config.md`](../config.md);
|
||||
- verify database registry behavior still supports first configured database
|
||||
selection;
|
||||
- verify endpoint definition APIs still support binders, handlers, formats,
|
||||
and template names;
|
||||
- verify negotiation order remains `format`, then `Accept`, then default;
|
||||
- verify success and error envelopes still match [`docs/api.md`](../api.md);
|
||||
- run `go test ./...` with private module access configured.
|
||||
|
||||
## Related Docs
|
||||
|
||||
- [`docs/internal/runtime.md`](../internal/runtime.md)
|
||||
- [`docs/internal/http-adapter.md`](../internal/http-adapter.md)
|
||||
- [`docs/api.md`](../api.md)
|
||||
175
docs/integrations/weatherfeeder-postgres.md
Normal file
175
docs/integrations/weatherfeeder-postgres.md
Normal file
@@ -0,0 +1,175 @@
|
||||
# Weatherfeeder Postgres Contract
|
||||
|
||||
`weatherapi` reads weather data from Postgres tables owned and populated by
|
||||
`weatherfeeder`. This document describes only the storage contract consumed by
|
||||
`weatherapi`; it is not a full weatherfeeder schema reference.
|
||||
|
||||
## Version
|
||||
|
||||
`go.mod` depends on:
|
||||
|
||||
- `gitea.maximumdirect.net/ejr/weatherfeeder v0.10.0`
|
||||
|
||||
The repository code also depends on weatherfeeder canonical model types. Table
|
||||
compatibility must match the SQL in `internal/adapters/outbound/postgres`.
|
||||
|
||||
## Boundary
|
||||
|
||||
`weatherapi` is read-only:
|
||||
|
||||
- it does not create tables;
|
||||
- it does not migrate tables;
|
||||
- it does not ingest provider data;
|
||||
- it does not write weatherfeeder events.
|
||||
|
||||
Weatherfeeder owns provider polling, normalization, table shape, and writes.
|
||||
Postgres owns persistence, backup, restore, and availability.
|
||||
|
||||
## Table Families Read
|
||||
|
||||
| Resource | Tables read |
|
||||
| --- | --- |
|
||||
| Latest observation | `observations`, `observation_present_weather` |
|
||||
| Current conditions | `observations` |
|
||||
| Active alerts | `alert_runs`, `alerts`, `alert_references` |
|
||||
| Hourly forecast | `forecasts`, `forecast_periods` |
|
||||
| Narrative forecast | `forecasts`, `forecast_periods` |
|
||||
| Forecast discussion | `forecast_discussions`, `forecast_discussion_key_messages` |
|
||||
| Weather story run | `weather_story_runs`, `weather_stories` |
|
||||
| Latest weather story | `weather_stories` |
|
||||
|
||||
## Latest Row Selection
|
||||
|
||||
Latest parent resources use these ordering rules:
|
||||
|
||||
- observations: `observed_at DESC, event_emitted_at DESC`;
|
||||
- alert runs: `as_of DESC, event_emitted_at DESC`;
|
||||
- hourly forecasts: `product = 'hourly'`, then `issued_at DESC,
|
||||
event_emitted_at DESC`;
|
||||
- narrative forecasts: `product = 'narrative'`, then `issued_at DESC,
|
||||
event_emitted_at DESC`;
|
||||
- forecast discussions: `issued_at DESC, event_emitted_at DESC`;
|
||||
- weather story runs: `as_of DESC, event_emitted_at DESC`;
|
||||
- latest individual weather story: `updated_at DESC, as_of DESC,
|
||||
story_order ASC, story_index ASC`.
|
||||
|
||||
Current conditions aggregate `observations` rows where `observed_at` is inside
|
||||
the application-provided observation window.
|
||||
|
||||
## Child Ordering
|
||||
|
||||
Child rows are loaded separately and attached in stored order:
|
||||
|
||||
- observation present weather: `weather_index ASC`;
|
||||
- alerts: `alert_index ASC`;
|
||||
- alert references: `alert_index ASC, reference_index ASC`;
|
||||
- forecast periods: `period_index ASC`;
|
||||
- forecast discussion key messages: `message_index ASC`;
|
||||
- weather stories for a run: `story_index ASC`.
|
||||
|
||||
## Columns Read
|
||||
|
||||
The repository reads only these columns.
|
||||
|
||||
### `observations`
|
||||
|
||||
`event_id`, `station_id`, `station_name`, `observed_at`, `condition_code`,
|
||||
`is_day`, `text_description`, `temperature_c`, `dewpoint_c`,
|
||||
`wind_direction_degrees`, `wind_speed_kmh`, `wind_gust_kmh`,
|
||||
`barometric_pressure_pa`, `visibility_meters`, `relative_humidity_percent`,
|
||||
`apparent_temperature_c`, and `event_emitted_at`.
|
||||
|
||||
Current conditions additionally aggregate recent `observations` values for
|
||||
temperature, apparent temperature, dewpoint, humidity, wind speed, wind
|
||||
direction, condition code, and latest `is_day`.
|
||||
|
||||
### `observation_present_weather`
|
||||
|
||||
`weather_index`, `raw_text`, and `event_id`.
|
||||
|
||||
`raw_text` is decoded as JSON when present. Empty or null raw text maps to an
|
||||
empty present-weather value; invalid JSON is returned as a repository error.
|
||||
|
||||
### `alert_runs`
|
||||
|
||||
`event_id`, `location_id`, `location_name`, `as_of`, `latitude`, `longitude`,
|
||||
and `event_emitted_at`.
|
||||
|
||||
### `alerts`
|
||||
|
||||
`alert_index`, `alert_id`, `event`, `headline`, `severity`, `urgency`,
|
||||
`certainty`, `status`, `message_type`, `category`, `response`, `description`,
|
||||
`instruction`, `sent`, `effective`, `onset`, `expires`, `area_description`,
|
||||
`sender_name`, and `run_event_id`.
|
||||
|
||||
### `alert_references`
|
||||
|
||||
`alert_index`, `reference_index`, `id`, `identifier`, `sender`, `sent`, and
|
||||
`run_event_id`.
|
||||
|
||||
### `forecasts`
|
||||
|
||||
`event_id`, `location_id`, `location_name`, `issued_at`, `updated_at`,
|
||||
`product`, `latitude`, `longitude`, `elevation_meters`, and
|
||||
`event_emitted_at`.
|
||||
|
||||
Only `product = 'hourly'` and `product = 'narrative'` are read by implemented
|
||||
routes.
|
||||
|
||||
### `forecast_periods`
|
||||
|
||||
`period_index`, `start_time`, `end_time`, `name`, `is_day`,
|
||||
`condition_code`, `text_description`, `temperature_c`, `temperature_c_min`,
|
||||
`temperature_c_max`, `dewpoint_c`, `relative_humidity_percent`,
|
||||
`wind_direction_degrees`, `wind_speed_kmh`, `wind_gust_kmh`,
|
||||
`barometric_pressure_pa`, `visibility_meters`, `apparent_temperature_c`,
|
||||
`cloud_cover_percent`, `probability_of_precipitation_percent`,
|
||||
`precipitation_amount_mm`, `snowfall_depth_mm`, `uv_index`, and
|
||||
`run_event_id`.
|
||||
|
||||
### `forecast_discussions`
|
||||
|
||||
`event_id`, `office_id`, `office_name`, `issued_at`, `updated_at`, `product`,
|
||||
`short_term_qualifier`, `short_term_issued_at`, `short_term_text`,
|
||||
`long_term_qualifier`, `long_term_issued_at`, `long_term_text`, and
|
||||
`event_emitted_at`.
|
||||
|
||||
### `forecast_discussion_key_messages`
|
||||
|
||||
`message_index`, `message_text`, and `run_event_id`.
|
||||
|
||||
### `weather_story_runs`
|
||||
|
||||
`event_id`, `office_id`, `as_of`, and `event_emitted_at`.
|
||||
|
||||
### `weather_stories`
|
||||
|
||||
`story_index`, `office_id`, `start_time`, `end_time`, `updated_at`, `title`,
|
||||
`description`, `alt_text`, `priority`, `story_order`, `download_url`,
|
||||
`run_event_id`, and `as_of`.
|
||||
|
||||
## Nullability and Time Assumptions
|
||||
|
||||
The repository scans nullable columns with `sql.Null*` types and maps them to
|
||||
nil pointers or omitted zero values depending on the canonical model field.
|
||||
|
||||
Timestamps returned by the repository are normalized to UTC. Presentation
|
||||
timezone conversion happens later in HTTP presenters.
|
||||
|
||||
Missing latest parent rows return `nil, nil` from repository methods. HTTP
|
||||
rendering exposes this as a successful response with `data: null`.
|
||||
|
||||
## Compatibility Checklist
|
||||
|
||||
Before changing weatherfeeder storage or upgrading the weatherfeeder module:
|
||||
|
||||
- compare table names and columns with `internal/adapters/outbound/postgres`;
|
||||
- preserve latest-row ordering columns used by `weatherapi`;
|
||||
- preserve child index columns used for ordering;
|
||||
- preserve nullable behavior expected by row mappers;
|
||||
- run `go test ./internal/adapters/outbound/postgres` and affected HTTP tests.
|
||||
|
||||
## Related Docs
|
||||
|
||||
- [`docs/internal/postgres-repository.md`](../internal/postgres-repository.md)
|
||||
- [`docs/operations.md`](../operations.md)
|
||||
159
docs/policy/development.md
Normal file
159
docs/policy/development.md
Normal file
@@ -0,0 +1,159 @@
|
||||
# Development Policy
|
||||
|
||||
This document gives developers and LLM coding agents the concrete workflow for
|
||||
changing `weatherapi` safely. Architecture invariants are defined in
|
||||
[`docs/policy/architecture.md`](architecture.md).
|
||||
|
||||
## Repository Layout
|
||||
|
||||
| Path | Purpose |
|
||||
| --- | --- |
|
||||
| `cmd/weatherapi` | executable composition root |
|
||||
| `internal/app` | read use cases, repository port, current-conditions model |
|
||||
| `internal/adapters/inbound/httpapi` | route definitions, query binding, handlers |
|
||||
| `internal/adapters/inbound/httpapi/presenter` | response payload shaping |
|
||||
| `internal/adapters/outbound/postgres` | SQL reads and row mapping |
|
||||
| `templates` | text response templates |
|
||||
| `docs` | current documentation, policy, internals, integrations, roadmap |
|
||||
| `examples` | copyable config and request examples |
|
||||
|
||||
## Build and Test Commands
|
||||
|
||||
Build:
|
||||
|
||||
```sh
|
||||
go build ./cmd/weatherapi
|
||||
```
|
||||
|
||||
Run all tests:
|
||||
|
||||
```sh
|
||||
go test ./...
|
||||
```
|
||||
|
||||
Focused tests:
|
||||
|
||||
```sh
|
||||
go test ./internal/app
|
||||
go test ./internal/adapters/inbound/httpapi
|
||||
go test ./internal/adapters/inbound/httpapi/presenter
|
||||
go test ./internal/adapters/outbound/postgres
|
||||
```
|
||||
|
||||
Private module access is required for `feedapi` and `weatherfeeder` downloads.
|
||||
If tests fail because Go cannot fetch a private module, fix module access before
|
||||
treating package tests as behavior failures.
|
||||
|
||||
## Coding Conventions
|
||||
|
||||
- Keep `cmd/weatherapi` thin.
|
||||
- Keep application use cases independent of HTTP, SQL, renderers, and templates.
|
||||
- Keep query validation in the HTTP adapter.
|
||||
- Keep unit conversion, rounding, timezone presentation, and payload copy
|
||||
behavior in presenters.
|
||||
- Keep SQL text, row structs, null handling, and UTC normalization in the
|
||||
Postgres adapter.
|
||||
- Prefer small focused changes over broad refactors.
|
||||
- Preserve contextual error wrapping in repository and runtime code.
|
||||
|
||||
## Dependency Policy
|
||||
|
||||
Current direct dependencies are:
|
||||
|
||||
- `feedapi` for config, DB registry, endpoint definitions, renderers,
|
||||
middleware, templates, and HTTP runtime;
|
||||
- `weatherfeeder` for canonical model and standards types;
|
||||
- `github.com/lib/pq` for the Postgres driver.
|
||||
|
||||
Do not add dependencies for small conveniences. New dependencies need a clear
|
||||
adapter or domain purpose and should not leak through application boundaries
|
||||
unless they are the explicit boundary contract.
|
||||
|
||||
## Adding or Changing Endpoints
|
||||
|
||||
1. Add or update the `Service` method in `internal/adapters/inbound/httpapi` if
|
||||
the handler needs a new application read.
|
||||
2. Add or update the application repository port in `internal/app`.
|
||||
3. Implement the read in the Postgres adapter if needed.
|
||||
4. Add the endpoint definition and binder in `internal/adapters/inbound/httpapi`.
|
||||
5. Add presenter behavior in `presenter` instead of shaping payloads in handlers.
|
||||
6. Add or update text templates when `format=text` should be supported.
|
||||
7. Add endpoint tests for registration, query validation, formats, errors,
|
||||
`data: null`, and representative payload behavior.
|
||||
8. Update [`docs/api.md`](../api.md) and examples when the public HTTP contract changes.
|
||||
|
||||
## Changing Query Parameters
|
||||
|
||||
- Update binder functions and endpoint tests together.
|
||||
- Preserve strict unknown-parameter rejection unless the route explicitly allows
|
||||
a new parameter.
|
||||
- Keep timezone parsing limited to route families that support it.
|
||||
- Keep precision range validation aligned with presenter rounding support.
|
||||
- Update [`docs/api.md`](../api.md) for public query behavior changes.
|
||||
|
||||
## Adding Config Fields
|
||||
|
||||
Config shape is loaded by feedapi. When `weatherapi` starts using a new config
|
||||
field:
|
||||
|
||||
1. update runtime composition or the relevant adapter;
|
||||
2. update [`docs/config.md`](../config.md);
|
||||
3. update examples under `examples/` if operators need to set it;
|
||||
4. add config/runtime tests where practical;
|
||||
5. avoid committing real credentials or private infrastructure details.
|
||||
|
||||
## Adding CLI Flags
|
||||
|
||||
The executable currently supports only `-config`. If adding a flag:
|
||||
|
||||
1. keep parsing in `cmd/weatherapi`;
|
||||
2. avoid putting business logic in `cmd`;
|
||||
3. document precedence with environment variables if applicable;
|
||||
4. update [`docs/cli.md`](../cli.md);
|
||||
5. add tests when flag behavior is not trivial.
|
||||
|
||||
## Changing Repository Reads
|
||||
|
||||
- Keep SQL in `*_queries.go`.
|
||||
- Keep row DTOs in `*_rows.go`.
|
||||
- Keep mapping/null handling in `*_mapper.go`.
|
||||
- Return `nil, nil` for missing latest parent rows.
|
||||
- Normalize timestamps to UTC in mappers.
|
||||
- Preserve child ordering from stored index columns.
|
||||
- Update [`docs/internal/postgres-repository.md`](../internal/postgres-repository.md)
|
||||
and [`docs/integrations/weatherfeeder-postgres.md`](../integrations/weatherfeeder-postgres.md)
|
||||
when table or column assumptions change.
|
||||
|
||||
## Changing Presenters or Templates
|
||||
|
||||
- Copy input values before conversion, rounding, or timezone changes.
|
||||
- Preserve nil pointer and `omitempty` behavior.
|
||||
- Keep text-template helper fields out of JSON/XML when they are not public API
|
||||
fields.
|
||||
- Check `templates/*.txt.tmpl` for field references.
|
||||
- Update presenter tests and endpoint text tests.
|
||||
- Update [`docs/api.md`](../api.md) when response shape changes.
|
||||
|
||||
## Updating Examples
|
||||
|
||||
Examples must be copyable, valid, and free of secrets.
|
||||
|
||||
- Config examples belong under `examples/config.*.yml`.
|
||||
- HTTP examples belong in `examples/requests.http`.
|
||||
- Do not include unimplemented routes.
|
||||
- Re-run YAML syntax checks or config loading tests when config examples change.
|
||||
|
||||
## Documentation Checklist
|
||||
|
||||
When behavior changes, update the canonical doc in the same change:
|
||||
|
||||
- README for project orientation and shortest useful command;
|
||||
- `docs/api.md` for public HTTP behavior;
|
||||
- `docs/config.md` for YAML config;
|
||||
- `docs/cli.md` for executable flags and environment variables;
|
||||
- `docs/operations.md` and `docs/troubleshooting.md` for operator behavior;
|
||||
- `docs/internal/` for implementation boundaries;
|
||||
- `docs/integrations/` for external storage/runtime contracts;
|
||||
- `docs/roadmap/` only for unimplemented work.
|
||||
|
||||
Do not describe unimplemented behavior outside `docs/roadmap/`.
|
||||
Reference in New Issue
Block a user