Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a927ccc732 | |||
| 817c338745 | |||
| af3c82518c | |||
| 9511db8ee9 | |||
| fc92331238 | |||
| a4e6386fd1 | |||
| 3199037c5a | |||
| a78e749d55 | |||
| fb4d7b2ed6 | |||
| 3d750b1b18 | |||
| f9463f3d29 | |||
| 7fe1be9b48 | |||
| c8731caadd | |||
| 20cb7b101c | |||
| ac3124ff14 | |||
| b29f42c004 | |||
| 051721ce02 | |||
| 04ed0ede29 | |||
| a17e7a5290 | |||
| aab04bb194 |
@@ -16,89 +16,86 @@ jobs:
|
|||||||
- name: Get Version
|
- name: Get Version
|
||||||
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-action@v1.3
|
|
||||||
with:
|
|
||||||
setup_only: true
|
|
||||||
version: '1.63.0'
|
|
||||||
|
|
||||||
- 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:
|
||||||
node-version: '18.18.x'
|
node-version: '18.18.x'
|
||||||
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 typescript
|
|
||||||
echo "$PATH:$(pwd)/ts-proto" >> $GITHUB_PATH
|
|
||||||
|
|
||||||
- name: Generate Code
|
|
||||||
run: buf generate
|
|
||||||
|
|
||||||
- name: Build and Publish TS
|
- name: Build and Publish TS
|
||||||
working-directory: gen/ts
|
working-directory: gen/ts
|
||||||
run: |
|
run: |
|
||||||
npm init -y
|
npm init -y
|
||||||
|
|
||||||
# 1. Create a dynamic index.ts using Namespace exports
|
# Build index.ts
|
||||||
# This converts 'rail_backend/v1/pis_mapping.ts'
|
echo "// Auto-generated" > index.ts
|
||||||
# into 'export * as PisMapping from "./rail_backend/v1/pis_mapping";'
|
find . -maxdepth 1 -name "*.ts" -not -name "index.ts" | sed 's|^\./||; s|\.ts$||' | awk '{
|
||||||
# We use awk to handle the naming conversion (snake_case to PascalCase-ish)
|
# Use gsub to turn hyphens into underscores so we can split easily
|
||||||
find . -name "*.ts" -not -name "index.ts" | sed 's|^\./||; s|\.ts$||' | awk -F'/' '{
|
clean = $0;
|
||||||
name=$NF;
|
gsub(/-/, "_", clean);
|
||||||
gsub(/_/, "", name);
|
|
||||||
printf "export * as %s from \"./%s\";\n", toupper(substr(name,1,1)) substr(name,2), $0
|
n = split(clean, parts, "_");
|
||||||
}' > index.ts
|
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
|
||||||
|
|
||||||
# 2. Update package.json
|
VERSION="${{ steps.get_version.outputs.VERSION }}"
|
||||||
jq '.name = "@owlboard/backend-data-contracts" |
|
jq --arg ver "$VERSION" \
|
||||||
.version = "${{ steps.get_version.outputs.VERSION }}" |
|
--arg name "@owlboard/backend-data-contracts" \
|
||||||
.type = "module" |
|
'.name = $name | .version = $ver | .type = "module" | .main = "./dist/index.js" | .types = "./dist/index.d.ts"' \
|
||||||
.main = "./dist/index.js" |
|
package.json > package.json.new && mv package.json.new package.json
|
||||||
.types = "./dist/index.d.ts" |
|
|
||||||
.publishConfig = { "registry": "https://git.fjla.uk/api/packages/owlboard/npm" }' \
|
|
||||||
package.json > temp.json && mv temp.json package.json
|
|
||||||
|
|
||||||
# 3. Compile
|
# Compile
|
||||||
npx tsc index.ts --declaration --module esnext --target es2022 --moduleResolution node --outDir dist/
|
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
|
npm publish
|
||||||
env:
|
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.PACKAGE_PUSH }}
|
|
||||||
|
|
||||||
- name: Publish Go
|
- name: Publish Go
|
||||||
run: |
|
run: |
|
||||||
# 1. Setup variables
|
VERSION="v${{ steps.get_version.outputs.VERSION }}"
|
||||||
VERSION="${{ steps.get_version.outputs.VERSION }}"
|
|
||||||
MOD_NAME="git.fjla.uk/owlboard/backend-data-contracts"
|
MOD_NAME="git.fjla.uk/owlboard/backend-data-contracts"
|
||||||
# Create a temporary directory structure that matches Go proxy requirements
|
ZIP_ROOT="/tmp/go_upload"
|
||||||
DEST_DIR="temp_zip/$MOD_NAME@$VERSION"
|
FULL_PATH="$ZIP_ROOT/$MOD_NAME@$VERSION"
|
||||||
|
|
||||||
# 2. Generate go.mod and tidy
|
# 1. Prepare
|
||||||
cd gen/go
|
cd gen/go
|
||||||
go mod init $MOD_NAME
|
|
||||||
go mod tidy
|
|
||||||
|
|
||||||
# 3. Move files into the required nested structure
|
# 2. Initialize the module
|
||||||
mkdir -p "../../$DEST_DIR"
|
go mod init "$MOD_NAME"
|
||||||
cp -r . "../../$DEST_DIR/"
|
|
||||||
|
|
||||||
# 4. Zip from the temp root so the internal paths are correct
|
# 3. Create the structure
|
||||||
cd ../../temp_zip
|
mkdir -p "$FULL_PATH"
|
||||||
zip -r ../module.zip .
|
|
||||||
|
|
||||||
# 5. Upload with the explicit version
|
# 4. Copy the CONTENTS of models to the root of the module
|
||||||
cd ..
|
# This flattens the structure so the .go files are next to go.mod
|
||||||
curl -f -v --user "owlbot:${{ secrets.PACKAGE_PUSH }}" \
|
cp -r models/* "$FULL_PATH/"
|
||||||
--upload-file module.zip \
|
cp go.mod "$FULL_PATH/"
|
||||||
|
|
||||||
|
# 5. Zip and Upload
|
||||||
|
cd "$ZIP_ROOT"
|
||||||
|
zip -r -D "$GITHUB_WORKSPACE/module.zip" .
|
||||||
|
|
||||||
|
curl -f --user "owlbot:${{ secrets.PACKAGE_PUSH }}" \
|
||||||
|
--upload-file "$GITHUB_WORKSPACE/module.zip" \
|
||||||
"${{ github.server_url }}/api/packages/owlboard/go/upload?version=$VERSION"
|
"${{ github.server_url }}/api/packages/owlboard/go/upload?version=$VERSION"
|
||||||
40
README.md
40
README.md
@@ -1,32 +1,18 @@
|
|||||||
# backend-data-contracts
|
# backend-data-contracts
|
||||||
|
|
||||||
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.
|
This repository is the single source of truth for all schema definitions used across the Owlboard backend communication and storage services. Language specific types are generated here and published to the Gitea package repository linked to the repo.
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
The Protobuf files defined here serve as the immutable data contract for:
|
|
||||||
1. **Ingress Logic:** Ensuring the Node.js and Go inregrate properly by sharing types.
|
|
||||||
2. **Message Payloads:** Defining messages for cross-service communication.
|
|
||||||
3. **Persistence:** Defining the expected structure of documents in MongoDB for the Go processing services.
|
|
||||||
|
|
||||||
## Directory Structure
|
## Directory Structure
|
||||||
|
|
||||||
| Path | Description | Contents |
|
| Path | Description |
|
||||||
| :--- | :--- | :--- |
|
| :--- | :--- |
|
||||||
| `proto/` | **Source of Truth.** Contains the Protobuf schema definitions. | `rail_backend/v1/*.proto` |
|
| `schemas` | JSON Schema files organised into clear subfolders |
|
||||||
| `buf.yaml` | **Workspace Config.** Defines linting and breaking change rules (Buf v2). | Workspace settings |
|
| `scripts` | Workflow Scripts |
|
||||||
| `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
|
## Code Generation and Publishing Workflow
|
||||||
|
|
||||||
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 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.
|
||||||
|
|
||||||
1. **Validation:** `buf lint` ensures schemas follow best practices.
|
|
||||||
2. **Generation:** `buf generate` creates Go and TypeScript code using local plugins.
|
|
||||||
3. **TS Release:** Compiles `.ts` to `.js` and publishes to the Gitea NPM Registry as `@owlboard/backend-data-contracts`.
|
|
||||||
4. **Go Release:** Initializes a `go.mod`, zips the artifacts, and pushes them to the Gitea Go Package Registry.
|
|
||||||
|
|
||||||
## To Consume the Contracts
|
## To Consume the Contracts
|
||||||
|
|
||||||
### 1. Go Services
|
### 1. Go Services
|
||||||
@@ -34,4 +20,18 @@ Since the package is hosted in the Gitea Package Registry, you must configure yo
|
|||||||
|
|
||||||
**Setup:**
|
**Setup:**
|
||||||
```bash
|
```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)
|
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)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Typescript Services
|
||||||
|
You will need to configure .npmrc in your projects root directory to point to the correct repo:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
@owlboard:registry=https://git.fjla.uk/api/packages/OwlBoard/npm/
|
||||||
|
```
|
||||||
|
|
||||||
|
Then you can install as usual:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install @owlboard/backend-data-contracts@0.1.0
|
||||||
|
```
|
||||||
16
buf.gen.yaml
16
buf.gen.yaml
@@ -1,16 +0,0 @@
|
|||||||
version: v2
|
|
||||||
managed:
|
|
||||||
enabled: true
|
|
||||||
override:
|
|
||||||
- file_option: go_package_prefix
|
|
||||||
value: github.com/owlboard/backend-data-contracts
|
|
||||||
plugins:
|
|
||||||
- local: protoc-gen-go
|
|
||||||
out: gen/go
|
|
||||||
opt: paths=source_relative
|
|
||||||
- local: ./node_modules/ts-proto/protoc-gen-ts_proto
|
|
||||||
out: gen/ts
|
|
||||||
opt:
|
|
||||||
- esModuleInterop=true
|
|
||||||
- outputJsonMethods=true
|
|
||||||
- forceLong=string
|
|
||||||
9
buf.yaml
9
buf.yaml
@@ -1,9 +0,0 @@
|
|||||||
version: v2
|
|
||||||
modules:
|
|
||||||
- path: protos
|
|
||||||
lint:
|
|
||||||
use:
|
|
||||||
- DEFAULT
|
|
||||||
breaking:
|
|
||||||
use:
|
|
||||||
- FILE
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package rail_backend.v1;
|
|
||||||
|
|
||||||
message Metadata {
|
|
||||||
int64 push_to_queue_time = 1;
|
|
||||||
int64 data_fetch_time = 2;
|
|
||||||
map<string, string> tags = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
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,20 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package rail_backend.v1;
|
|
||||||
|
|
||||||
import "rail_backend/v1/common.proto";
|
|
||||||
import "rail_backend/v1/schedule_payload.proto";
|
|
||||||
|
|
||||||
message IngressMessage {
|
|
||||||
string correlation_id = 1;
|
|
||||||
Metadata tracking_data = 2;
|
|
||||||
oneof payload {
|
|
||||||
UrlReference url_ref = 5;
|
|
||||||
SchedulePayload schedule_payload = 6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message UrlReference {
|
|
||||||
string kind = 1;
|
|
||||||
string url = 2;
|
|
||||||
}
|
|
||||||
43
schemas/data-ingress/mq-file-update.json
Normal file
43
schemas/data-ingress/mq-file-update.json
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"$id": "https://schema.owlboard.info/data-ingress/mq-file-update.schema.json",
|
||||||
|
"$schema": "https://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "MQFileUpdate",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"service_name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Name of the service submitting the update"
|
||||||
|
},
|
||||||
|
"data_type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["file"],
|
||||||
|
"description": "The type of data contained in this message object"
|
||||||
|
},
|
||||||
|
"sent_timestamp": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Unix timestamp representing the time the message was sent"
|
||||||
|
},
|
||||||
|
"data_kind": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["pis", "timetable", "knowledgebase"],
|
||||||
|
"description": "The data type contained in the file. Currently supported PIS: PIS Data, Timetable: CIF or VSTP Data, Knowledgebase: Station Data"
|
||||||
|
},
|
||||||
|
"payload": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"version": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The version string from the package source"
|
||||||
|
},
|
||||||
|
"filepath": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The full path to the file, including protocol (eg. s3://) where appropriate"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["version", "filepath"],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["service_name", "data_type", "sent_timestamp", "data_kind", "payload"],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
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 contracts "$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