Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ jobs:
- name: Run tests
run: cargo test --verbose

# cargo test regenerates bindings/ (ts-rs), so a diff here means the
# committed TypeScript bindings are stale — commit the regenerated files
- name: Check TypeScript bindings are up to date
run: git diff --exit-code bindings/

- name: Build
run: cargo build --release --verbose

Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1 @@
/target
/target
18 changes: 18 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,24 @@ Useful when you want to see if a patch actually works on production-shaped data
### Rust version
The project does not pin its Rust version. Contributors should use a recent stable Rust toolchain with `rustfmt` and `clippy` components installed.

## TypeScript Bindings (`bindings/`)

Committed, generated TypeScript definitions for the v4 REST types consumed by
the btcmap.org frontend (see the README section "TypeScript bindings" for the
full picture). Rules when working here:

- Never edit `bindings/*.ts` by hand — they are ts-rs output, regenerated by
every `cargo test` run. CI fails if the committed files don't match the code.
- When you change a struct that derives `ts_rs::TS`, run `cargo test` and
commit the updated `bindings/` files in the same commit.
- New exported structs follow the conventions: `#[ts(type = "number")]` on
i64/u64 fields, `#[ts(type = "string")]` on RFC 3339 timestamp fields,
`#[ts(optional)]` on `Option` fields with `skip_serializing_if`, and
`#[ts(rename = "...")]` when the struct name collides with another module's.
Export is opt-in: only derive on types the frontend actually consumes.
- The all-optional `Place` binding must list every `service::element::TAGS`
entry — the `place_type_covers_all_generate_tags_fields` test enforces it.

## Code Structure

```
Expand Down
23 changes: 23 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,8 @@ x509-parser = "0.18"
# SHA-256 hashing for SPKI pin comparison.
# https://github.com/RustCrypto/hashes
sha2 = "0.10"

# Derives TypeScript definitions from response structs so btcmap.org can
# generate its API types from this crate (dev tool; no runtime use)
# https://github.com/Aleph-Alpha/ts-rs/blob/main/CHANGELOG.md
ts-rs = "12.0.1"
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,35 @@ The `devtools` script provides helper commands for development:
| `fetch-log-db` | Fetch only the log database |
| `deploy` | Run tests, build release, deploy to production |
| `gen-main-schema` | Generate `schema.sql` from migrations |
| `export-ts-types [dir]` | Export the TypeScript bindings to a directory |

### TypeScript bindings (`bindings/`)

`bindings/` contains TypeScript definitions for the v4 REST types that
[btcmap.org](https://github.com/teambtcmap/btcmap.org) consumes. They are
**generated, not hand-written**: structs annotated with `#[derive(ts_rs::TS)]`
are exported by [ts-rs](https://github.com/Aleph-Alpha/ts-rs) every time
`cargo test` runs, so the directory always matches the code on your branch and
never goes stale silently — CI fails if a commit leaves it out of date.

How the pieces fit:

- **Changing an exported struct?** Run `cargo test` (or
`devtools export-ts-types`), and commit the updated `bindings/` files along
with your change. The diff doubles as a readable record of the API change.
- **Adding a new response type for the frontend?** Add
`#[derive(ts_rs::TS)]` + `#[ts(export)]` to the struct. Conventions:
64-bit integers get `#[ts(type = "number")]` (JSON transport), RFC 3339
timestamps get `#[ts(type = "string")]`, and names that repeat across
modules get `#[ts(rename = "...")]` so every binding file is unique.
Export is opt-in per struct — types not meant for third-party use simply
don't get the derive.
- **Consuming the types?** The frontend fetches this directory from GitHub
(`pnpm types:api` in btcmap.org) — no Rust toolchain or checkout of this
repo required. Any other client can do the same.

The dynamic `GET /v4/places` responses (shaped by the `fields` query param)
are described by the all-optional `Place` type, kept in sync with
`service::element::TAGS` by the `place_type_covers_all_generate_tags_fields`
test.
| `install-completions` | Install bash tab completions for devtools |
3 changes: 3 additions & 0 deletions bindings/ActivityItem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export type ActivityItem = { type: string, place_id: number, place_name?: string, osm_user_id?: number, osm_user_name?: string, osm_user_tip?: string, comment?: string, duration_days?: number, image: string, date: string, };
3 changes: 3 additions & 0 deletions bindings/Area.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export type Area = { id: number, name: string, type: string, url_alias: string, icon: string | null, icon_wide: string | null, website_url: string, description: string, };
3 changes: 3 additions & 0 deletions bindings/AreaSearchResult.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export type AreaSearchResult = { id: number, name: string, type: string, url_alias: string, icon: string | null, website_url: string, };
3 changes: 3 additions & 0 deletions bindings/AuthNostrResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export type AuthNostrResponse = { token: string, username: string, npub: string, };
3 changes: 3 additions & 0 deletions bindings/ChangePasswordArgs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export type ChangePasswordArgs = { old_password: string, new_password: string, };
3 changes: 3 additions & 0 deletions bindings/ChartEntry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export type ChartEntry = { date: string, value: number, };
3 changes: 3 additions & 0 deletions bindings/Community.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export type Community = { id: number, alias: string, name: string, icon: string | null, places_total: number, places_verified_1y: number, grade: number, };
3 changes: 3 additions & 0 deletions bindings/Country.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export type Country = { id: number, alias: string, name: string, icon: string | null, places_total: number, places_verified_1y: number, grade: number, };
3 changes: 3 additions & 0 deletions bindings/CreateTokenArgs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export type CreateTokenArgs = { label: string | null, };
4 changes: 4 additions & 0 deletions bindings/CreateTokenResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { MeResponse } from "./MeResponse";

export type CreateTokenResponse = { token: string, user: MeResponse, };
3 changes: 3 additions & 0 deletions bindings/CreateUserArgs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export type CreateUserArgs = { name: string | null, password: string, };
3 changes: 3 additions & 0 deletions bindings/CreateUserResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export type CreateUserResponse = { id: number, name: string, roles: Array<string>, };
4 changes: 4 additions & 0 deletions bindings/Dashboard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ChartEntry } from "./ChartEntry";

export type Dashboard = { total_merchants: number, total_merchants_chart: Array<ChartEntry>, verified_merchants_1y: number, verified_merchants_1y_chart: Array<ChartEntry>, total_exchanges: number, total_exchanges_chart: Array<ChartEntry>, verified_exchanges_1y: number, total_areas: number, verified_areas_1y: number, };
3 changes: 3 additions & 0 deletions bindings/Invoice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export type Invoice = { id: string, status: string, };
15 changes: 15 additions & 0 deletions bindings/MeResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { SavedArea } from "./SavedArea";
import type { SavedPlace } from "./SavedPlace";

export type MeResponse = { id: number, name: string, roles: Array<string>, saved_places: Array<SavedPlace>, saved_areas: Array<SavedArea>,
/**
* Area ids the user is restricted to when acting as an event manager.
* Empty means unrestricted.
*/
geofence: Array<number>,
/**
* Bech32 npub (`npub1...`) of the Nostr identity linked to this user,
* or `null` when no pubkey is linked.
*/
npub: string | null, };
7 changes: 7 additions & 0 deletions bindings/NostrIdentityResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export type NostrIdentityResponse = {
/**
* Bech32 npub (`npub1...`) currently linked to the account, or `null`.
*/
npub: string | null, };
3 changes: 3 additions & 0 deletions bindings/PaginationInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export type PaginationInfo = { offset: number, limit: number, total: number, };
11 changes: 11 additions & 0 deletions bindings/Place.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

/**
* Type-export-only description of the dynamic place responses.
* `GET /v4/places` and `GET /v4/places/{id}` build their JSON at runtime
* from the `fields` query param, so every field except `id` is optional.
* Fields requested via the `osm:<tag>` passthrough appear as extra keys and
* cannot be typed statically. Kept in sync with `service::element::TAGS` by
* `place_type_covers_all_generate_tags_fields`.
*/
export type Place = { id: number, osm_id?: string, osm_url?: string, osm_edit_url?: string, lat?: number, lon?: number, name?: string, address?: string, icon?: string, phone?: string, website?: string, twitter?: string, facebook?: string, instagram?: string, line?: string, email?: string, opening_hours?: string, boosted_until?: string, required_app_url?: string, created_at?: string, updated_at?: string, deleted_at?: string, verified_at?: string, comments?: number, description?: string, image?: string, payment_provider?: string, telegram?: string, localized_name?: Record<string, string>, localized_opening_hours?: Record<string, string>, };
3 changes: 3 additions & 0 deletions bindings/PlaceActivity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export type PlaceActivity = { id: number, type: string, user_id: number | null, user_name?: string, user_tip?: string, created_at: string, updated_at: string, };
3 changes: 3 additions & 0 deletions bindings/PlaceArea.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export type PlaceArea = { id: number, alias: string, tags: Record<string, unknown>, created_at: string, updated_at: string, };
3 changes: 3 additions & 0 deletions bindings/PlaceBoostQuote.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export type PlaceBoostQuote = { quote_30d_sat: number, quote_90d_sat: number, quote_365d_sat: number, };
3 changes: 3 additions & 0 deletions bindings/PlaceComment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export type PlaceComment = { id: number, text: string, created_at: string, };
3 changes: 3 additions & 0 deletions bindings/PlaceCommentListItem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export type PlaceCommentListItem = { id: number, place_id: number, text: string, created_at: string, updated_at: string, deleted_at?: string, };
3 changes: 3 additions & 0 deletions bindings/PlaceCommentQuote.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export type PlaceCommentQuote = { quote_sat: number, };
3 changes: 3 additions & 0 deletions bindings/PlaceIssue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export type PlaceIssue = { id: number, place_id: number, code: string, severity: number, created_at: string, updated_at: string, deleted_at?: string, };
3 changes: 3 additions & 0 deletions bindings/PlaceIssueSummary.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export type PlaceIssueSummary = { element_osm_type: string, element_osm_id: number, element_name: string, issue_code: string, };
4 changes: 4 additions & 0 deletions bindings/PlaceIssuesRes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { PlaceIssueSummary } from "./PlaceIssueSummary";

