mirror of
https://github.com/MeshCore-Beacon/beacon-server.git
synced 2026-09-01 16:48:19 +00:00
311 lines
9.6 KiB
Markdown
311 lines
9.6 KiB
Markdown
# Contributing to Beacon
|
|
|
|
Thank you for your interest in contributing. Beacon is a focused project and we
|
|
want contributions to be high quality and sustainable. Please read this guide
|
|
before opening a PR.
|
|
|
|
---
|
|
|
|
## Before you start
|
|
|
|
**Open or comment on an issue before starting work.** This avoids duplicate
|
|
effort and lets maintainers flag if something is already in progress or out of
|
|
scope. For small bug fixes a brief comment is fine; for larger features please
|
|
discuss the approach first.
|
|
|
|
**One thing per PR.** Each pull request should cover one logical change — a bug
|
|
fix, a new endpoint, a refactor, a new test. PRs that touch many unrelated parts
|
|
of the codebase are hard to review and hard to revert if something goes wrong.
|
|
|
|
**No fully AI-generated contributions.** We welcome developers who use AI tools
|
|
to assist their work, but PRs should reflect the author's own understanding and
|
|
judgement. PRs that appear to be unreviewed AI output may be closed without
|
|
further comment.
|
|
|
|
---
|
|
|
|
## Branches
|
|
|
|
- `main` — stable releases only, protected. Never target this directly.
|
|
- `dev` — active development. All PRs target `dev`.
|
|
|
|
## Workflow
|
|
|
|
1. Fork or create a branch from `dev`
|
|
2. Make your changes
|
|
3. Run the checklist below
|
|
4. Open a pull request against `dev` with a clear description of what changed
|
|
and why, referencing any related issues
|
|
|
|
---
|
|
|
|
## Checklist before opening a PR
|
|
|
|
```
|
|
go build ./... # must compile
|
|
gofmt -l . # must be empty (no unformatted files)
|
|
go vet ./... # no warnings
|
|
go test ./... # all tests pass
|
|
swag init # if you changed any handler or api type (see below)
|
|
```
|
|
|
|
---
|
|
|
|
## Code style
|
|
|
|
- Run `gofmt -w .` before committing — CI will fail on unformatted files
|
|
- Run `go vet ./...` — no warnings
|
|
- Follow the existing patterns in each package before introducing new ones
|
|
- Keep functions small and single-purpose
|
|
- Prefer explicit error handling over panic
|
|
|
|
---
|
|
|
|
## Tests
|
|
|
|
- **Add tests for any new pure functions.** Pure functions (no DB, no network,
|
|
no side effects) should have unit tests. See `internal/hub/hub_test.go`,
|
|
`internal/api/nodes_test.go`, and `internal/keystore/keystore_test.go` for
|
|
examples of the style we use.
|
|
- Integration tests (requiring a real DB) are not yet required but are welcome.
|
|
They will be gated before release.
|
|
- Run `go test ./...` before opening a PR. All tests must pass.
|
|
- If you are fixing a bug, add a test that would have caught it.
|
|
|
|
---
|
|
|
|
## Database changes
|
|
|
|
All schema changes must include a proper migration path:
|
|
|
|
- Add a new migration file to `db/migrations/` following the existing naming
|
|
convention (e.g. `002_add_observation_count.sql`). Do not modify existing
|
|
migration files — append only via new files.
|
|
- Update `db/queries/queries.sql` with any new or modified queries
|
|
- Re-run `sqlc generate` to regenerate `db/sqlc/`
|
|
- Update the store layer in `db/` to expose the new functionality
|
|
- Update `internal/api/reader.go` if the change needs to be exposed via the API
|
|
|
|
Never edit files under `db/sqlc/` by hand — they are generated by sqlc and will
|
|
be overwritten. If you need to work around a sqlc limitation, document it
|
|
clearly in the query comment.
|
|
|
|
To regenerate after modifying `db/queries/queries.sql`:
|
|
|
|
```bash
|
|
sqlc generate
|
|
```
|
|
|
|
---
|
|
|
|
## API changes
|
|
|
|
Any new or modified REST endpoint must have swagger annotations and regenerated
|
|
docs:
|
|
|
|
- Add or update `// @Summary`, `// @Param`, `// @Success`, `// @Failure`, and
|
|
`// @Router` comments on the handler function
|
|
- Response types are defined in `internal/api/` — add new types there, not
|
|
inline in handlers
|
|
- After changing any handler or API type, regenerate the swagger docs and commit
|
|
the updated `docs/` directory alongside your changes:
|
|
|
|
```bash
|
|
swag init -g cmd/beacon/main.go -o docs --parseInternal --parseDependency
|
|
```
|
|
|
|
Install swag if you don't have it:
|
|
|
|
```bash
|
|
go install github.com/swaggo/swag/cmd/swag@latest
|
|
```
|
|
|
|
Each handler function should have a godoc-style annotation block:
|
|
|
|
```go
|
|
// listThings godoc
|
|
//
|
|
// @Summary Short description shown in the UI
|
|
// @Tags TagName
|
|
// @Produce json
|
|
// @Param paramName query string false "Description"
|
|
// @Param id path string true "Resource ID"
|
|
// @Success 200 {object} api.MyResponseType
|
|
// @Failure 400 {object} handlers.APIError
|
|
// @Failure 500 {object} handlers.APIError
|
|
// @Router /things [get]
|
|
func listThings(reader api.Reader) http.HandlerFunc {
|
|
```
|
|
|
|
For paginated responses use the generic page wrapper:
|
|
|
|
```go
|
|
// @Success 200 {object} api.Page[api.MyType]
|
|
```
|
|
|
|
---
|
|
|
|
## Caching
|
|
|
|
Beacon uses a Redis-backed caching layer in `internal/cache`. If you add a new
|
|
REST endpoint backed by a new `api.Reader` method, consider whether it should be
|
|
cached:
|
|
|
|
- Add the method to `CachedReader` in `internal/cache/reader.go`
|
|
- Pass-through to `cr.inner` if the data is highly dynamic, paginated with many
|
|
filter combinations, or low traffic
|
|
- Use `getOrSet` with an appropriate TTL category (`cr.ttl.Stats`,
|
|
`cr.ttl.Reference`, `cr.ttl.Nodes`, or `cr.ttl.Observers`) for read-heavy,
|
|
slow-changing responses
|
|
- If the data is mutated by the ingest path, add an invalidation call in
|
|
`internal/ingest/side_effects.go` via the `onNodeUpsert` or `onObserverUpsert`
|
|
callbacks, or add a new callback following the same pattern
|
|
|
|
Cache keys live as constants at the top of `reader.go`. Use the `beacon:`
|
|
namespace prefix and include all parameters that affect the response in the key.
|
|
|
|
---
|
|
|
|
## Updating the IATA database
|
|
|
|
Beacon includes a static IATA → country/continent mapping compiled into the
|
|
binary, generated from the [OurAirports](https://ourairports.com/data/) public
|
|
dataset.
|
|
|
|
To refresh it with the latest airport data:
|
|
|
|
```bash
|
|
rm internal/iatadb/gen/airports.csv
|
|
go generate ./internal/iatadb/
|
|
```
|
|
|
|
This fetches a fresh `airports.csv` from OurAirports, saves it locally, and
|
|
regenerates `internal/iatadb/db.go`. Commit both files.
|
|
|
|
To use a local CSV instead (e.g. in a restricted network environment):
|
|
|
|
```bash
|
|
AIRPORTS_CSV=/path/to/airports.csv go run ./internal/iatadb/gen
|
|
```
|
|
|
|
---
|
|
|
|
## Adding a new dependency
|
|
|
|
- Run `go get <module>` and `go mod tidy`
|
|
- Add an entry to [SHOULDERS.md](SHOULDERS.md) with a brief description of what
|
|
the dependency does and why it was added
|
|
|
|
---
|
|
|
|
## Commit messages
|
|
|
|
Use the conventional commits format:
|
|
|
|
```
|
|
feat(routes): add observation_count to known routes response
|
|
fix(ingest): correct lat/lon divisor for advert payloads
|
|
refactor(hub): collapse RegionIATAs into IATAs in Scope
|
|
test(api): add unit tests for NodeTypeName and NodeTypeFromString
|
|
chore: update airports.csv
|
|
docs: expand CONTRIBUTING.md
|
|
```
|
|
|
|
Scopes are optional but helpful for larger codebases. Common scopes: `api`,
|
|
`db`, `ingest`, `hub`, `ws`, `handlers`, `config`, `keystore`.
|
|
|
|
---
|
|
|
|
## Project structure
|
|
|
|
```
|
|
cmd/beacon/ — main entry point, wiring, startup
|
|
db/ — store layer: sqlc-generated code + thin mapping layer
|
|
migrations/ — SQL schema (single file, append only)
|
|
queries/ — SQL queries (input to sqlc)
|
|
sqlc/ — generated Go code (do not edit by hand)
|
|
internal/
|
|
api/ — response types and Reader interface
|
|
handlers/ — HTTP handlers (validation, routing, response)
|
|
router/ — chi router wiring
|
|
config/ — config loading and scope key derivation
|
|
hub/ — WebSocket fan-out broker
|
|
ingest/ — MQTT packet ingestion and side effects
|
|
iatadb/ — in-memory IATA airport lookup
|
|
keystore/ — channel key lookup
|
|
scopestore/ — transport scope key lookup
|
|
ws/ — WebSocket connection handling
|
|
docs/ — generated swagger docs (do not edit by hand)
|
|
```
|
|
|
|
Key patterns to understand before contributing:
|
|
|
|
- **Store layer** (`db/`): thin wrappers around sqlc-generated queries. Each
|
|
method maps between the ingest/api param structs and sqlc param structs. Never
|
|
put business logic here.
|
|
- **Ingest layer** (`internal/ingest/`): processes raw MQTT packets, calls the
|
|
store, and broadcasts hub events. The `DB` interface in `ingest.go` defines
|
|
exactly what the ingest layer needs from the store — keep it minimal.
|
|
- **Hub** (`internal/hub/`): pure fan-out broker. Events are pre-serialised JSON
|
|
before entering the hub so the broadcast loop never touches encoding.
|
|
- **Reader interface** (`internal/api/reader.go`): defines everything the API
|
|
layer can read. The store implements it. All handler tests use a stub reader.
|
|
|
|
---
|
|
|
|
## Releases
|
|
|
|
Merges from `dev` to `main` are done by maintainers and represent a versioned
|
|
release. Do not open PRs directly against `main`.
|
|
|
|
### Cutting a release
|
|
|
|
> NOTE: release are done manually, not from GH. Commits for releases should be
|
|
> signed.
|
|
|
|
1. Ensure all changes are committed and CI is green on `dev`
|
|
2. Bump the version string in the `@version` swagger annotation in
|
|
`cmd/beacon/main.go`
|
|
3. Regenerate swagger docs:
|
|
|
|
```bash
|
|
swag init -g cmd/beacon/main.go -o docs --parseInternal --parseDependency
|
|
```
|
|
|
|
4. Commit the version bump and updated docs:
|
|
|
|
```
|
|
chore: bump version to vX.Y.Z
|
|
```
|
|
|
|
5. Merge `dev` into `main` (fast-forward only):
|
|
|
|
```bash
|
|
git checkout main
|
|
git merge --ff-only dev
|
|
```
|
|
|
|
6. Tag the release and push:
|
|
|
|
```bash
|
|
git tag vX.Y.Z
|
|
git push origin main --tags
|
|
```
|
|
|
|
Pushing the tag triggers the release CI workflow, which builds binaries for all
|
|
supported platforms and attaches them to a draft GitHub release.
|
|
|
|
7. Open the draft release on GitHub, paste the release notes, and publish.
|
|
|
|
8. Rebase `dev` on `main` to keep histories in sync for future releases:
|
|
|
|
```bash
|
|
git checkout dev
|
|
git rebase main
|
|
```
|
|
|
|
## Recognition
|
|
|
|
If you'd like to be listed as a contributor, add yourself to
|
|
[CONTRIBUTORS.md](CONTRIBUTORS.md) in your PR.
|