Skip to content

Commit

Permalink
Merge pull request #53 from swrlab/dev/50-core-id-migration
Browse files Browse the repository at this point in the history
🚧 BREAKING CHANGES for `serviceIds` 🚧
[#50] Core-ID migration
  • Loading branch information
frytg authored Mar 25, 2021
2 parents 18fa1df + 73f3eff commit 8ab283f
Show file tree
Hide file tree
Showing 41 changed files with 1,232 additions and 489 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@ yarn-error.log*

# keys
keys/*.json
.env

# Dependency directories
node_modules/
coverage
lib-cov
.nyc_output

# Optional npm cache directory
.npm
Expand Down
29 changes: 24 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,51 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.0.0-beta1] - 2021-03-24

🚧 BREAKING CHANGES for `serviceIds` 🚧

### Added

- New detailed return output when posting events for each service (`published`, `blocked`, `failed`)
- Improved log output in JSON format for better monitoring

### Changed

- Now using `services` with required fields `type`, `externalId` and `publisherId` to identify a publishers' channel

### Removed

- No longer using `serviceIds` as required identification keys
- `event` in the POST body for new events is now called `name` and is no longer required
- variable is inserted using the event name provided by the URL

## [0.1.7] - 2021-03-18

### Changes
### Changed

- Add auth verification for unit tests
- Check `content-type` in unit tests
- Update unit tests for new ESLint config

## [0.1.6] - 2021-03-17

### Changes
### Changed

- Allow optional fields in POST /events to be `null`
- Remove field `isInternal` from POST /events

## [0.1.5] - 2021-03-17

### Changes
### Changed

- Preventing errors when `institution.name` isn't properly set in the user account
- Enforcing separation between dev/prod topics and subscriptions
- New onboarding and naming docs

## [0.1.4] - 2021-03-16

### Changes
### Changed

- Removed `attribution` from required media fields of new events
- Added OpenAPI documentation to the docs
Expand All @@ -39,7 +58,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [0.1.3] - 2021-03-16

### Changes
### Changed

- Hotfix `content-type` bug for error responses
- Updated endpoint structure for `/events`
Expand Down
3 changes: 3 additions & 0 deletions Dockerfile.ingest
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# Load desired node pckg
FROM node:14.16-alpine

# Add python
RUN apk add g++ make python

# Create app directory
WORKDIR /web/app

Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,18 @@ This source code is provided under EUPL v1.2, except for the [`spdx-exceptions`]
| NPM | `firebase-admin` | [Apache License 2.0](https://github.com/firebase/firebase-admin-node/blob/master/LICENSE) |
| NPM | `jsonwebtoken` | [MIT](https://github.com/auth0/node-jsonwebtoken/blob/master/LICENSE) |
| NPM | `moment` | [MIT](https://github.com/moment/moment/blob/develop/LICENSE) |
| NPM | `node-crc` | [MIT](https://github.com/magiclen/node-crc/blob/master/LICENSE) |
| NPM | `node-fetch` | [MIT](https://github.com/node-fetch/node-fetch/blob/master/LICENSE.md) |
| NPM | `slug` | [MIT](https://github.com/Trott/slug/blob/master/LICENSE) |
| NPM | `swagger-ui-express` | [MIT](https://github.com/scottie1984/swagger-ui-express/blob/master/LICENSE) |
| NPM | `uuid` | [MIT](https://github.com/uuidjs/uuid/blob/master/LICENSE.md) |
| NPM | `winston` | [MIT](hhttps://github.com/winstonjs/winston/blob/master/LICENSE) |
| NPM DEV | `@swrlab/eslint-plugin-swr` | [ISC](https://github.com/swrlab/eslint-plugin-swr/) |
| NPM DEV | `@swrlab/swr-prettier-config` | [ISC](https://github.com/swrlab/prettier-config/blob/main/license.md) |
| NPM DEV | `chai` | [MIT](https://github.com/chaijs/chai/blob/master/LICENSE) |
| NPM DEV | `chai-http` | [MIT](https://github.com/chaijs/chai-http/blob/master/package.json) |
| NPM DEV | `docsify-cli` | [MIT](https://github.com/docsifyjs/docsify-cli/blob/master/LICENSE) |
| NPM DEV | `dotenv` | [BSD-2-Clause](<[BSD-2-Clause](https://github.com/motdotla/dotenv/blob/master/LICENSE)>) |
| NPM DEV | `eslint` | [MIT](https://github.com/eslint/eslint/blob/master/LICENSE) |
| NPM DEV | `eslint-plugin-swr` | [ISC](https://github.com/swrlab/eslint-plugin-swr/blob/main/package.json) |
| NPM DEV | `eslint-plugin-chai-friendly` | [MIT](https://github.com/ihordiachenko/eslint-plugin-chai-friendly/blob/master/LICENSE) |
Expand Down
5 changes: 5 additions & 0 deletions config/coreIdPrefixes.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"EventLivestream": "urn:ard:event-livestream:",
"Publisher": "urn:ard:publisher:",
"PermanentLivestream": "urn:ard:permanent-livestream:"
}
11 changes: 9 additions & 2 deletions config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

// import version from package.json
const { version } = require('../package.json')
const coreIdPrefixes = require('./coreIdPrefixes.json')

// check existence of several process vars
if (!process.env.GCP_PROJECT_ID) {
Expand All @@ -23,10 +24,12 @@ const stage = process.env.STAGE
// set config
const serviceName = 'ard-eventhub'
const baseConfig = {
userAgent: `${serviceName}/${version}`,
pubsubPrefix: 'de.ard.eventhub',
coreIdPrefixes,
pubSubPrefix: `de.ard.eventhub.${stage}.`,
stage,
userAgent: `${serviceName}/${version}`,
version,
isDebug: process.env.DEBUG === 'true',
}

// set config based on stages
Expand All @@ -35,6 +38,10 @@ const config = {
...baseConfig,
serviceName: `${serviceName}-dev`,
},
test: {
...baseConfig,
serviceName: `${serviceName}-test`,
},
prod: {
...baseConfig,
serviceName,
Expand Down
12 changes: 6 additions & 6 deletions docs/NAMING.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,17 @@ Pub/Sub includes a number of restrictions around names, keys and values. For all
## Pub/Sub Topics

```txt
<domain-prefix> . <service> . <module> . <stage> . <service-id>
de.ard . eventhub . publisher . dev . 284680
<domain-prefix> . <service> . <stage> . <encoded-core-id>
de.ard . eventhub . dev . urn%3Aard%3Aper...
=> de.ard.eventhub.publisher.dev.284680
=> de.ard.eventhub.dev.urn%3Aard%3Apermanent-livestream%3Aa315d3e482f09e1b
```

## Pub/Sub Subscriptions

```txt
<domain-prefix> . <service> . <module> . <stage> . <institution> . <uid>
de.ard . eventhub . subscription . dev . swr . 9bdb9316-c78a-4ebe-a131-30b2738435a3
<domain-prefix> . <service> . <module> . <stage> . <uid>
de.ard . eventhub . subscription . dev . 9bdb9316-c78a-4ebe-a131-30b2738435a3
=> de.ard.eventhub.subscription.dev.swr.9bdb9316-c78a-4ebe-a131-30b2738435a3
=> de.ard.eventhub.subscription.dev.9bdb9316-c78a-4ebe-a131-30b2738435a3
```
167 changes: 155 additions & 12 deletions docs/QUICKSTART.md
Original file line number Diff line number Diff line change
@@ -1,39 +1,182 @@
# ARD-Eventhub / Quickstart

This guide will help you get started with ARD-Eventhub.
This guide will help you get started with ARD-Eventhub.

No matter if you are a Publisher or Subscriber, you will need a user account to interact with the API. Request one through your contacts at SWR Audio Lab or ARD Online. Admins can reference the Users docs for account registrations.
No matter if you are a Publisher or Subscriber, you will need a user account to interact with the API. Request one through your contacts at SWR Audio Lab or ARD Online. Admins can reference the Users docs for account registrations.

Once this has been set up, check the Authentication docs to learn more about the login and token exchange process.

- [ARD-Eventhub / Quickstart](#ard-eventhub--quickstart)
- [Publishers](#publishers)
- [Importance of External IDs](#importance-of-external-ids)
- [Workflow Example](#workflow-example)
- [Subscribers](#subscribers)
- [Security](#security)
- [Receiver Example](#receiver-example)

## Publishers

If you are a radio station that wants to start publishing events to ARD-Eventhub, follow these easy steps:
If you are a radio station that wants to start publishing events to ARD-Eventhub, follow these easy steps:

- Set up your account and understand the authentication process
- Use the POST `/events/{eventName}` endpoint to add your events
- Note: Even if GET `/topics` does not list your radio station(s) beforehand, the topic(s) will be created during your first published event (response will contain: `"topics": {"de.ard.eventhub.publisher.dev.{serviceId}": "TOPIC_CREATED"}`)
- Note: Even if GET `/topics` does not list your radio station(s) beforehand, the topic(s) will be created during your first published event (response will contain: `"topic": { ..., "status": "TOPIC_CREATED" }`)

It is recommended to use the Eventhub `test` system first, to make sure everything works. Then bring it to production on `prod`. The host names are listed in the Stages document.

Security Note: Every user account has a list of permitted `serviceIds` that they can publish to. If you are receiving an error, the Id could be misspelled, or the user account was wrongly configured by an admin.
Security Note: Every user account can only publish to `publisherId`s from their own institution. If you are receiving an error, the Id could be misspelled, or the user account was wrongly configured by an admin.

### Importance of External IDs

For the Eventhub to work it needs to be able to uniquely identify a service. This is defined as the so-called `externalId` in ARD's new Core API. You might currently know this as _CRID_, which you are using in the TVA documents.

⚠️ Please make sure to use the **exact** `externalId` that you will be using to deliver the metadata of your livestreams to ARD Core (_PermanentLivestream_). When in doubt please reach out to your metadata contacts or to SWR Audio Lab.

> **External ID Requirements and Recommendations**
> The external ID may be provided through the field `externalId` during an entity creation request.
>
> If you do not already deliver content via TVA you are free in your choice of external ID. However, your choice **must** meet the following criteria:
>
> (a) The external ID of a single entity does not change over time
> (b) The external ID is referring to the local entity you want to import
> (c) The external ID is unique in your own local context
> (d) The external ID is unique in the whole ARD context
[Source: developer.ard.de](https://developer.ard.de/core-api-v2-delivering-content#ExternalIDRequirementsRecommendations)

### Workflow Example

In your system for every new event, you might follow a workflow like this:

1. Check if you have `token` from a previous call that has not expired
2. If not found, check if you have a `refreshToken` from a previous call
1. If found exchange it for a new `token`
2. If not found, create a new login
3. POST the event using the pre-defined format. The example below might help you understand the different fields:

```js
{
"start": "2021-03-17T10:04:35+01:00",
"length": 215.2,
"title": "Save your tears",
"artist": "The Weeknd",
"contributors": [
{
"name": "The Weeknd",
"role": "artist",
"normDb": {
"type": "Person",
"id": "12345"
}
}
],
"services": [
{
"type": "PermanentLivestream",
"externalId": "crid://swr.de/282310",
"publisherId": "282310"
}
],
"playlistItemId": "radiomax:SWR3-BAD-MAX:12569153",
"externalId": "M0589810001",
"isrc": null,
"upc": null,
"mpn": null,
"media": [
{
"type": "cover",
"url": "http://rdz-dev:4001/covers/M0589810.001",
"templateUrl": null,
"description": "SWR Cover zu Save your tears von The Weeknd",
"attribution": ""
}
],
"type": "music",
"hfdbIds": [
"swrhfdb1.KONF.12345"
]
}

```

## Subscribers

If you plan to receive events published by other stations, add yourself as one of their subscribers and receive real-time POST webhooks for all published events. Those can then be used to improve your products such as websites and apps during re-broadcasts in the nightly tracks.
If you plan to receive events published by other stations, add yourself as one of their subscribers and receive real-time POST webhooks for all published events. Those can then be used to improve your products such as websites and apps during re-broadcasts in the nightly tracks.

Please be aware that the type of events published to this service may be extended in the future. Make sure to filter them appropriately. The data format should and will always be backwards-compatible, but new fields may be added to this service as needed.
Please be aware that the type of events published to this service may be extended in the future. Make sure to filter them appropriately. The data format should and will always be backwards-compatible, but new fields may be added to this service as needed.

In case of nightly re-broadcasts you should create a permanent subscription and keep this one running 24/7. The filter based on the program schedule should be done on your side. Pub/Sub should not be used to create and delete subscriptions once the re-broadcast starts and ends.
In case of nightly re-broadcasts you should create a permanent subscription and keep this one running 24/7. The filter based on the program schedule should be done on your side. Pub/Sub should not be used to create and delete subscriptions once the re-broadcast starts and ends.

Start receiving events with these steps:
Start receiving events with these steps:

- Set up your account and understand the authentication process
- Use the GET `/topics` endpoint to see a list of available channels (topics) that you can subscribe to
- If a channel is not yet visible, no one has attempted to publish an event to it before. Topics are not created until someone starts publishing
- If a channel is not yet visible, no one has attempted to publish an event to it before. Topics are not created until someone starts publishing
- Use the POST `/subscriptions` endpoint to create your own subscription.
- Check the Google Cloud page ["Receiving messages using Push"](https://cloud.google.com/pubsub/docs/push#receiving_messages) to learn more about the format that you will be receiving those events in
- Check the Google Cloud page ["Receiving messages using Push"](https://cloud.google.com/pubsub/docs/push#receiving_messages) to learn more about the format that you will be receiving those events in
- Use GET `/subcriptions` to verify your new or existing subscriptions

Security Note: When a user is registered, it is linked to a specific institution (_Landesrundfunkanstalt_ or _GSEA_). Users can manage all subscriptions within this institution, so be careful not to delete your colleagues' (production) entries.
With this method you will still have access to all subscriptions, even if a person leaves your institution or their account is deactivated.
With this method you will still have access to all subscriptions, even if a person leaves your institution or their account is deactivated.

### Security

Generally it is recommended to keep your endpoints hidden from public indexes. To be absolutely sure that an event is actually being received from Eventhub, you can make use of the provided JWT token and service account.
For every subscription that you create, the response will (amongst other metadata) also include a field about the used service account:

```js
{
...
"serviceAccount": "[email protected]",
...
}
```

Please note that for now the service account usually contains the same response. However, for future subscriptions, it might contain a different account. Configure your service to validate the appropriate account for each subscription.

### Receiver Example

In a simplified way, your receiver might look something like this (example for NodeJS with Express). The Google Cloud section ["Authentication and authorization by the push endpoint"](https://cloud.google.com/pubsub/docs/push#authentication_and_authorization_by_the_push_endpoint) also holds more information about this process.

```js
// load node packages
const { OAuth2Client } = require('google-auth-library')
const authClient = new OAuth2Client()

// set received serviceAccount
const serviceAccountEmail = '[email protected]'

module.exports = async (req, res) => {
try {
// read token from header
const bearer = req.header('Authorization')
const [, idToken] = bearer.match(/Bearer (.*)/)

// verify token, throws error if invalid
const verification = await authClient.verifyIdToken({
idToken,
})

// check token email vs. subscription email
if(verification?.payload?.email === serviceAccountEmail) {
// get message and metadata from pubsub body
const { attributes, messageId } = req.body.message
const { subscription } = req.body
let data = Buffer.from(req.body.message.data, 'base64').toString()
data = JSON.parse(data)

// request successful, you can now use the received data
console.log({ attributes, messageId, subscription, data })

// close connection
return res.sendStatus(201)
} else {
// user provided valid token but failed email verification
return res.sendStatus(204)
}
} catch (err) {
// request failed or invalid token
return res.sendStatus(204)
}
}
```
Loading

0 comments on commit 8ab283f

Please sign in to comment.