From be577bc62bd0d5fd99bb5418523ab7c6b00fcc6b Mon Sep 17 00:00:00 2001 From: Marks Polakovs Date: Sun, 29 May 2022 10:24:57 +0100 Subject: [PATCH 1/6] WIP: developer documentation --- README.md | 69 +----------------- bundle-src/package.json | 3 +- docs/00-getting-started.md | 142 +++++++++++++++++++++++++++++++++++++ docs/01-code-structure.md | 28 ++++++++ docs/02-data-model.md | 0 docs/03-testing.md | 0 docs/04-workflow.md | 0 docs/05-deployment.md | 0 docs/README.md | 16 +++++ docs/images/00-cb-user.png | Bin 0 -> 111728 bytes 10 files changed, 189 insertions(+), 69 deletions(-) create mode 100644 docs/00-getting-started.md create mode 100644 docs/01-code-structure.md create mode 100644 docs/02-data-model.md create mode 100644 docs/03-testing.md create mode 100644 docs/04-workflow.md create mode 100644 docs/05-deployment.md create mode 100644 docs/README.md create mode 100644 docs/images/00-cb-user.png diff --git a/README.md b/README.md index 710a4055..11aea770 100644 --- a/README.md +++ b/README.md @@ -8,74 +8,7 @@ This repo houses (hopefully) everything related to doing graphics overlays for Y ## Developing -You will need [Node.js](https://nodejs.org/en/) and [Yarn](https://yarnpkg.com/getting-started/install). If you want to work on the API backend, you will also need a [Couchbase Server](https://docs.couchbase.com/server/current/install/install-intro.html) (7.1 or later) - you can also use [Docker](https://docs.couchbase.com/server/current/install/getting-started-docker.html) for this. Finally, you will need [Redis](https://redis.io/docs/getting-started/). - -There is a docker-compose.yml file that should set all this up if you run `docker compose up` - though it hasn't been updated in a while so might not fully work. - -First, install NodeCG - we're using our own fork that has some small fixups: - -```shell -$ git clone --branch ystv https://github.com/ystv/nodecg.git -``` - -Then, clone this repo inside the `bundles` folder: - -```shell -$ git clone https://github.com/ystv/ystv-sports-graphics.git bundles/ystv-sports-graphics -``` - -Open it and run `yarn`. - -### Backend/UI - -`cd scores-src`. - -Create a .env file that looks something like this: - -``` -DB_CONNECTION_STRING=couchbase://your.couchbase.server -DB_USER=sports-scores -DB_PASSWORD=password -DB_BUCKET=sports-scores -REDIS_CONNECTION_STRING=redis://localhost -PUBLIC_API_BASE=http://localhost:8000/api -``` - -In Couchbase, create a bucket and user called `sports-scores` (or whatever you used above). - -Then, run `yarn dev` and go to `http://localhost:3000`. If something didn't work check the console for clues. - -### Graphics - -Create a file in the NodeCG `cfg` directory called `ystv-sports-graphics.json` that looks like this: - -```json -{ - "scoresService": { - "apiURL": "http://localhost:8000/api" - } -} -``` - -Optionally, create a `nodecg.json` in the same place that looks like this: - -```json -{ - "logging": { - "console": { - "enabled": true, - "timestamps": true, - "level": "debug" - } - } -} -``` - -This will ensure you get logging. - -`cd bundle-src` and run `yarn bundle:dev` to start a live-reloading dev server, or `yarn bundle:build` to build the bundle once. - -In a second terminal, run `yarn nodecg` (in the `ystv-sports-graphics` folder) and go to `http://localhost:9090`. +Take a look at the [docs folder](./docs) for instructions on getting started, as well as an overview of the codebase. ## Deploying diff --git a/bundle-src/package.json b/bundle-src/package.json index 78d19ff5..ec3af46b 100644 --- a/bundle-src/package.json +++ b/bundle-src/package.json @@ -13,7 +13,8 @@ "build": "cross-env NODE_ENV=production webpack", "bundle:storybook": "start-storybook -p 6006 --no-open", "build-storybook": "build-storybook", - "bundle:schema": "mkdir -p schemas && json2ts -i schemas/ -o src/common/types/ && json2ts -i configschema.json -o src/common/types/config.d.ts" + "bundle:schema": "mkdir -p schemas && json2ts -i schemas/ -o src/common/types/ && json2ts -i configschema.json -o src/common/types/config.d.ts", + "nodecg": "node ../../../index.js" }, "repository": "https://github.com/ystv/ystv-sports-graphics.git", "private": true, diff --git a/docs/00-getting-started.md b/docs/00-getting-started.md new file mode 100644 index 00000000..e2c0c1d1 --- /dev/null +++ b/docs/00-getting-started.md @@ -0,0 +1,142 @@ +# Getting Started + +There's a few things you'll need on your laptop before you can start hacking on sports-graphics: + +- Node.js +- Yarn +- Couchbase Server +- Redis +- NodeCG +- Git +- The code itself + +In addition, while it's not necessary, we recommend using [Docker](https://www.docker.com/get-started/) to install Couchbase Server, especially if you're on a M1 MacBook. + +It sounds like a long list, but you only need to go through it once and you're set! Let's get started. + +## Node.js + +Go to the [Node](https://nodejs.org/en/) website and download the latest LTS (long term support) version. Alternatively if you're on a Mac and have [Homebrew](http://homebrew.sh/) you can run `brew install node@16`. + +## Yarn + +Yarn is a package manager for Node, specifically an alternative to the usual npm. There's a few reasons this project uses Yarn (mostly "the original developers prefer it to npm"). + +Follow the [Yarn installation instructions](https://yarnpkg.com/getting-started/install) to get it. + +## Couchbase Server + +Couchbase is the database that we use for storing event data. For this example we'll install it using Docker, though you can install it [directly](https://www.couchbase.com/downloads) (except on a M1 MacBook where Docker is the only way currently). + +First, create a volume to keep the data around between restarts: + +```sh +$ docker volume create cbdata +``` + +Then launch the container: + +```sh +$ docker run -d -v cbdata:/opt/couchbase/var/lib/couchbase --restart=always -p 8091-8096:8091-8096 -p 11207-11211:11207-11211 -p 18091-18096:18091-18096 --name cb couchbase/server:community-7.1.0 +``` + +(if you're on a M1 MacBook substitute the last part for `couchbase/server:community-7.1.0-aarch64`) + +Then go to http://localhost:8091 (refresh a few times if you get nothing). Select "Setup New Cluster" and walk through the setup steps until you get to the Configure screen. Change the Data memory quota to 512MB, and leave everything else at the defaults. + +Then go to the Buckets tab on the left and click Add Bucket on the top-right. Name it `sports-scores` and leave everything else as the defaults. + +Finally, go into the Security tab, and create a user with the username `sports-scores`, password `password`, and Full Admin permissions. It should look like this: + +![](./images/00-cb-user.png) + +## Redis + +Redis is a "micro-database" that some things (namely real-time updates) rely on. + +We'll also install Redis using Docker. Run the following command, and you're all set. + +``` +$ docker run -d --restart=always -p 6379:6379 redis:6-alpine +``` + +## Git + +Git is the system we use to manage our code and combine the work of multiple developers. Get it from the [Git website](https://git-scm.com/downloads). + +## NodeCG + +NodeCG is the framework that powers the graphics themselves, and our code needs to be placed inside of it. YSTV uses a fork of NodeCG to work around some issues in the upstream version. + +Get the NodeCG code: + +```shell +$ git clone --branch ystv https://github.com/ystv/nodecg.git +``` + +Inside the `nodecg` folder it just created, run `npm ci --production` to install NodeCG's dependencies. + +While we're here, let's set up some bits we'll need later: in the `cfg` folder create a file called `ystv-sports-graphics.json` with the following contents: + +```json +{ + "scoresService": { + "apiURL": "http://localhost:8000/api" + } +} +``` + +Create another file in the `cfg` folder called `nodecg.json` with these contents: + +```json +{ + "logging": { + "console": { + "enabled": true, + "timestamps": true, + "level": "debug" + } + } +} +``` + +## sports-scores code + +Go into the `nodecg/bundles` folder and run this command: + +``` +git clone https://github.com/ystv/ystv-sports-graphics.git +``` + +Open `ystv-sports-graphics` in your code editor of choice. + +# Check it all worked + +First, to get all the dependencies our code is built upon, invoke Yarn: + +```sh +$ yarn +``` + +Then, configure it to talk to your Couchbase and Redis. Inside the `scores-src` folder, create a file called `.env.local` and put in the following values: + +``` +DB_CONNECTION_STRING=couchbase://localhost +DB_USER=sports-scores +DB_PASSWORD=password +DB_BUCKET=sports-scores +REDIS_CONNECTION_STRING=redis://localhost +``` + +Next, run the code and see if it works! + +```sh +# inside the scores-src folder +$ yarn dev +``` + +Watch the output and look for a line starting with `No existing application data found.`. Copy the long token at the end, and go to http://localhost:3000/bootstrap. Enter that token there, then set up a user (the username and password don't matter). Then you should be able to get to the home screen! + +Finally, let's check that we have NodeCG set up. Open a new terminal, switch to the `bundle-src` folder, and run `yarn bundle:build`, then `yarn nodecg`, then go to http://localhost:9090. + +You're all set! In the [next part](./01-code-structure.md), we'll go on a quick tour of how the code is laid out. diff --git a/docs/01-code-structure.md b/docs/01-code-structure.md new file mode 100644 index 00000000..998b6aef --- /dev/null +++ b/docs/01-code-structure.md @@ -0,0 +1,28 @@ +# Code Structure + +The two most important folders in the codebase are `bundle-src` and `scores-src`, so let's talk about all the others first: + +- `.devcontainer`: old crap that's no longer used +- `.github/workflows`: definitions of our GitHub Actions - discussed later in [Testing](./03-testing.md) +- `.husky`: can be ignored +- `dashboard`, `graphics`: can be ignored (NodeCG requires that they exist at the top level, though they're actually built from `bundle-src`) +- `patches`: local modifications to libraries to work around upstream issues +- `schemas`: a symbolic link to `bundle-src/schemas`, discussed later +- `scripts`: miscellaneous scripts + +Also there's quite a few files at the top level: + +- `.dockerignore`, `.gitignore`, `.prettierignore`: telling various tools which files to not care about +- `.editorconfig`: configures code editors to all follow the same coding style (indent size, line endings, etc.) +- `.eslintrc.js`: configures ESLint, a tool that checks the JavaScript/TypeScript for quality +- `.yarnrc.yml`: configures the Yarn package manager +- `Dockerfile.*`, `client-nginx.conf`: used to build the Docker images - discussed in [Deployment](./05-deployment.md) +- `docker-compose.yml`: used to be used for quickly setting up a development environment, but is outdated now +- `cypress.json`: configures the Cypress test runner - discussed later in [Testing](./03-testing.md) +- `Jenkinsfile`, `Jenkinsfile.prod-release`: used by the Jenkins build automation - discussed in [Deployment](./05-deployment.md) +- `package.json`, `yarn.lock`: specifies all the dependencies of the code + - Note that `bundle-src` and `scores-src` have their own `package.json` files, that are combined using [Yarn Workspaces](https://yarnpkg.com/features/workspaces) + +## scores-src + +scores-src houses the scores management and data entry application. It contains both the client-side and server-side code, as well as some that is common to both. diff --git a/docs/02-data-model.md b/docs/02-data-model.md new file mode 100644 index 00000000..e69de29b diff --git a/docs/03-testing.md b/docs/03-testing.md new file mode 100644 index 00000000..e69de29b diff --git a/docs/04-workflow.md b/docs/04-workflow.md new file mode 100644 index 00000000..e69de29b diff --git a/docs/05-deployment.md b/docs/05-deployment.md new file mode 100644 index 00000000..e69de29b diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..f453be76 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,16 @@ +# Sports Graphics Developer Docs + +If you want to hack on the sports graphics application, or just curious about how it's put together, you've come to the right place! + +These documents are meant to be read in a roughy chronological order, though you can skip back and forth if you like: + +- [Getting Started](./00-getting-started.md) - setting up a local copy of the system to work on +- [Code Structure](./01-code-structure.md) - how things are laid out and where to look for what +- [Data Model](./02-data-model.md) - where we store data and how it's structured +- [Testing](./03-testing.md) - the various bits of automation that check your code for mistakes +- [Workflow](./04-workflow.md) - how to get the code from your laptop into the main codebase +- [Deployment](./05-deployment.md) - the process of that code getting into users' hands + +## Assumed Knowledge + +We will assume that you know at least the basics of JavaScript and React.js. If you don't, there's plenty of resources online that teach it far better than we ever could - for React specifically, the [beta documentation](https://beta.reactjs.org/) provides a great introduction to the key concepts. diff --git a/docs/images/00-cb-user.png b/docs/images/00-cb-user.png new file mode 100644 index 0000000000000000000000000000000000000000..11e841492de690b857d019001f995a5a873166b9 GIT binary patch literal 111728 zcmeFZWmp``)&`10a1taqfsh~p65Iv}77{eLLvRLn*C4^&-QC?KNN^wA-Gc_dP4+(L zJKz42-23DHJo7v~-P66Qx@*;{cfD&>^?Z?%5J5vGK!$>XLKA%__z?;UfgK78&K?N? zIMeWaksJyN)!0x#KuT0VfK1BLOwZ6*7Ygd#mna29MVW87$toX34WQvNKQF+A5%9f7 zqH{kbpF$EPOZys&DZ8gD-BuNVG&L-T)maVmsk``g1Orny|=%-NlKcEOnH0X7^JB47W zU@uzw8VI4J7fLv3>+U%2HG~w%B2S?Cy51&Ihn}1~<3Wc~?~Lh1h9aXmlaD~je9ukz z351V|xgz&Iv8a7Eie1JV<6`zXr8!wMDpXw8>bG*};YP%>{@vlp7Zoa%%MK1$Y#=J#cg)ZzrWUsjIO5x}kTGhq)B&UA+79FcE%b_vv zUf=MpW^I!i!Fj5k|H(J<$B~yE1Pi@MPqV%<`!zg|dsEh{EO5jBN@{`_Dw5`-;PZk# z=_k#Ju)Bz;WVSd*Z_~+GJ=n#<@tzb->g#>!sKxA<_msfGGmju+{cN8mMG~J|Jx!Gm zn|kg&ssD}Lxay8ba`I4Nd)I@UEv{(8jpDK8#7(ST7!@_nCz`L5-Ybz${aBvahHbtO z&NC6l{lfkRmX%S+lhGCI_vCZdYH$WUm65Qum`M z?9e?s55;?t4_TpxH@C|F@%@z{ziK#*L?e|i9)6BLq6R`bG~#Ed7gDWZk6w5Zk(urr zZF#oHGHJOZ>W~p4c~mGfm6BJv3B#0pNkzJW4#Vt0guq`6A@e^Qcf%jgFCHW-aH}qL z6QZ|Aw&3xDaX6|;cZ<>)RzbFhVOepr-)w91{l3wbmUw@01-;LPMrf2iLD1*Ob%rlh)oxx7kSwn21