export type PlaceIssuesRes = { total_issues: number, requested_issues: Array<PlaceIssueSummary>, };
3 changes: 3 additions & 0 deletions bindings/PostPlaceBoostArgs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export type PostPlaceBoostArgs = { place_id: string, days: number, };
3 changes: 3 additions & 0 deletions bindings/PostPlaceBoostResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export type PostPlaceBoostResponse = { invoice_id: string, invoice: string, };
3 changes: 3 additions & 0 deletions bindings/PostPlaceCommentArgs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export type PostPlaceCommentArgs = { place_id: string, comment: string, };
3 changes: 3 additions & 0 deletions bindings/PostPlaceCommentResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export type PostPlaceCommentResponse = { invoice_id: string, invoice: string, };
3 changes: 3 additions & 0 deletions bindings/SavedArea.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export type SavedArea = { id: number, name: string, };
3 changes: 3 additions & 0 deletions bindings/SavedPlace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export type SavedPlace = { id: number, name: string, };
5 changes: 5 additions & 0 deletions bindings/SearchResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { PaginationInfo } from "./PaginationInfo";
import type { SearchResult } from "./SearchResult";

export type SearchResponse = { results: Array<SearchResult>, total_count: number, has_more: boolean, query: string, pagination: PaginationInfo, };
9 changes: 9 additions & 0 deletions bindings/SearchResult.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { SearchedArea } from "./SearchedArea";
import type { SearchedPlace } from "./SearchedPlace";

/**
* `SearchedPlace` is boxed because it is an order of magnitude larger than
* `SearchedArea`, and clippy's `large_enum_variant` would otherwise fire.
*/
export type SearchResult = { "type": "area" } & SearchedArea | { "type": "place" } & SearchedPlace;
7 changes: 7 additions & 0 deletions bindings/SearchedArea.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export type SearchedArea = { id: number, name: string, alias?: string,
/**
* `[west, south, east, north]`. Absent when the area has no bbox of its own.
*/
bbox?: [number, number, number, number], };
3 changes: 3 additions & 0 deletions bindings/SearchedPlace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export type SearchedPlace = { id: number, lat: number, lon: number, icon: string, name: string, address?: string, opening_hours?: string, comments?: number, created_at: string, updated_at: string, verified_at: string | null, osm_id?: string, phone?: string, website?: string, twitter?: string, facebook?: string, instagram?: string, line?: string, email?: string, boosted_until?: string, required_app_url?: string, description?: string, image?: string, payment_provider?: string, localized_name?: Record<string, string>, };
3 changes: 3 additions & 0 deletions bindings/TopEditor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export type TopEditor = { id: number, name: string, avatar_url: string | null, total_edits: number, places_created: number, places_updated: number, places_deleted: number, tip_url: string | null, };
3 changes: 3 additions & 0 deletions bindings/UpdateUsernameArgs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.

export type UpdateUsernameArgs = { username: string, };
11 changes: 11 additions & 0 deletions devtools
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,17 @@ case "$1" in
rm -f /tmp/schema_gen.db
;;

export-ts-types)
# Export the ts-rs TypeScript bindings into a btcmap.org checkout
# (or any target directory). The frontend commits the result.
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$script_dir"
target="${2:-../btcmap.org/src/types/btcmap-api}"
mkdir -p "$target"
TS_RS_EXPORT_DIR="$(cd "$target" && pwd)" cargo test export_bindings \
&& echo "exported TypeScript bindings to $target"
;;

install-completions)
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

Expand Down
2 changes: 1 addition & 1 deletion devtools_completion.bash
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ _devtools() {
local cur prev words cword
_init_completion || return

local commands="main-db image-db log-db fetch-db fetch-main-db fetch-image-db fetch-log-db deploy gen-main-schema install-completions"
local commands="main-db image-db log-db fetch-db fetch-main-db fetch-image-db fetch-log-db deploy gen-main-schema export-ts-types install-completions"

if [[ $cword -eq 1 ]]; then
COMPREPLY=($(compgen -W "$commands" -- "$cur"))
Expand Down
11 changes: 10 additions & 1 deletion src/rest/v4/activity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,24 +36,33 @@ pub struct GetActivityArgs {
places: Option<String>,
}

#[derive(Serialize, Deserialize)]
#[derive(Serialize, Deserialize, ts_rs::TS)]
#[ts(export)]
pub struct ActivityItem {
pub r#type: String,
#[ts(type = "number")]
pub place_id: i64,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub place_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional, type = "number")]
pub osm_user_id: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub osm_user_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub osm_user_tip: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub comment: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional, type = "number")]
pub duration_days: Option<i64>,
pub image: String,
#[serde(with = "time::serde::rfc3339", rename = "date")]
#[ts(type = "string")]
pub created_at: OffsetDateTime,
}

Expand Down
Loading
Loading