Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7fe1be9b48 | |||
| c8731caadd | |||
| 20cb7b101c | |||
| ac3124ff14 | |||
| b29f42c004 | |||
| 051721ce02 | |||
| 04ed0ede29 | |||
| a17e7a5290 | |||
| aab04bb194 | |||
| 1e0b3cc574 | |||
| cf2609f624 | |||
| ab31c84913 | |||
| 018190f76b | |||
| affdb052b2 | |||
| 90fe8be81d | |||
| 9c27e46bfb | |||
| e3877bc7c0 | |||
| c876637720 | |||
| 7fd7d94572 | |||
| d6c5750d5e | |||
| 2068a4335b | |||
| 7e2361015a | |||
| 59e405d2c4 |
@@ -17,11 +17,9 @@ jobs:
|
|||||||
id: get_version
|
id: get_version
|
||||||
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
|
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- uses: bufbuild/buf-setup-action@v1
|
|
||||||
|
|
||||||
- uses: actions/setup-go@v5
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '1.23'
|
go-version: '1.24'
|
||||||
|
|
||||||
- uses: actions/setup-node@v6
|
- uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
@@ -29,46 +27,73 @@ jobs:
|
|||||||
registry-url: 'https://git.fjla.uk/api/packages/owlboard/npm'
|
registry-url: 'https://git.fjla.uk/api/packages/owlboard/npm'
|
||||||
scope: '@owlboard'
|
scope: '@owlboard'
|
||||||
|
|
||||||
- name: Install Go Protoc Plugin
|
- name: Install Generators
|
||||||
run: |
|
run: |
|
||||||
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
|
npm install -g json-schema-to-typescript typescript
|
||||||
|
go install github.com/atombender/go-jsonschema@latest
|
||||||
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
|
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
|
||||||
|
|
||||||
- name: Install TS-Proto Plugin
|
- run: bash scripts/build.sh
|
||||||
run: |
|
|
||||||
npm install ts-proto
|
|
||||||
echo "$PATH:$(pwd)/ts-proto" >> $GITHUB_PATH
|
|
||||||
|
|
||||||
- name: Generate Code
|
- name: Build and Publish TS
|
||||||
run: buf generate
|
|
||||||
|
|
||||||
- name: Publish TS
|
|
||||||
working-directory: gen/ts
|
working-directory: gen/ts
|
||||||
run: |
|
run: |
|
||||||
npm init -y
|
npm init -y
|
||||||
jq '.name = "@owlboard/backend-data-contracts" |
|
|
||||||
.version = "${{ steps.get_version.outputs.VERSION }}" |
|
|
||||||
.description = "Generated Protobuf types for data ingress services" |
|
|
||||||
.repository = {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git+https://${{ github.server_url }}/${{ github.repository }}.git"
|
|
||||||
} |
|
|
||||||
.bugs = {
|
|
||||||
"url": "https://${{ github.server_url }}/${{ github.repository }}/issues"
|
|
||||||
} |
|
|
||||||
.homepage = "https://${{ github.server_url }}/${{ github.repository }}#readme"' \
|
|
||||||
package.json > temp.json && mv temp.json package.json
|
|
||||||
npm publish
|
|
||||||
env:
|
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.PACKAGE_PUSH }}
|
|
||||||
|
|
||||||
- name: Commit Generated Go
|
# Build index.ts
|
||||||
|
echo "// Auto-generated" > index.ts
|
||||||
|
find . -maxdepth 1 -name "*.ts" -not -name "index.ts" | sed 's|^\./||; s|\.ts$||' | awk '{
|
||||||
|
# Use gsub to turn hyphens into underscores so we can split easily
|
||||||
|
clean = $0;
|
||||||
|
gsub(/-/, "_", clean);
|
||||||
|
|
||||||
|
n = split(clean, parts, "_");
|
||||||
|
name = "";
|
||||||
|
for (i=1; i<=n; i++) {
|
||||||
|
if (length(parts[i]) > 0) {
|
||||||
|
name = name toupper(substr(parts[i],1,1)) substr(parts[i],2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# name will now be 'DataIngressPisData' (valid TS)
|
||||||
|
printf "export * as %s from \"./%s.js\";\n", name, $0
|
||||||
|
}' >> index.ts
|
||||||
|
|
||||||
|
VERSION="${{ steps.get_version.outputs.VERSION }}"
|
||||||
|
jq --arg ver "$VERSION" \
|
||||||
|
--arg name "@owlboard/backend-data-contracts" \
|
||||||
|
'.name = $name | .version = $ver | .type = "module" | .main = "./dist/index.js" | .types = "./dist/index.d.ts"' \
|
||||||
|
package.json > package.json.new && mv package.json.new package.json
|
||||||
|
|
||||||
|
# Compile
|
||||||
|
npx tsc index.ts --declaration --module nodenext --target es2022 --moduleResolution nodenext --outDir dist/ --skipLibCheck true
|
||||||
|
|
||||||
|
# Publish
|
||||||
|
npm config set //git.fjla.uk/api/packages/owlboard/npm/:_authToken ${{ secrets.PACKAGE_PUSH }}
|
||||||
|
npm publish
|
||||||
|
|
||||||
|
- name: Publish Go
|
||||||
run: |
|
run: |
|
||||||
git config user.name "owlbot"
|
# 1. Setup variables
|
||||||
git config user.email "owlbot@owlboard.info"
|
VERSION="${{ steps.get_version.outputs.VERSION }}"
|
||||||
git add gen/go/*.go
|
MOD_NAME="git.fjla.uk/owlboard/backend-data-contracts"
|
||||||
git commit -m "OwlBot: Generated go types for ${{ steps.get_version.outputs.VERSION }}"
|
# Create a temporary directory structure that matches Go proxy requirements
|
||||||
git diff-index --quiet HEAD || git commit -m "OwlBot: Generated go types for ${{ steps.get_version.outputs.VERSION }}"
|
DEST_DIR="temp_zip/$MOD_NAME@$VERSION"
|
||||||
git push origin HEAD:refs/tags/${{ github.ref_name }} --force
|
|
||||||
env:
|
# 2. Generate go.mod and tidy
|
||||||
GITHUB_TOKEN: ${{ secrets.REPO_PUSH }}
|
cd gen/go
|
||||||
|
go mod init $MOD_NAME
|
||||||
|
go mod tidy
|
||||||
|
|
||||||
|
# 3. Move files into the required nested structure
|
||||||
|
mkdir -p "../../$DEST_DIR"
|
||||||
|
cp -r . "../../$DEST_DIR/"
|
||||||
|
|
||||||
|
# 4. Zip from the temp root so the internal paths are correct
|
||||||
|
cd ../../temp_zip
|
||||||
|
zip -r ../module.zip .
|
||||||
|
|
||||||
|
# 5. Upload with the explicit version
|
||||||
|
cd ..
|
||||||
|
curl -f -v --user "owlbot:${{ secrets.PACKAGE_PUSH }}" \
|
||||||
|
--upload-file module.zip \
|
||||||
|
"${{ github.server_url }}/api/packages/owlboard/go/upload?version=$VERSION"
|
||||||
43
README.md
43
README.md
@@ -1,34 +1,37 @@
|
|||||||
# data-contracts
|
# backend-data-contracts
|
||||||
|
|
||||||
This repository is the single source of truth for all Protocol Buffer (Protobuf) schema definitions used across the Rail Ingress and Processing microservices.
|
This repository is the single source of truth for all Protocol Buffer (Protobuf) schema definitions used across the Owlboard Rail Ingress and Processing microservices. It follows a **Contract-First** approach, where language-specific code (Go, TypeScript) is generated and distributed via private registries.
|
||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
|
|
||||||
The Protobuf files defined here serve as the immutable data contract for:
|
The Protobuf files defined here serve as the immutable data contract for:
|
||||||
1. **Message Queue Payloads:** Defining messages pushed to the Artemis queue (Go Process Service consumption).
|
1. **Ingress Logic:** Ensuring the Node.js and Go inregrate properly by sharing types.
|
||||||
2. **Database Schemas:** Defining the expected structure of documents in MongoDB (Go Process and TypeScript API service consumption).
|
2. **Message Payloads:** Defining messages for cross-service communication.
|
||||||
3. **Cross-Service Communication:** Ensuring type-safe data exchange between all polyglot services (Go, TypeScript).
|
3. **Persistence:** Defining the expected structure of documents in MongoDB for the Go processing services.
|
||||||
|
|
||||||
## Directory Structure and Artifacts
|
## Directory Structure
|
||||||
|
|
||||||
| Path | Description | Contents |
|
| Path | Description | Contents |
|
||||||
| :--- | :--- | :--- |
|
| :--- | :--- | :--- |
|
||||||
| `protos/rail/v1/` | **Source Code.** Contains all source `.proto` files. **Only these files reside on the `main` branch.** | `.proto` files |
|
| `proto/` | **Source of Truth.** Contains the Protobuf schema definitions. | `rail_backend/v1/*.proto` |
|
||||||
| `ts/` | **Generated TypeScript/JavaScript code.** Used as the root for the NPM package publish. **Not committed to Git.** | `package.json`, generated `.js`, `.d.ts` |
|
| `buf.yaml` | **Workspace Config.** Defines linting and breaking change rules (Buf v2). | Workspace settings |
|
||||||
| `go.mod` | Defines the Go Module path: `git.fjla.uk/owlboard/backend-data-contracts`. | Go Module definition |
|
| `buf.gen.yaml` | **Generation Config.** Defines how Go and TS code is built. | Plugin & Managed Mode settings |
|
||||||
|
| `gen/` | **Transient Output.** Generated code resides here during CI/CD. | `.pb.go`, `.js`, `.d.ts` (**Git Ignored**) |
|
||||||
|
|
||||||
## Code Generation and Publishing Workflow (Gitea Action)
|
## Code Generation and Publishing Workflow
|
||||||
|
|
||||||
The generation process is automated via a Gitea Action (`.gitea/workflows/generate_contracts.yml`), which runs when a change is pushed to a source `.proto` file.
|
The generation and release process is automated via Gitea Actions. It is triggered whenever a new **SemVer tag** (e.g., `v1.0.0`) is pushed to this repository.
|
||||||
|
|
||||||
The action performs the following steps:
|
1. **Validation:** `buf lint` ensures schemas follow best practices.
|
||||||
1. Generates all Go and TypeScript/JavaScript artifacts.
|
2. **Generation:** `buf generate` creates Go and TypeScript code using local plugins.
|
||||||
2. **Go Artifacts:** Commits the generated `*.pb.go` files to a **new Git tag** (e.g., `v1.0.1`), ensuring the `main` branch remains clean.
|
3. **TS Release:** Compiles `.ts` to `.js` and publishes to the Gitea NPM Registry as `@owlboard/backend-data-contracts`.
|
||||||
3. **TypeScript Artifacts:** Packages the generated files and publishes the corresponding version (e.g., `1.0.1`) to the internal NPM registry.
|
4. **Go Release:** Initializes a `go.mod`, zips the artifacts, and pushes them to the Gitea Go Package Registry.
|
||||||
|
|
||||||
### To Consume the Contract:
|
## To Consume the Contracts
|
||||||
|
|
||||||
| Language | Artifact | Consumption Method |
|
### 1. Go Services
|
||||||
| :--- | :--- | :--- |
|
Since the package is hosted in the Gitea Package Registry, you must configure your environment to use the Gitea instance as a proxy.
|
||||||
| **Go** | Source Code (`*.pb.go`) | **Requires a Git Tag.** Update your service's `go.mod` file to reference the desired tag: `git.fjla.uk/owlboard/backend-data-contracts v1.0.1`. |
|
|
||||||
| **TypeScript** | NPM Package | Update the version in your `package.json` file and install: `"@owlboard/contracts": "1.0.1"`. |
|
**Setup:**
|
||||||
|
```bash
|
||||||
|
export GOPROXY=[https://git.fjla.uk/api/packages/owlboard/go,https://proxy.golang.org,direct](https://git.fjla.uk/api/packages/owlboard/go,https://proxy.golang.org,direct)
|
||||||
12
buf.gen.yaml
12
buf.gen.yaml
@@ -1,12 +0,0 @@
|
|||||||
version: v1
|
|
||||||
plugins:
|
|
||||||
- plugin: go
|
|
||||||
out: gen/go
|
|
||||||
opt: paths=source_relative
|
|
||||||
- plugin: ts-proto
|
|
||||||
path: ./node_modules/ts-proto/protoc-gen-ts_proto
|
|
||||||
out: gen/ts
|
|
||||||
opt:
|
|
||||||
- esModuleInterop=true
|
|
||||||
- outputJsonMethods=true
|
|
||||||
- forceLong=string
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
# buf.work.yaml
|
|
||||||
version: v1
|
|
||||||
directories:
|
|
||||||
- protos
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package rail.v1;
|
|
||||||
option go_package = "git.fjla.uk/owlboard/backend-data-contracts";
|
|
||||||
|
|
||||||
message PisReferenceList {
|
|
||||||
repeated PisMapping entries = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message PisMapping {
|
|
||||||
string code = 1;
|
|
||||||
string operator = 2;
|
|
||||||
repeated string stops = 3;
|
|
||||||
fixed64 stops_xxh4 = 4; // XXH4 Hash for fast lookup of exact match
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
syntax = "proto3";
|
syntax = "proto3";
|
||||||
|
|
||||||
package rail.v1;
|
package rail_backend.v1;
|
||||||
option go_package = "git.fjla.uk/owlboard/generated/go/rail/v1";
|
|
||||||
|
|
||||||
message Metadata {
|
message Metadata {
|
||||||
int64 push_to_queue_time = 1;
|
int64 push_to_queue_time = 1;
|
||||||
16
protos/rail_backend/v1/pis_schema.proto
Normal file
16
protos/rail_backend/v1/pis_schema.proto
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package rail_backend.v1;
|
||||||
|
|
||||||
|
message PisReferenceList {
|
||||||
|
repeated PisMapping entries = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message PisMapping {
|
||||||
|
string code = 1;
|
||||||
|
string toc = 2;
|
||||||
|
repeated string crsStops = 3;
|
||||||
|
fixed64 crsHash = 4; // XXH4 Hash for fast lookup of exact match
|
||||||
|
repeated string tiplocStops = 5;
|
||||||
|
fixed64 tiplocHash = 6;
|
||||||
|
}
|
||||||
@@ -1,11 +1,9 @@
|
|||||||
syntax = "proto3";
|
syntax = "proto3";
|
||||||
|
|
||||||
package rail.v1;
|
package rail_backend.v1;
|
||||||
option go_package = "git.fjla.uk/owlboard/generated/go/rail/v1";
|
|
||||||
|
|
||||||
|
import "rail_backend/v1/common.proto";
|
||||||
import "rail/v1/common.proto";
|
import "rail_backend/v1/schedule_payload.proto";
|
||||||
import "rail/v1/schedule_payload.proto";
|
|
||||||
|
|
||||||
message IngressMessage {
|
message IngressMessage {
|
||||||
string correlation_id = 1;
|
string correlation_id = 1;
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
syntax = "proto3";
|
syntax = "proto3";
|
||||||
|
|
||||||
package rail.v1;
|
package rail_backend.v1;
|
||||||
option go_package = "git.fjla.uk/owlboard/generated/go/rail/v1";
|
|
||||||
|
|
||||||
enum SchedulePayloadType {
|
enum SchedulePayloadType {
|
||||||
VSTP_MESSAGE_TYPE_UNSPECIFIED = 0;
|
VSTP_MESSAGE_TYPE_UNSPECIFIED = 0;
|
||||||
54
schemas/data-ingress/pis-data.json
Normal file
54
schemas/data-ingress/pis-data.json
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"$id": "https://schema.owlboard.info/data-ingress/pis-data.schema.json",
|
||||||
|
"$schema": "https://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "PisObjects",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"code": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "PIS Code - Code that is entered in to the PIS system"
|
||||||
|
},
|
||||||
|
"toc": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 2,
|
||||||
|
"maxLength": 2,
|
||||||
|
"pattern": "^[a-zA-Z]+$",
|
||||||
|
"description": "Two letter TOC Code"
|
||||||
|
},
|
||||||
|
"crsStops": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 3,
|
||||||
|
"maxLength": 3,
|
||||||
|
"pattern": "^[a-zA-Z]+$"
|
||||||
|
},
|
||||||
|
"description": "List of 3ALPHA/CRS Codes"
|
||||||
|
},
|
||||||
|
"crsHash": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 64,
|
||||||
|
"pattern": "^[0-9]+$",
|
||||||
|
"description": "Stringified 64-bit hash"
|
||||||
|
},
|
||||||
|
"tiplocStops": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 4,
|
||||||
|
"maxLength": 7,
|
||||||
|
"pattern": "^[a-zA-Z0-9]+$"
|
||||||
|
},
|
||||||
|
"description": "List of TIPLOC Codes"
|
||||||
|
},
|
||||||
|
"tiplocHash": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 64,
|
||||||
|
"pattern": "^[0-9]+$"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["code", "toc", "crsStops", "crsHash", "tiplocStops", "tiplocHash"],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
25
scripts/build.sh
Normal file
25
scripts/build.sh
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Create clean output directories
|
||||||
|
rm -rf gen && mkdir -p gen/ts gen/go/models
|
||||||
|
|
||||||
|
# Find all .json files
|
||||||
|
FILES=$(find schemas -name "*.json")
|
||||||
|
|
||||||
|
# Initialize the TypeScript Barrel File
|
||||||
|
echo "// Auto-generated barrel file" > gen/ts/index.ts
|
||||||
|
|
||||||
|
for file in $FILES; do
|
||||||
|
# Get a clean name (e.g., data-ingress_pis-mapping)
|
||||||
|
clean_name=$(echo "${file#schemas/}" | sed 's/\//_/g' | sed 's/\.json//g')
|
||||||
|
|
||||||
|
# OGenerate TS
|
||||||
|
npx --yes json-schema-to-typescript "$file" > "gen/ts/${clean_name}.ts"
|
||||||
|
|
||||||
|
# Generate Go
|
||||||
|
go-jsonschema -p models "$file" > "gen/go/models/${clean_name}.go"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "✅ Generated single TS package in gen/ts"
|
||||||
|
echo "✅ Generated single Go package in gen/go/models"
|
||||||
Reference in New Issue
Block a user