Y+v4%EpfGc;YO-qVtiq_6k3Eae;fVc7 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

    n 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 z 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 literal 0 HcmV?d00001 From 677375d71380b94b40449e3c18ecb08871d4ab1b Mon Sep 17 00:00:00 2001 From: Marks Polakovs Date: Wed, 7 Dec 2022 22:36:52 +0000 Subject: [PATCH 2/6] Document code structure --- docs/00-getting-started.md | 6 ++---- docs/01-code-structure.md | 38 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/docs/00-getting-started.md b/docs/00-getting-started.md index e2c0c1d1..60ca497c 100644 --- a/docs/00-getting-started.md +++ b/docs/00-getting-started.md @@ -26,7 +26,7 @@ Follow the [Yarn installation instructions](https://yarnpkg.com/getting-started/ ## Couchbase Server -Couchbase is the database that we use for storing event data. For this example we'll install it using Docker, though you can install it [directly](https://www.couchbase.com/downloads) (except on a M1 MacBook where Docker is the only way currently). +Couchbase Server is the database that we use for storing event data. For this example we'll install it using Docker, though you can install it [directly](https://www.couchbase.com/downloads) (except on a M1 MacBook where Docker is the only way currently). First, create a volume to keep the data around between restarts: @@ -37,11 +37,9 @@ $ docker volume create cbdata Then launch the container: ```sh -$ docker run -d -v cbdata:/opt/couchbase/var/lib/couchbase --restart=always -p 8091-8096:8091-8096 -p 11207-11211:11207-11211 -p 18091-18096:18091-18096 --name cb couchbase/server:community-7.1.0 +$ docker run -d -v cbdata:/opt/couchbase/var/lib/couchbase --restart=always -p 8091-8096:8091-8096 -p 11207-11211:11207-11211 -p 18091-18096:18091-18096 --name cb couchbase/server:community-7.1.1 ``` -(if you're on a M1 MacBook substitute the last part for `couchbase/server:community-7.1.0-aarch64`) - Then go to http://localhost:8091 (refresh a few times if you get nothing). Select "Setup New Cluster" and walk through the setup steps until you get to the Configure screen. Change the Data memory quota to 512MB, and leave everything else at the defaults. Then go to the Buckets tab on the left and click Add Bucket on the top-right. Name it `sports-scores` and leave everything else as the defaults. diff --git a/docs/01-code-structure.md b/docs/01-code-structure.md index 998b6aef..b10e2f4f 100644 --- a/docs/01-code-structure.md +++ b/docs/01-code-structure.md @@ -2,7 +2,7 @@ The two most important folders in the codebase are `bundle-src` and `scores-src`, so let's talk about all the others first: -- `.devcontainer`: old crap that's no longer used +- `.devcontainer`: used to have a [dev container](https://code.visualstudio.com/docs/devcontainers/containers) definition, except it's grown outdated. We should fix it up one day. - `.github/workflows`: definitions of our GitHub Actions - discussed later in [Testing](./03-testing.md) - `.husky`: can be ignored - `dashboard`, `graphics`: can be ignored (NodeCG requires that they exist at the top level, though they're actually built from `bundle-src`) @@ -25,4 +25,38 @@ Also there's quite a few files at the top level: ## scores-src -scores-src houses the scores management and data entry application. It contains both the client-side and server-side code, as well as some that is common to both. +scores-src houses the scores management and data entry application. It contains both the client-side and server-side code, as well as some that is common to both. Each of these lives in the folders `client`, `server`, and `common` respectively. + +### common + +This has a few top-level files that are useful across both `client` and `server`, for example calculating times. However the most interesting part of `common` is the `sports` subfolder, which has definitions of each of the sport types tha tthe system supports. The exact contents are discussed further in the [Data Model](./02-data-model.md) section. + +### server + +On the server-side the main entry point is `index.server.ts`, which sets up the application's Web server. In the process it imports (among many others): + +- `loggingSetup.ts`, which sets up logging +- `config.ts`, where the structure of the server's configuration is defined + - The actual configuration is in `scores-src/config/{NODE_ENV}.json`, where `{NODE_ENV}` is the value of the `NODE_ENV` environment variable - if none is set it uses the values in `defaults.json`. +- `db.ts`, which houses the logic for connecting to Couchbase Server +- `redis.ts`, ditto for Redis +- `...Routes.ts`, which define the various "routes" (API endpoints), such as `/events`, `/teams` etc. Some of these are discussed in more detail below, but the general principle is that each one exports a function called `createXxxRouter()`, which returns an Express [`Router`](https://expressjs.com/en/guide/routing.html), which `index.ts` combines together. +- `metrics.ts`, which defines our Prometheus metrics, used for debugging issues. +- `bootstrap.ts`, which is responsible for handling setting up the application for the first time. + +There's a few other files in the `server` folder which merit calling out: + +- `auth.ts` has the user authentication logic, including checking passwords and setting session cookies. +- `*.spec.ts` - [tests](./03-testing.md) +- `__mocks__` - mock files for [testing](./03-testing.md) + +The app's functionality is exposed through various APIs (for example, `localhost:8000/api/events`) that all take in and return JSON. The most important ones are: + +- `/events` (`eventsRoutes.ts`) - handles listing events. Notably it doesn't handle things like creating or updating events, instead those are handled by... +- `/events//` (`eventTypeRoutes.ts`) - this one is a little interesting, because it creates a bunch of routers that all do more or less the same thing. Essentially, for each event type (such as `football`, `swimming`, etc.) we create a router at `/events//football`, `/events//swimming`, which handles creating, updating, and deleting events of that type. There's no real reason why this couldn't be a single generic router that determines the event based on the path, this was just simpler. +- `/updates/stream/v2` (`liveRoutes.ts`) - handles WebSocket connections to get real-time notifications about event changes. + - (Historical note: the `v2` comes from [the first iteration of this system](https://github.com/ystv/roses-scores-api/tree/main/update_stream), which also had a `v1`. This one only has `v2`.) + +### client + +The client-side is the part that humans will interact with. It's a relatively typical React app, using React Router for client-side routing (NB: completely unrelated to routers in the `server` section). Everything kicks off in `index.html`, which (through [build-time magic](https://vitejs.dev/)) loads `index.client.tsx` and renders the `App` component from `App.tsx`. From d4a81d0b7d77a7c0e7538e3187983831ec4cedfa Mon Sep 17 00:00:00 2001 From: Marks Polakovs Date: Wed, 7 Dec 2022 23:00:43 +0000 Subject: [PATCH 3/6] Document data model --- docs/02-data-model.md | 90 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/docs/02-data-model.md b/docs/02-data-model.md index e69de29b..a35527d1 100644 --- a/docs/02-data-model.md +++ b/docs/02-data-model.md @@ -0,0 +1,90 @@ +# Data Model + +## NoSQL recap + +As you will have seen, Sports Graphics stores data using Couchbase Server, which is a NoSQL database. This may be a little different to other databases you may have used, especially relational (SQL) ones, so it's perhaps worth briefly recapping the idea. + +In a NoSQL database there's no defined structure to your data - Couchbase Server groups it into "buckets"[^1] but that's about it. The only thing to identify your data is a so-called "key", a unique reference to a bit of data (called a "document"). Given a document's key you can get and update its value - blindingly quickly (as in, sub-millisecond at times). + +The contents of a document can be anything, but most documents are JSON - though there are a few that aren't which are called out below. Beyond that though, there's no rules about what this JSON can store, which we take advantage of. + +Couchbase Server does also allow us to query the contents of the documents using a language called [SQL++](https://docs.couchbase.com/server/current/getting-started/try-a-query.html), which should be familiar if you've used SQL. + +[^1] Couchbase does allow us to further segment the data into scopes and collections, though at the time of writing we only use the default collection. + +## Sports Scores data + +Given all that, how do we use Couchbase? This is perhaps best answered by listing all the "families" of documents we have, grouped by key: + +- `League/`: stores some basic information about "leagues", which are used to group together events (such as all BUCS or Roses events) to make browsing easier +- `EventMeta///`: stores some basic information about an event, such as the name, time, and teams that are playing +- `EventHistory///`: stores the full history of everything that happened at an event - discussed further below +- `Team/`: stores information about a team +- `Attachment/`: used to store team crest images (NB: this is not JSON - we store the raw contents of the image file as the document body, and the file format as the [extended attribute](https://docs.couchbase.com/server/current/learn/data/extended-attributes-fundamentals.html) `mimeType`) +- `User/`: stores information about a user who can access the system, including their name, hash of their password, and what level of access they have. +- `Session/`: used to sign in a user (NB: technically JSON, but the value is only a string representing the user's username) +- `BootstrapState`: used to remember whether this instance of Sports Graphics has been fully set up + +## Event data model + +Briefly mentioned above, but the information for an event is split up into the "metadata" and "history". The metadata stores some basic information like the name, start time, and participating teams, while the history is the full sequence of events. + +The history is the more interesting of the two: it's one big JSON array with objects containing everything that has happened in a match, for example goals or timer starts/stops. Here's an (abridged) example: + +```json +[ + { + "type": "@@init", + "payload": { + // ... + "clock": { + "startingTime": 900000, + "timeLastStartedOrStopped": 0, + "wallClockLastStarted": 0, + "state": "stopped", + "type": "downward" + }, + "scoreAway": 0, + "scoreHome": 0, + "players": { + "away": [], + "home": [] + } + }, + "meta": { + "ts": 1668253910811 + } + }, + { + "type": "netball/startNextQuarter", + "payload": {}, + "meta": { + "ts": 1668263425000 + } + }, + , + { + "type": "netball/goal", + "payload": { + "side": "home", + "player": null + }, + "meta": { + "ts": 1668263941336 + } + } + // ... +] +``` + +Each entry in the history is an object, sometimes referred to as an "action" (if you've written applications that use [Redux](https://redux.js.org/) this will be _very_ familiar - though don't worry if not!), which represents, more or less, "something happened". In the above example, the event was created, a quarter started, and the "home" side scored a goal. + +So how does the system get from that array to "the score is James 1 Derwent 0"? It runs the array of actions through a "reducer" function - the one for Netball lives in [common/sports/netball/index.tsx](../scores-src/src/common/sports/netball/index.tsx). It takes the current state and an action, and returns the new state - for example, given the state `{"scoreHome": 0, "scoreAway": 0}` and the action `{"type": "netball/goal", "payload": { "side": "home" }}`, it will return the state `{"scoreHome": 1, "scoreAway": 0}`. + +But what's the point of going through all this hassle? In a word (or two), time travel! + +If, for example, whoever's pitchside happens to make a mistake and enters a Derwent goal when James scored, they can simply undo the goal action, and the system will re-calculate the score while pretending that the mistaken goal never happened. Alternatively they can edit the action post-factum and the system will re-calculate the score with the corrected goal information. This wouldn't be possible without this data model. + +This is enabled by the `meta.ts` ("time stamp") field, which is also used as an identifier for a specific action - you'll see `ts` thrown around a lot in the codebase. + +So, after all that, why split the metadata and history? In fact, we didn't use to, and only stored the history array, however writing queries for it (such as "give me all events where James College are playing") is a bit of a pain on arrays like that, so the decision was made to split the two. From 3f05925b9e97466a65571504876367403243a621 Mon Sep 17 00:00:00 2001 From: Marks Polakovs Date: Wed, 7 Dec 2022 23:02:01 +0000 Subject: [PATCH 4/6] Fix markdown syntax --- docs/02-data-model.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/02-data-model.md b/docs/02-data-model.md index a35527d1..2020c6e4 100644 --- a/docs/02-data-model.md +++ b/docs/02-data-model.md @@ -10,7 +10,7 @@ The contents of a document can be anything, but most documents are JSON - though Couchbase Server does also allow us to query the contents of the documents using a language called [SQL++](https://docs.couchbase.com/server/current/getting-started/try-a-query.html), which should be familiar if you've used SQL. -[^1] Couchbase does allow us to further segment the data into scopes and collections, though at the time of writing we only use the default collection. +[^1]: Couchbase does allow us to further segment the data into scopes and collections, though at the time of writing we only use the default collection. ## Sports Scores data From 2e7b9d2707ce0d3a284c763c7cf8d0e42cdc4bd5 Mon Sep 17 00:00:00 2001 From: Marks Polakovs Date: Wed, 7 Dec 2022 23:21:13 +0000 Subject: [PATCH 5/6] Document protocol --- docs/README.md | 2 + docs/a-live-protocol.md | 139 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 docs/a-live-protocol.md diff --git a/docs/README.md b/docs/README.md index f453be76..5c9ec925 100644 --- a/docs/README.md +++ b/docs/README.md @@ -11,6 +11,8 @@ These documents are meant to be read in a roughy chronological order, though you - [Workflow](./04-workflow.md) - how to get the code from your laptop into the main codebase - [Deployment](./05-deployment.md) - the process of that code getting into users' hands +There's also a rundown of the [data sync protocol](./a-live-protocol.md) - this isn't required reading but may be useful. + ## Assumed Knowledge We will assume that you know at least the basics of JavaScript and React.js. If you don't, there's plenty of resources online that teach it far better than we ever could - for React specifically, the [beta documentation](https://beta.reactjs.org/) provides a great introduction to the key concepts. diff --git a/docs/a-live-protocol.md b/docs/a-live-protocol.md new file mode 100644 index 00000000..8918f2e6 --- /dev/null +++ b/docs/a-live-protocol.md @@ -0,0 +1,139 @@ +# Appendix A: Live Protocol + +The Sports Graphics live data protocol uses JSON over WebSockets. Every object sent by either the client or server must have a `kind` property. + +The API can operate in two modes: + +- in "state mode", whenever an event changes, the client is sent the complete state of the event right now +- in "actions mode", whenever an event changes, the client is sent an object describing the change + +## Connecting + +The client should open a WebSocket connection to `https:///api/updates/stream/v2?token=&mode=`, where `` is a valid session ID and `mode` is either `state` or `actions` (discussed below). Optionally it can supply the `sid` and `last_mid` query parameters, discussed later. + +If the user is valid, the server will send a `HELLO` message: + +```json +{ + "kind": "HELLO", + "sid": "", + "subs": [], + "mode": "" +} +``` + +`sid` will be a random string that the client should remember for later use. `subs` will be all the event IDs that the client is subscribed to, which will be an empty array the first time it connects. + +The server will now start periodically sending `PING` messages: + +```json +{ "kind": "PING" } +``` + +The client must reply to each `PING` with a `PONG`, or the server will close the connection: + +```json +{ "kind": "PONG" } +``` + +The client can also send its own `PING`s, to which the server will reply with `PONG`s. + +## Getting Event Updates + +To subscribe to an event the client sends a `SUBSCRIBE` message: + +```json +{ + "kind": "SUBSCRIBE", + "to": "Event///" +} +``` + +If successful the server will reply with a `SUBSCRIBE_OK` message: + +```json +{ + "kind": "SUBSCRIBE_OK", + "to": "Event///", + "current": {} +} +``` + +The value of `current` depends on the mode: + +- in state mode it is an object representing the current state of the event +- in actions mode it is an array with the full actions history so far + +The server will now start sending messages whenever the event changes. In state mode it will send `CHANGE` messages: + +```json +{ + "kind": "CHANGE", + "changed": "Event///", + "mid": "", + "data": {} +} +``` + +`data` is the current state of the event. + +In actions mode the server will send `ACTION` messages: + +```json +{ + "kind": "ACTION", + "event": "Event///", + "mid": "", + "type": "", + "payload": {}, + "meta": {} +} +``` + +`payload` and `meta` are the action payload and metadata. + +In both modes, `mid` is the Message ID - the client should remember the last `mid` it receives. + +To unsubscribe, the client can send a `UNSUBSCRIBE`: + +```json +{ + "kind": "UNSUBSCRIBE", + "to": "Event///" +} +``` + +The server will reply with a `UNSUBSCRIBE_OK`. + +### Resyncing + +A resync is when the server sends the client the complete event state (in state mode) or complete actions history (in actions mode). The client can request a resync by sending a `RESYNC` message: + +```json +{ + "kind": "RESYNC", + "what": "Event///" +} +``` + +The server can also initiate a resync at any time. In either case, the server will either send the client a `CHANGE` as above (in state mode), or a `BULK_ACTIONS` (in actions mode): + +```json +{ + "kind": "BULK_ACTIONS", + "event": "Event///", + "actions": [] +} +``` + +The client is expected to discard its knowledge of the action history and use the `actions` it has just received. + +## Reconnecting + +The internet is a scary place and connections can be lost. The server supports replaying the messages that a client missed while it was disconnected. + +To do so, the client should establish a new connection as in [Connecting](#connecting), but also include the `sid` and `last_mid` query parameters, which should be the `sid` the server first sent on connecting and the `mid` of the last message it received before disconnecting. + +If successful, the server will send a `HELLO` with a matching `sid`, followed by all the `CHANGE`s or `ACTION`s the client missed. The client will be re-subscribed with no need to send a `SUBSCRIBE` message. + +If unsuccessful, the server will send a `HELLO` with a different `sid`. In this case the client is expected to discard its knowledge of the event state and send a `SUBSCRIBE` as if connecting for the first time. From 71e56b4fd4bbe97230bf299fe86bbc1378784f2e Mon Sep 17 00:00:00 2001 From: Marks Polakovs Date: Wed, 7 Dec 2022 23:22:58 +0000 Subject: [PATCH 6/6] Clarifications --- docs/a-live-protocol.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/a-live-protocol.md b/docs/a-live-protocol.md index 8918f2e6..3fa6eb36 100644 --- a/docs/a-live-protocol.md +++ b/docs/a-live-protocol.md @@ -5,11 +5,11 @@ The Sports Graphics live data protocol uses JSON over WebSockets. Every object s The API can operate in two modes: - in "state mode", whenever an event changes, the client is sent the complete state of the event right now -- in "actions mode", whenever an event changes, the client is sent an object describing the change +- in "actions mode", whenever an event changes, the client is sent an object describing the change (per the [data model](./02-data-model.md)) ## Connecting -The client should open a WebSocket connection to `https:///api/updates/stream/v2?token=&mode=`, where `` is a valid session ID and `mode` is either `state` or `actions` (discussed below). Optionally it can supply the `sid` and `last_mid` query parameters, discussed later. +The client should open a WebSocket connection to `https:///api/updates/stream/v2?token=&mode=`, where `` is a valid session ID and `mode` is either `state` or `actions` (discussed below). If no mode is specified, `state` will be used. Optionally it can supply the `sid` and `last_mid` query parameters, discussed later. If the user is valid, the server will send a `HELLO` message: