From 2591491bf97e680e6e0d32c8ccaf411981049f5d Mon Sep 17 00:00:00 2001 From: Andreas Hubel Date: Thu, 10 Mar 2022 16:39:24 +0100 Subject: [PATCH 1/9] Draft for referencing corresponding broadcast series / shows for news items --- docs/TYPES.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/TYPES.md b/docs/TYPES.md index 2b3fabc1..0d89017c 100644 --- a/docs/TYPES.md +++ b/docs/TYPES.md @@ -69,7 +69,7 @@ If a live element is starting. This can be an moderation by an anchor, interview ## `news` -This indicates the beginning of the news in general or a new news item. Get as detailed as possible. The `contributors` field can be used to include details of the `author`. `media` may be used to supply additional elements. +This indicates the beginning of the news in general or a new news item. Get as detailed as possible. The `contributors` field should be used to include details of the `author`. `media` may be used to supply additional elements. `show` references the correspoding broadcast series / grouping. ```json { @@ -87,7 +87,10 @@ This indicates the beginning of the news in general or a new news item. Get as d } ], "playlistItemId": "BCS1:cd052498-da90-4308-85d3-046cb15c6840", - "externalId": "TBD", + "externalId": "crid://swr.de/av/406d20f0-d9b8-431f-9e36-e2fa2cf263a5", + "show": { + "externalId": "crid://swr.de/1234567", + }, … } ``` From 30538e21a5f32805beeebf441d92f20703a75d7f Mon Sep 17 00:00:00 2001 From: Andreas Hubel Date: Mon, 28 Mar 2022 18:23:18 +0200 Subject: [PATCH 2/9] Add stub for alternateIds field --- docs/TYPES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/TYPES.md b/docs/TYPES.md index 0d89017c..7a96ab9b 100644 --- a/docs/TYPES.md +++ b/docs/TYPES.md @@ -90,6 +90,7 @@ This indicates the beginning of the news in general or a new news item. Get as d "externalId": "crid://swr.de/av/406d20f0-d9b8-431f-9e36-e2fa2cf263a5", "show": { "externalId": "crid://swr.de/1234567", + "alternateIds": ["https://normdb.ivz.cn.ard.de/sendereihe/427", "urn:ard:show:027708befb6bfe14", "brid://br.de/broadcastSeries/1235"] }, … } From d7046edfe81a25e40b88a6fc70c1988f57b5a1ae Mon Sep 17 00:00:00 2001 From: Andreas Hubel Date: Mon, 6 Mar 2023 19:22:18 +0100 Subject: [PATCH 3/9] Updates after discussion from last week --- docs/TYPES.md | 16 ++++++++--- openapi.json | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++ openapi.yaml | 54 ++++++++++++++++++++++++++++++++++++ 3 files changed, 142 insertions(+), 4 deletions(-) diff --git a/docs/TYPES.md b/docs/TYPES.md index 7a96ab9b..47907979 100644 --- a/docs/TYPES.md +++ b/docs/TYPES.md @@ -88,10 +88,18 @@ This indicates the beginning of the news in general or a new news item. Get as d ], "playlistItemId": "BCS1:cd052498-da90-4308-85d3-046cb15c6840", "externalId": "crid://swr.de/av/406d20f0-d9b8-431f-9e36-e2fa2cf263a5", - "show": { - "externalId": "crid://swr.de/1234567", - "alternateIds": ["https://normdb.ivz.cn.ard.de/sendereihe/427", "urn:ard:show:027708befb6bfe14", "brid://br.de/broadcastSeries/1235"] - }, + "references": [ + { + "type": "Show", + "externalId": "crid://swr.de/1234567", + "alternateIds": ["https://normdb.ivz.cn.ard.de/sendereihe/427", "urn:ard:show:027708befb6bfe14", "brid://br.de/broadcastSeries/1235"] + }, + { + "type": "Article", + "title": "Kommerzielle US-Raumfahrt – Die neue Weltraumökonomie", + "url": "https://www.deutschlandfunkkultur.de/kommerzielle-us-raumfahrt-die-neue-weltraumoekonomie-100.html" + }, + ] … } ``` diff --git a/openapi.json b/openapi.json index a241a1c4..def7e1fb 100644 --- a/openapi.json +++ b/openapi.json @@ -899,6 +899,51 @@ } } }, + "reference": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": [ + "Episode", + "Section", + "Publication", + "Broadcast", + "Show", + "Season", + "Article" + ] + }, + "id": { + "type": "string", + "pattern": "^urn:ard:[a-z0-9-]+:[a-z0-9-]+$", + "example": "urn:ard:show:49267f7d67be180d" + }, + "externalId": { + "type": "string", + "example": "crid://swr.de/123450", + "pattern": "^(c|b)rid://.+$" + }, + "title": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + }, + "alternateIds": { + "type": "array", + "items": { + "type": "string", + "example": "https://normdb.ivz.cn.ard.de/sendereihe/427" + } + } + }, + "required": [ + "type" + ] + }, "eventV1PostBody": { "additionalProperties": false, "required": [ @@ -1028,6 +1073,37 @@ ] } }, + "references": { + "type": "string", + "description": "related external entities", + "nullable": true, + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/reference", + "required": [ + "type", + "externalId" + ] + }, + { + "$ref": "#/components/schemas/reference", + "required": [ + "type", + "id" + ] + }, + { + "$ref": "#/components/schemas/reference", + "required": [ + "type", + "title", + "url" + ] + } + ] + } + }, "playlistItemId": { "type": "string", "description": "Unique identifier (within a publisher) to connect next and playing items if needed", diff --git a/openapi.yaml b/openapi.yaml index 7d6c3c7f..ef5d5b44 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -621,6 +621,41 @@ components: description: Globally unique identifier, created by Eventhub example: 'urn:ard:permanent-livestream:49267f7d67be180d' + reference: + type: object + additionalProperties: false + properties: + type: + type: string + enum: + - Episode + - Section + - Publication + - Broadcast + - Show + - Season + - Article + id: + type: string + pattern: ^urn:ard:[a-z0-9-]+:[a-z0-9-]+$ + example: urn:ard:show:49267f7d67be180d + externalId: + type: string + example: crid://swr.de/123450 + pattern: ^(c|b)rid://.+$ + title: + type: string + url: + type: string + format: uri + alternateIds: + type: array + items: + type: string + example: https://normdb.ivz.cn.ard.de/sendereihe/427 + required: + - type + eventV1PostBody: additionalProperties: false required: @@ -729,6 +764,25 @@ components: minItems: 1 allOf: - $ref: '#/components/schemas/services' + references: + type: string + description: related external entities + nullable: true + items: + oneOf: + - $ref: '#/components/schemas/reference' + required: + - type + - externalId + - $ref: '#/components/schemas/reference' + required: + - type + - id + - $ref: '#/components/schemas/reference' + required: + - type + - title + - url playlistItemId: type: string description: >- From a3ab3b61022bc6a2dbf7b30a9c47bc217b47a027 Mon Sep 17 00:00:00 2001 From: Andreas Hubel Date: Mon, 6 Mar 2023 19:22:47 +0100 Subject: [PATCH 4/9] chore: prettify table --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 1ec03fd8..a6ccf3e3 100644 --- a/README.md +++ b/README.md @@ -20,14 +20,14 @@ These ARD broadcasters are currently sending live metadata via ARD Eventhub: | Broadcaster | TEST | PROD | | ---------------- | ---- | ---- | | BR | - | - | -| HR | ✅ | ✅ | -| MDR | ✅ | ✅ | -| NDR | ✅ | ✅ | -| Radio Bremen | ✅ | ✅ | -| RBB | ⌛️ | - | +| HR | ✅ | ✅ | +| MDR | ✅ | ✅ | +| NDR | ✅ | ✅ | +| Radio Bremen | ✅ | ✅ | +| RBB | ⌛️ | - | | SR | - | - | -| SWR | ✅ | ✅ | -| WDR | ✅ | ⌛️ | +| SWR | ✅ | ✅ | +| WDR | ✅ | ⌛️ | | Deutschlandradio | - | - | ## Get Started and Documentation From dafff2c3e92b068f9a2844a0cc12eb3d420a8040 Mon Sep 17 00:00:00 2001 From: Rafael M Date: Tue, 4 Apr 2023 16:01:13 +0200 Subject: [PATCH 5/9] chore: update node-dependencies --- package.json | 8 ++--- yarn.lock | 84 ++++++++++++++++++++++++++++++---------------------- 2 files changed, 53 insertions(+), 39 deletions(-) diff --git a/package.json b/package.json index 21530ed5..676aab46 100644 --- a/package.json +++ b/package.json @@ -29,12 +29,12 @@ "author": "SWR Audio Lab ", "license": "EUPL-1.2", "dependencies": { - "@google-cloud/datastore": "^7.4.0", + "@google-cloud/datastore": "^7.5.0", "@google-cloud/pubsub": "^3.4.1", "@google-cloud/secret-manager": "^4.2.1", "@swrlab/utils": "1.1.2", "compression": "1.7.4", - "dd-trace": "3.15.0", + "dd-trace": "3.16.0", "dotenv": "16.0.3", "express": "4.18.2", "express-openapi-validator": "5.0.3", @@ -53,13 +53,13 @@ "chai": "^4.3.7", "chai-http": "^4.3.0", "docsify-cli": "^4.4.4", - "eslint": "^8.36.0", + "eslint": "^8.37.0", "eslint-plugin-chai-friendly": "^0.7.2", "license-compliance": "^1.2.5", "mocha": "^10.2.0", "nodemon": "^2.0.22", "prettier": "^2.8.7", - "typescript": "^5.0.2" + "typescript": "^5.0.3" }, "resolutions": { "ansi-regex": "^5.0.1", diff --git a/yarn.lock b/yarn.lock index 8e7ec9fc..10afc6b9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -80,10 +80,10 @@ dependencies: node-gyp-build "^3.9.0" -"@datadog/pprof@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@datadog/pprof/-/pprof-2.0.0.tgz#d6a13587ffc83779e16e271f90e363f69d5c44f6" - integrity sha512-Qsy/IjB1QbPH77FGMgMUEw3PE/oA1wgXhc+Q3cnv88OOs/Q1olEvnEeu59eS2DXUBfDFVK82W2aiMrDnJT2ytA== +"@datadog/pprof@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@datadog/pprof/-/pprof-2.1.0.tgz#acc8a7a2a74442cfd725abc620a5f8505dbe1807" + integrity sha512-nHZ16CuwKfscNF2PKAEPMqdn5AsxHmvurwiFmPd65VoDXKWLX2Ourj/izgL/HJ4Q5LZS/yiV4lsM4d7Xwmw0zQ== dependencies: delay "^5.0.0" node-gyp-build "^3.9.0" @@ -110,14 +110,14 @@ resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.4.0.tgz#3e61c564fcd6b921cb789838631c5ee44df09403" integrity sha512-A9983Q0LnDGdLPjxyXQ00sbV+K+O+ko2Dr+CZigbHWtX9pNfxlaBkMR8X1CztI73zuEyEBXTVjx7CE+/VSwDiQ== -"@eslint/eslintrc@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.0.1.tgz#7888fe7ec8f21bc26d646dbd2c11cd776e21192d" - integrity sha512-eFRmABvW2E5Ho6f5fHLqgena46rOj7r7OKHYfLElqcBfGFHHpjBhivyi5+jOEQuSpdc/1phIZJlbC2te+tZNIw== +"@eslint/eslintrc@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.0.2.tgz#01575e38707add677cf73ca1589abba8da899a02" + integrity sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ== dependencies: ajv "^6.12.4" debug "^4.3.2" - espree "^9.5.0" + espree "^9.5.1" globals "^13.19.0" ignore "^5.2.0" import-fresh "^3.2.1" @@ -125,10 +125,10 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@8.36.0": - version "8.36.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.36.0.tgz#9837f768c03a1e4a30bd304a64fb8844f0e72efe" - integrity sha512-lxJ9R5ygVm8ZWgYdUweoq5ownDlJ4upvoWmO4eLxBYHdMo+vZ/Rx0EN6MbKWDJOSUGrqJy2Gt+Dyv/VKml0fjg== +"@eslint/js@8.37.0": + version "8.37.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.37.0.tgz#cf1b5fa24217fe007f6487a26d765274925efa7d" + integrity sha512-x5vzdtOOGgFVDCUs81QRB2+liax8rFg3+7hqM+QhBG0/G3F1ZsoYl97UrqgHgQ9KKT7G6c4V+aTUCgu/n22v1A== "@fastify/busboy@^1.1.0": version "1.2.1" @@ -201,10 +201,10 @@ dependencies: tslib "^2.1.0" -"@google-cloud/datastore@^7.4.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@google-cloud/datastore/-/datastore-7.4.0.tgz#1db8f1e62e833b2768ea5f9a162e4186987f7686" - integrity sha512-A/NGLFeQROJu6UpGDZ49MY9PZik1+6UVH+YAnGvXUWlVcYVD49EGNGbCftDRSAa/TpYBCm9aSKQ5Vj3NCZlO1A== +"@google-cloud/datastore@^7.5.0": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@google-cloud/datastore/-/datastore-7.5.0.tgz#88da05e11865322a5f95f86c92fd3e82b9c363b8" + integrity sha512-npOKzssHJrULQjmjfwMNOqPomFquPhhL9fjp+FAaRd59qT/36eTfVJ33+dptAZOFSCrlPFQlaZoVjPpD66sqaQ== dependencies: "@google-cloud/promisify" "^3.0.0" arrify "^2.0.1" @@ -1487,16 +1487,16 @@ cssesc@^3.0.0: resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== -dd-trace@3.15.0: - version "3.15.0" - resolved "https://registry.yarnpkg.com/dd-trace/-/dd-trace-3.15.0.tgz#1f19017ada852d54964140ecff1759ac0b0b0ba4" - integrity sha512-/aytNWL4rnwp4bxF0RQLr46DjpACdsKhjfPi2qFxStNKxO6X76+TZZeKoe85mVkDfUo7lMBNNbbQl5P+VHkgow== +dd-trace@3.16.0: + version "3.16.0" + resolved "https://registry.yarnpkg.com/dd-trace/-/dd-trace-3.16.0.tgz#baa76f9cfe047f16a995ace3d515d21d81944ed6" + integrity sha512-ogRsTotTF/dMzGVrULzEPShCf9MP6aHxNS5LQS0igvx7I/gJof0MTCXNGDhLY8X3KYRczolTkYTcJ3S4siiyGg== dependencies: "@datadog/native-appsec" "2.0.0" "@datadog/native-iast-rewriter" "2.0.1" "@datadog/native-iast-taint-tracking" "1.1.1" "@datadog/native-metrics" "^1.5.0" - "@datadog/pprof" "^2.0.0" + "@datadog/pprof" "^2.1.0" "@datadog/sketches-js" "^2.1.0" crypto-randomuuid "^1.0.0" diagnostics_channel "^1.1.0" @@ -2012,15 +2012,20 @@ eslint-visitor-keys@^3.3.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== -eslint@^8.36.0: - version "8.36.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.36.0.tgz#1bd72202200a5492f91803b113fb8a83b11285cf" - integrity sha512-Y956lmS7vDqomxlaaQAHVmeb4tNMp2FWIvU/RnU5BD3IKMD/MJPr76xdyr68P8tV1iNMvN2mRK0yy3c+UjL+bw== +eslint-visitor-keys@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz#c7f0f956124ce677047ddbc192a68f999454dedc" + integrity sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ== + +eslint@^8.37.0: + version "8.37.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.37.0.tgz#1f660ef2ce49a0bfdec0b0d698e0b8b627287412" + integrity sha512-NU3Ps9nI05GUoVMxcZx1J8CNR6xOvUT4jAUMH5+z8lpp3aEdPVCImKw6PWG4PY+Vfkpr+jvMpxs/qoE7wq0sPw== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.4.0" - "@eslint/eslintrc" "^2.0.1" - "@eslint/js" "8.36.0" + "@eslint/eslintrc" "^2.0.2" + "@eslint/js" "8.37.0" "@humanwhocodes/config-array" "^0.11.8" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" @@ -2031,8 +2036,8 @@ eslint@^8.36.0: doctrine "^3.0.0" escape-string-regexp "^4.0.0" eslint-scope "^7.1.1" - eslint-visitor-keys "^3.3.0" - espree "^9.5.0" + eslint-visitor-keys "^3.4.0" + espree "^9.5.1" esquery "^1.4.2" esutils "^2.0.2" fast-deep-equal "^3.1.3" @@ -2058,7 +2063,7 @@ eslint@^8.36.0: strip-json-comments "^3.1.0" text-table "^0.2.0" -espree@^9.0.0, espree@^9.3.1, espree@^9.5.0: +espree@^9.0.0, espree@^9.3.1: version "9.5.0" resolved "https://registry.yarnpkg.com/espree/-/espree-9.5.0.tgz#3646d4e3f58907464edba852fa047e6a27bdf113" integrity sha512-JPbJGhKc47++oo4JkEoTe2wjy4fmMwvFpgJT9cQzmfXKp22Dr6Hf1tdCteLz1h0P3t+mGvWZ+4Uankvh8+c6zw== @@ -2067,6 +2072,15 @@ espree@^9.0.0, espree@^9.3.1, espree@^9.5.0: acorn-jsx "^5.3.2" eslint-visitor-keys "^3.3.0" +espree@^9.5.1: + version "9.5.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.5.1.tgz#4f26a4d5f18905bf4f2e0bd99002aab807e96dd4" + integrity sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg== + dependencies: + acorn "^8.8.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.0" + esprima@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" @@ -5039,10 +5053,10 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== -typescript@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.2.tgz#891e1a90c5189d8506af64b9ef929fca99ba1ee5" - integrity sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw== +typescript@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.3.tgz#fe976f0c826a88d0a382007681cbb2da44afdedf" + integrity sha512-xv8mOEDnigb/tN9PSMTwSEqAnUvkoXMQlicOb0IUVDBSQCgBSaAAROUZYy2IcUy5qU6XajK5jjjO7TMWqBTKZA== uc.micro@^1.0.1, uc.micro@^1.0.5: version "1.0.6" From 8eed1ca700ebf6ea05f83a19010231c9d72c3c65 Mon Sep 17 00:00:00 2001 From: Rafael M Date: Tue, 4 Apr 2023 16:08:04 +0200 Subject: [PATCH 6/9] chore: update version and changelog --- CHANGELOG.md | 4 ++++ openapi.json | 2 +- openapi.yaml | 2 +- package.json | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a83b3466..58d90b3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ 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.5.1] - 2023-04-04 + +- feat: add reference to broadcast series and shows + ## [1.5.0] - 2023-03-14 - fix: stop logging requests on dev diff --git a/openapi.json b/openapi.json index 14388422..eeb3479b 100644 --- a/openapi.json +++ b/openapi.json @@ -11,7 +11,7 @@ "name": "European Union Public License 1.2", "url": "https://spdx.org/licenses/EUPL-1.2.html" }, - "version": "1.5.0" + "version": "1.5.1" }, "externalDocs": { "description": "ARD-Eventhub Documentation", diff --git a/openapi.yaml b/openapi.yaml index f3e737b3..f48c4bbe 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -10,7 +10,7 @@ info: license: name: European Union Public License 1.2 url: "https://spdx.org/licenses/EUPL-1.2.html" - version: 1.5.0 + version: 1.5.1 externalDocs: description: ARD-Eventhub Documentation url: "https://swrlab.github.io/ard-eventhub/" diff --git a/package.json b/package.json index 676aab46..aba4118a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ard-eventhub", - "version": "1.5.0", + "version": "1.5.1", "description": "ARD system to distribute real-time (live) metadata for primarily radio broadcasts.", "main": "./src/ingest/index.js", "engines": { From 810bbf3afecf83fa8cc9a7f7455a138221cd21c3 Mon Sep 17 00:00:00 2001 From: Rafael M Date: Wed, 19 Apr 2023 15:37:42 +0200 Subject: [PATCH 7/9] Update CHANGELOG.md Co-authored-by: Daniel Freytag --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58d90b3b..aac633e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.5.1] - 2023-04-04 -- feat: add reference to broadcast series and shows +- feat: add `references` to external broadcast series and shows ## [1.5.0] - 2023-03-14 From 478555b1769ad17d596b17eeea6e910066ce826c Mon Sep 17 00:00:00 2001 From: Daniel Freytag Date: Wed, 19 Apr 2023 16:11:36 +0200 Subject: [PATCH 8/9] chore: update scheme and tests for `references` --- docs/TYPES.md | 4 +- openapi.json | 2782 ++++++++++++++++++++++--------------------- openapi.yaml | 22 +- package.json | 14 +- test/ingest.test.js | 31 +- yarn.lock | 164 ++- 6 files changed, 1576 insertions(+), 1441 deletions(-) diff --git a/docs/TYPES.md b/docs/TYPES.md index 47907979..4b6f4d2c 100644 --- a/docs/TYPES.md +++ b/docs/TYPES.md @@ -96,9 +96,9 @@ This indicates the beginning of the news in general or a new news item. Get as d }, { "type": "Article", - "title": "Kommerzielle US-Raumfahrt – Die neue Weltraumökonomie", + "title": "Kommerzielle US-Raumfahrt - Die neue Weltraumökonomie", "url": "https://www.deutschlandfunkkultur.de/kommerzielle-us-raumfahrt-die-neue-weltraumoekonomie-100.html" - }, + } ] … } diff --git a/openapi.json b/openapi.json index eeb3479b..d05e5c72 100644 --- a/openapi.json +++ b/openapi.json @@ -1,1365 +1,1419 @@ { - "openapi": "3.0.3", - "info": { - "title": "ARD-Eventhub", - "description": "ARD system to distribute real-time (live) metadata for primarily radio broadcasts.", - "termsOfService": "https://www.ard.de", - "contact": { - "email": "lab@swr.de" - }, - "license": { - "name": "European Union Public License 1.2", - "url": "https://spdx.org/licenses/EUPL-1.2.html" - }, - "version": "1.5.1" - }, - "externalDocs": { - "description": "ARD-Eventhub Documentation", - "url": "https://swrlab.github.io/ard-eventhub/" - }, - "servers": [ - { - "url": "/", - "description": "Local (domain-relative) environment" - } - ], - "tags": [ - { - "name": "auth", - "description": "Authentication services for Eventhub" - }, - { - "name": "events", - "description": "Manage events" - }, - { - "name": "subscriptions", - "description": "Access to subscription management" - }, - { - "name": "topics", - "description": "Access to topics details" - } - ], - "paths": { - "/auth/login": { - "post": { - "tags": ["auth"], - "summary": "Swap login credentials for a token", - "operationId": "authLoginPost", - "requestBody": { - "content": { - "application/json": { - "schema": { - "additionalProperties": false, - "type": "object", - "properties": { - "email": { - "type": "string", - "example": "my-email@example.com" - }, - "password": { - "type": "string", - "example": "my-password" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Authentication successful", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/authResponse" - } - } - } - }, - "400": { - "description": "Bad Request (invalid input)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorBadRequest" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorInternalServerError" - } - } - } - } - } - } - }, - "/auth/refresh": { - "post": { - "tags": ["auth"], - "summary": "Swap refresh token for new id token", - "operationId": "authRefreshPost", - "requestBody": { - "content": { - "application/json": { - "schema": { - "additionalProperties": false, - "type": "object", - "properties": { - "refreshToken": { - "type": "string", - "example": "abcXYZ..." - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Authentication successful", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/authResponse" - } - } - } - }, - "400": { - "description": "Bad Request (invalid input)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorBadRequest" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorInternalServerError" - } - } - } - } - } - } - }, - "/auth/reset": { - "post": { - "tags": ["auth"], - "summary": "Request password reset email", - "operationId": "authResetPost", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "email": { - "type": "string", - "example": "my-email@example.com" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Request successful", - "content": {} - }, - "400": { - "description": "Bad Request (invalid input)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorBadRequest" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorInternalServerError" - } - } - } - } - } - } - }, - "/events/de.ard.eventhub.v1.radio.track.next": { - "post": { - "tags": ["events"], - "summary": "Distribute a next track", - "operationId": "eventPostV1RadioTrackNext", - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "$ref": "#/components/requestBodies/eventV1RadioTrack" - }, - "responses": { - "201": { - "$ref": "#/components/responses/eventV1RadioTrack" - }, - "400": { - "description": "Bad Request (invalid input)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorBadRequest" - } - } - } - }, - "401": { - "description": "Missing authentication", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorUnauthorized" - } - } - } - }, - "403": { - "description": "Invalid authorization", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorForbidden" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorInternalServerError" - } - } - } - } - } - } - }, - "/events/de.ard.eventhub.v1.radio.track.playing": { - "post": { - "tags": ["events"], - "summary": "Distribute a now-playing track", - "operationId": "eventPostV1RadioTrackPlaying", - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "$ref": "#/components/requestBodies/eventV1RadioTrack" - }, - "responses": { - "201": { - "$ref": "#/components/responses/eventV1RadioTrack" - }, - "400": { - "description": "Bad Request (invalid input)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorBadRequest" - } - } - } - }, - "401": { - "description": "Missing authentication", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorUnauthorized" - } - } - } - }, - "403": { - "description": "Invalid authorization", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorForbidden" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorInternalServerError" - } - } - } - } - } - } - }, - "/subscriptions": { - "get": { - "tags": ["subscriptions"], - "summary": "List all subscriptions for this user", - "operationId": "subscriptionList", - "security": [ - { - "bearerAuth": [] - } - ], - "responses": { - "200": { - "description": "Subscriptions found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/subscriptionsList" - } - } - } - }, - "401": { - "description": "Missing authentication", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorUnauthorized" - } - } - } - }, - "403": { - "description": "Invalid authorization", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorForbidden" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorInternalServerError" - } - } - } - } - } - }, - "post": { - "tags": ["subscriptions"], - "summary": "Add a new subscription", - "operationId": "subscriptionPost", - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "description": "New event to be distributed to subscribers.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/subscriptionPost" - } - } - }, - "required": true - }, - "responses": { - "201": { - "description": "Subscription created", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/subscriptionResponse" - } - } - } - }, - "400": { - "description": "Bad Request (invalid input)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorBadRequest" - } - } - } - }, - "401": { - "description": "Missing authentication", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorUnauthorized" - } - } - } - }, - "403": { - "description": "Invalid authorization", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorForbidden" - } - } - } - }, - "404": { - "description": "Topic for subscription not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorNotFound" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorInternalServerError" - } - } - } - } - } - } - }, - "/subscriptions/{name}": { - "get": { - "tags": ["subscriptions"], - "summary": "Get details about a single subscription from this user", - "operationId": "subscriptionsGet", - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "name": "name", - "in": "path", - "description": "`name` of the desired subscription", - "required": true, - "style": "simple", - "explode": false, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Subscription found", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/subscriptionResponse" - } - } - } - } - }, - "401": { - "description": "Missing authentication", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorUnauthorized" - } - } - } - }, - "403": { - "description": "Invalid authorization", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorForbidden" - } - } - } - }, - "404": { - "description": "Subscription not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorNotFound" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorInternalServerError" - } - } - } - } - } - }, - "delete": { - "tags": ["subscriptions"], - "summary": "Remove a single subscription by this user", - "operationId": "subscriptionsDelete", - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "name": "name", - "in": "path", - "description": "`name` of the desired subscription", - "required": true, - "style": "simple", - "explode": false, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Subscription deleted", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/subscriptionDeleted" - } - } - } - }, - "401": { - "description": "Missing authentication", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorUnauthorized" - } - } - } - }, - "403": { - "description": "Invalid authorization", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorForbidden" - } - } - } - }, - "404": { - "description": "Subscription not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorNotFound" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorInternalServerError" - } - } - } - } - } - } - }, - "/topics": { - "get": { - "tags": ["topics"], - "summary": "List all available topics", - "operationId": "topicsGet", - "security": [ - { - "bearerAuth": [] - } - ], - "responses": { - "200": { - "description": "Topics found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/topicResponse" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorInternalServerError" - } - } - } - } - } - } - } - }, - "components": { - "requestBodies": { - "eventV1RadioTrack": { - "description": "New event to be distributed to subscribers. \n \nThe Eventhub format validation expects only a subset of these variables as minimum set. All other fields are technically optional, but **highly encouraged** to be included, so a best-possible metadata exchange is possible. \nThe subset is defined in the list of required fields of Schemas `eventV1PostBody`, resulting in this body: \n```json\n{\n \"type\": \"music\",\n \"start\": \"2020-01-19T06:00:00+01:00\",\n \"title\": \"Song name\",\n \"services\": [ { ... } ],\n \"playlistItemId\": \"swr3-5678\"\n}\n```\nRequired fields not specified in the Schema, will cause your request to fail. \n \nThe `id` is inserted by Eventhub as string-formatted number, but might be a true string in the future, do not expect this string to remain numbers only!\n", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/eventV1PostBody" - } - } - }, - "required": true - } - }, - "responses": { - "eventV1RadioTrack": { - "description": "Event created \n \n*Note:* The first request of an event for an externalId that is not registered yet, will return the status `failed: 1`. This indicates that a new topic for the externalId has been created, and the request needs to be repeated:\n```json\n\"statuses\": {\n \"published\": 0,\n \"blocked\": 0,\n \"failed\": 1\n}\n```\nIf the request returns the status `blocked: 1`, it indicates that you are not allowed to publish events under the given publisherId.\n", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/eventV1ResBody" - } - } - } - } - }, - "securitySchemes": { - "bearerAuth": { - "type": "http", - "scheme": "bearer", - "bearerFormat": "JWT" - } - }, - "schemas": { - "authResponse": { - "type": "object", - "properties": { - "expiresIn": { - "type": "number", - "description": "TTL for the token in seconds", - "example": 3600 - }, - "expires": { - "type": "string", - "description": "ISO8601 compliant timestamp for the token expiry", - "format": "iso8601-timestamp", - "example": "2020-01-19T06:00:00+01:00" - }, - "token": { - "type": "string", - "description": "ready to use token for API queries", - "example": "ey..." - }, - "refreshToken": { - "type": "string", - "description": "refresh token to be used with `/auth/refresh`/ endpoint", - "example": "A0..." - }, - "user": { - "type": "object", - "description": "Firebase-type user object obtained by decoding the JWT token" - }, - "trace": { - "type": "string", - "example": null - } - } - }, - "errorBadRequest": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "request.body should have required property 'XYZ'" - }, - "errors": { - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "properties": { - "path": { - "type": "string", - "example": ".body.XYZ" - }, - "message": { - "type": "string", - "example": "should have required property 'XYZ'" - }, - "errorCode": { - "type": "string", - "example": "required.openapi.validation" - } - } - } - }, - "trace": { - "type": "string", - "example": null - } - } - }, - "errorUnauthorized": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "request.headers should have required property 'Authorization'" - }, - "errors": { - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "properties": { - "path": { - "type": "string", - "example": ".headers.authorization" - }, - "message": { - "type": "string", - "example": "should have required property 'authorization'" - }, - "errorCode": { - "type": "string", - "example": "required.user" - } - } - } - }, - "trace": { - "type": "string", - "example": null - } - } - }, - "errorNotFound": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "object 'object.name' not found" - }, - "trace": { - "type": "string", - "example": null - } - } - }, - "errorForbidden": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "user is missing required permission" - }, - "errors": { - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "properties": { - "path": { - "type": "string", - "example": ".headers.authorization" - }, - "message": { - "type": "string", - "example": "should have required permission" - }, - "errorCode": { - "type": "string", - "example": "required.user.permission" - } - } - } - }, - "trace": { - "type": "string", - "example": null - } - } - }, - "errorInternalServerError": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Internal Server Error" - }, - "trace": { - "type": "string", - "example": null - } - } - }, - "services": { - "type": "object", - "required": ["type", "externalId", "publisherId"], - "properties": { - "type": { - "type": "string", - "example": "PermanentLivestream", - "enum": ["EventLivestream", "PermanentLivestream"] - }, - "externalId": { - "type": "string", - "example": "crid://swr.de/123450" - }, - "publisherId": { - "type": "string", - "description": "External ID or globally unique identifier (Core ID) for the associated publisher.\nWhen no Core ID is provided, the External ID will be converted by Eventhub.\n", - "example": "248000" - }, - "id": { - "type": "string", - "description": "Globally unique identifier, created by Eventhub", - "example": "urn:ard:permanent-livestream:49267f7d67be180d" - } - } - }, - "reference": { - "type": "object", - "additionalProperties": false, - "properties": { - "type": { - "type": "string", - "enum": [ - "Episode", - "Section", - "Publication", - "Broadcast", - "Show", - "Season", - "Article" - ] - }, - "id": { - "type": "string", - "pattern": "^urn:ard:[a-z0-9-]+:[a-z0-9-]+$", - "example": "urn:ard:show:49267f7d67be180d" - }, - "externalId": { - "type": "string", - "example": "crid://swr.de/123450", - "pattern": "^(c|b)rid://.+$" - }, - "title": { - "type": "string" - }, - "url": { - "type": "string", - "format": "uri" - }, - "alternateIds": { - "type": "array", - "items": { - "type": "string", - "example": "https://normdb.ivz.cn.ard.de/sendereihe/427" - } - } - }, - "required": ["type"] - }, - "eventV1PostBody": { - "additionalProperties": false, - "required": ["type", "start", "title", "services", "playlistItemId"], - "type": "object", - "description": "**Please also note the details in the `POST /events/v1` endpoint above!**\n", - "properties": { - "event": { - "type": "string", - "description": "If set, it needs to match the URL event parameter", - "example": "de.ard.eventhub.v1.radio.track.playing", - "enum": [ - "de.ard.eventhub.v1.radio.track.playing", - "de.ard.eventhub.v1.radio.track.next" - ] - }, - "type": { - "type": "string", - "description": "The type of the element that triggered this event. See additional file in docs for details.", - "example": "music", - "enum": [ - "audio", - "commercial", - "jingle", - "live", - "music", - "news", - "traffic", - "weather" - ] - }, - "start": { - "type": "string", - "description": "ISO8601 compliant timestamp", - "format": "iso8601-timestamp", - "example": "2020-01-19T06:00:00+01:00" - }, - "length": { - "type": "number", - "format": "float", - "description": "Scheduled length of the element in seconds", - "example": 240, - "nullable": true - }, - "title": { - "type": "string", - "description": "Representative title for external use", - "example": "Song name" - }, - "artist": { - "type": "string", - "description": "Pre-formatted artist information", - "example": "Sam Feldt feat. Someone Else", - "nullable": true - }, - "contributors": { - "type": "array", - "description": "Full details about involved artists if available", - "nullable": true, - "items": { - "type": "object", - "required": ["name", "role"], - "properties": { - "name": { - "type": "string", - "example": "Sam Feldt" - }, - "role": { - "type": "string", - "example": "artist", - "enum": [ - "artist", - "author", - "composer", - "performer" - ] - }, - "normDb": { - "type": "object", - "description": "Reference to an entity in ARD's Norm-DB catalog", - "nullable": true, - "properties": { - "type": { - "type": "string", - "example": "Person" - }, - "id": { - "type": "string", - "example": "1641010" - } - } - }, - "isni": { - "type": "string", - "description": "ISNI ID if available", - "nullable": true, - "externalDocs": { - "description": "International Standard Name Identifier", - "url": "https://kb.ddex.net/display/HBK/Communication+of+Identifiers+in+DDEX+Messages" - } - }, - "url": { - "type": "string", - "description": "Can link to external reference", - "nullable": true - } - } - } - }, - "services": { - "type": "array", - "description": "The playing stations unique Service-IDs. Do not include the Service-Type suffix.", - "items": { - "minItems": 1, - "allOf": [ - { - "$ref": "#/components/schemas/services" - } - ] - } - }, - "references": { - "type": "string", - "description": "related external entities", - "nullable": true, - "items": { - "oneOf": [ - { - "$ref": "#/components/schemas/reference", - "required": ["type", "externalId"] - }, - { - "$ref": "#/components/schemas/reference", - "required": ["type", "id"] - }, - { - "$ref": "#/components/schemas/reference", - "required": ["type", "title", "url"] - } - ] - } - }, - "playlistItemId": { - "type": "string", - "description": "Unique identifier (within a publisher) to connect next and playing items if needed", - "example": "swr3-5678" - }, - "hfdbIds": { - "type": "array", - "description": "Can reference all available tracks in ARD HFDB instances. Should ideally at least include the common ZSK instance.", - "nullable": true, - "items": { - "type": "string" - }, - "example": ["swrhfdb1.KONF.12345", "zskhfdb1.KONF.12345"] - }, - "externalId": { - "type": "string", - "description": "Can reference the original ID in the publisher's system", - "example": "M012345.001", - "nullable": true - }, - "isrc": { - "type": "string", - "description": "Appropriate ISRC code if track is a music element", - "example": "DE012345678", - "nullable": true - }, - "upc": { - "type": "string", - "description": "Corresponding reference to an album where such ISRC was published", - "nullable": true - }, - "mpn": { - "type": "string", - "description": "If available the reference to the original delivery from MPN", - "nullable": true - }, - "media": { - "type": "array", - "description": "Can contain an array of media files like cover, artist, etc.", - "nullable": true, - "items": { - "required": ["type", "url", "description"], - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "cover", - "artist", - "anchor", - "audio", - "video" - ], - "example": "cover" - }, - "url": { - "type": "string", - "example": "https://example.com/cover.jpg" - }, - "templateUrl": { - "type": "string", - "example": "https://example.com/cover.jpg?width={width}", - "nullable": true - }, - "description": { - "type": "string", - "example": "Cover Demo Artist" - }, - "attribution": { - "type": "string", - "example": "Photographer XYZ", - "nullable": true - } - } - } - }, - "plugins": { - "type": "array", - "description": "Highly optional field for future third-party metadata distribution or other connected services", - "nullable": true, - "items": { - "type": "object", - "properties": { - "type": { - "type": "string", - "example": "postToThirdPartyPlatformXYZ" - } - } - } - }, - "id": { - "type": "string", - "description": "ID gets inserted by Eventhub as string-formatted number, but might be a true string in the future, do not expect this string to remain numbers only!", - "example": "1234567890" - } - } - }, - "eventV1ResBody": { - "type": "object", - "properties": { - "statuses": { - "type": "object", - "properties": { - "published": { - "type": "integer", - "example": 1 - }, - "blocked": { - "type": "integer", - "example": 0 - }, - "failed": { - "type": "integer", - "example": 0 - } - } - }, - "event": { - "$ref": "#/components/schemas/eventV1PostBody" - }, - "trace": { - "type": "string", - "example": null - } - } - }, - "subscriptionPost": { - "required": ["type", "method", "url", "contact", "topic"], - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": ["PUBSUB"], - "example": "PUBSUB" - }, - "method": { - "type": "string", - "enum": ["PUSH"], - "example": "PUSH" - }, - "url": { - "type": "string", - "description": "Publicly accessible URL that should receive the events", - "example": "https://example.com/my/webhook/for/this/subscription" - }, - "contact": { - "type": "string", - "description": "Email address to be contacted in case of problems with this subscription", - "example": "my-emergency-and-notifications-contact@ard.de" - }, - "topic": { - "type": "string", - "description": "ID of the topic to subscribe to", - "example": "topic-id-to-subscribe-to" - } - } - }, - "subscriptionsList": { - "type": "array", - "items": { - "allOf": [ - { - "$ref": "#/components/schemas/subscriptionResponse" - } - ] - } - }, - "subscriptionResponse": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": ["PUBSUB"], - "example": "PUBSUB" - }, - "method": { - "type": "string", - "enum": ["PUSH"], - "example": "PUSH" - }, - "name": { - "type": "string", - "description": "ID of the subscription to be referenced in API calls", - "example": "de.ard.eventhub.subscription.subscription-id" - }, - "path": { - "type": "string", - "description": "Path of subscription in project", - "example": "projects/ard-eventhub/subscriptions/subscription-name" - }, - "topic": { - "type": "object", - "description": "Object of the subscribed topic", - "properties": { - "id": { - "type": "string", - "example": "urn:ard:permanent-livestream:topic-id" - }, - "name": { - "type": "string", - "example": "de.ard.eventhub.dev.urn%3Aard%3Apermanent-livestream%3Atopic-id" - }, - "path": { - "type": "string", - "example": "projects/ard-eventhub/topics/topic-name" - } - } - }, - "ackDeadlineSeconds": { - "type": "integer", - "example": 20 - }, - "retryPolicy": { - "type": "string", - "example": null - }, - "serviceAccount": { - "type": "string", - "example": "name-of-service-account" - }, - "url": { - "type": "string", - "description": "Publicly accessible URL that should receive the events", - "example": "https://example.com/my/webhook/for/this/subscription" - }, - "contact": { - "type": "string", - "description": "Email address to be contacted in case of problems with this subscription", - "example": "my-emergency-and-notifications-contact@ard.de" - }, - "institutionId": { - "type": "string", - "description": "ID of the institution the current user belongs to", - "example": "urn:ard:institution:institution-id" - } - } - }, - "subscriptionDeleted": { - "type": "object", - "properties": { - "valid": { - "type": "boolean", - "example": true - }, - "trace": { - "type": "string", - "example": null - } - } - }, - "topicResponse": { - "type": "array", - "items": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": ["PUBSUB"], - "example": "PUBSUB" - }, - "id": { - "type": "string", - "example": "urn:ard:permanent-livestream:topic-id" - }, - "name": { - "type": "string", - "example": "de.ard.eventhub.dev.urn%3Aard%3Apermanent-livestream%3Atopic-id" - }, - "path": { - "type": "string", - "example": "projects/ard-eventhub/topics/topic-name" - }, - "labels": { - "type": "object", - "properties": { - "id": { - "type": "string", - "example": "1234567890" - }, - "creator-slug": { - "type": "string", - "example": "ard-eventhub-swr" - }, - "publisher-slug": { - "type": "string", - "example": "swr-rheinland-pfalz" - }, - "stage": { - "type": "string", - "example": "prod" - }, - "created": { - "type": "string", - "example": "2021-03-25" - }, - "institution-slug": { - "type": "string", - "example": "sudwestrundfunk" - } - } - } - } - } - } - } - } -} + "openapi": "3.0.3", + "info": { + "title": "ARD-Eventhub", + "description": "ARD system to distribute real-time (live) metadata for primarily radio broadcasts.", + "termsOfService": "https://www.ard.de", + "contact": { + "email": "lab@swr.de" + }, + "license": { + "name": "European Union Public License 1.2", + "url": "https://spdx.org/licenses/EUPL-1.2.html" + }, + "version": "1.5.1" + }, + "externalDocs": { + "description": "ARD-Eventhub Documentation", + "url": "https://swrlab.github.io/ard-eventhub/" + }, + "servers": [ + { + "url": "/", + "description": "Local (domain-relative) environment" + } + ], + "tags": [ + { + "name": "auth", + "description": "Authentication services for Eventhub" + }, + { + "name": "events", + "description": "Manage events" + }, + { + "name": "subscriptions", + "description": "Access to subscription management" + }, + { + "name": "topics", + "description": "Access to topics details" + } + ], + "paths": { + "/auth/login": { + "post": { + "tags": [ + "auth" + ], + "summary": "Swap login credentials for a token", + "operationId": "authLoginPost", + "requestBody": { + "content": { + "application/json": { + "schema": { + "additionalProperties": false, + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "my-email@example.com" + }, + "password": { + "type": "string", + "example": "my-password" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Authentication successful", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/authResponse" + } + } + } + }, + "400": { + "description": "Bad Request (invalid input)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorBadRequest" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorInternalServerError" + } + } + } + } + } + } + }, + "/auth/refresh": { + "post": { + "tags": [ + "auth" + ], + "summary": "Swap refresh token for new id token", + "operationId": "authRefreshPost", + "requestBody": { + "content": { + "application/json": { + "schema": { + "additionalProperties": false, + "type": "object", + "properties": { + "refreshToken": { + "type": "string", + "example": "abcXYZ..." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Authentication successful", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/authResponse" + } + } + } + }, + "400": { + "description": "Bad Request (invalid input)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorBadRequest" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorInternalServerError" + } + } + } + } + } + } + }, + "/auth/reset": { + "post": { + "tags": [ + "auth" + ], + "summary": "Request password reset email", + "operationId": "authResetPost", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "my-email@example.com" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Request successful", + "content": {} + }, + "400": { + "description": "Bad Request (invalid input)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorBadRequest" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorInternalServerError" + } + } + } + } + } + } + }, + "/events/de.ard.eventhub.v1.radio.track.next": { + "post": { + "tags": [ + "events" + ], + "summary": "Distribute a next track", + "operationId": "eventPostV1RadioTrackNext", + "security": [ + { + "bearerAuth": [] + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/eventV1RadioTrack" + }, + "responses": { + "201": { + "$ref": "#/components/responses/eventV1RadioTrack" + }, + "400": { + "description": "Bad Request (invalid input)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorBadRequest" + } + } + } + }, + "401": { + "description": "Missing authentication", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorUnauthorized" + } + } + } + }, + "403": { + "description": "Invalid authorization", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorForbidden" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorInternalServerError" + } + } + } + } + } + } + }, + "/events/de.ard.eventhub.v1.radio.track.playing": { + "post": { + "tags": [ + "events" + ], + "summary": "Distribute a now-playing track", + "operationId": "eventPostV1RadioTrackPlaying", + "security": [ + { + "bearerAuth": [] + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/eventV1RadioTrack" + }, + "responses": { + "201": { + "$ref": "#/components/responses/eventV1RadioTrack" + }, + "400": { + "description": "Bad Request (invalid input)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorBadRequest" + } + } + } + }, + "401": { + "description": "Missing authentication", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorUnauthorized" + } + } + } + }, + "403": { + "description": "Invalid authorization", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorForbidden" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorInternalServerError" + } + } + } + } + } + } + }, + "/subscriptions": { + "get": { + "tags": [ + "subscriptions" + ], + "summary": "List all subscriptions for this user", + "operationId": "subscriptionList", + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Subscriptions found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/subscriptionsList" + } + } + } + }, + "401": { + "description": "Missing authentication", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorUnauthorized" + } + } + } + }, + "403": { + "description": "Invalid authorization", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorForbidden" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorInternalServerError" + } + } + } + } + } + }, + "post": { + "tags": [ + "subscriptions" + ], + "summary": "Add a new subscription", + "operationId": "subscriptionPost", + "security": [ + { + "bearerAuth": [] + } + ], + "requestBody": { + "description": "New event to be distributed to subscribers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/subscriptionPost" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Subscription created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/subscriptionResponse" + } + } + } + }, + "400": { + "description": "Bad Request (invalid input)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorBadRequest" + } + } + } + }, + "401": { + "description": "Missing authentication", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorUnauthorized" + } + } + } + }, + "403": { + "description": "Invalid authorization", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorForbidden" + } + } + } + }, + "404": { + "description": "Topic for subscription not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorNotFound" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorInternalServerError" + } + } + } + } + } + } + }, + "/subscriptions/{name}": { + "get": { + "tags": [ + "subscriptions" + ], + "summary": "Get details about a single subscription from this user", + "operationId": "subscriptionsGet", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "name": "name", + "in": "path", + "description": "`name` of the desired subscription", + "required": true, + "style": "simple", + "explode": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Subscription found", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/subscriptionResponse" + } + } + } + } + }, + "401": { + "description": "Missing authentication", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorUnauthorized" + } + } + } + }, + "403": { + "description": "Invalid authorization", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorForbidden" + } + } + } + }, + "404": { + "description": "Subscription not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorNotFound" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorInternalServerError" + } + } + } + } + } + }, + "delete": { + "tags": [ + "subscriptions" + ], + "summary": "Remove a single subscription by this user", + "operationId": "subscriptionsDelete", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "name": "name", + "in": "path", + "description": "`name` of the desired subscription", + "required": true, + "style": "simple", + "explode": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Subscription deleted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/subscriptionDeleted" + } + } + } + }, + "401": { + "description": "Missing authentication", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorUnauthorized" + } + } + } + }, + "403": { + "description": "Invalid authorization", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorForbidden" + } + } + } + }, + "404": { + "description": "Subscription not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorNotFound" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorInternalServerError" + } + } + } + } + } + } + }, + "/topics": { + "get": { + "tags": [ + "topics" + ], + "summary": "List all available topics", + "operationId": "topicsGet", + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Topics found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/topicResponse" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorInternalServerError" + } + } + } + } + } + } + } + }, + "components": { + "requestBodies": { + "eventV1RadioTrack": { + "description": "New event to be distributed to subscribers.\nThe Eventhub format validation expects only a subset of these variables as minimum set. All other fields are technically optional, but **highly encouraged** to be included, so a best-possible metadata exchange is possible.\nThe subset is defined in the list of required fields of Schemas `eventV1PostBody`, resulting in this body:\n```json\n{\n \"type\": \"music\",\n \"start\": \"2020-01-19T06:00:00+01:00\",\n \"title\": \"Song name\",\n \"services\": [ { ... } ],\n \"playlistItemId\": \"swr3-5678\"\n}\n```\nRequired fields not specified in the Schema, will cause your request to fail.\nThe `id` is inserted by Eventhub as string-formatted number, but might be a true string in the future, do not expect this string to remain numbers only!\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/eventV1PostBody" + } + } + }, + "required": true + } + }, + "responses": { + "eventV1RadioTrack": { + "description": "Event created\n*Note:* The first request of an event for an externalId that is not registered yet, will return the status `failed: 1`. This indicates that a new topic for the externalId has been created, and the request needs to be repeated:\n```json\n\"statuses\": {\n \"published\": 0,\n \"blocked\": 0,\n \"failed\": 1\n}\n```\nIf the request returns the status `blocked: 1`, it indicates that you are not allowed to publish events under the given publisherId.\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/eventV1ResBody" + } + } + } + } + }, + "securitySchemes": { + "bearerAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT" + } + }, + "schemas": { + "authResponse": { + "type": "object", + "properties": { + "expiresIn": { + "type": "number", + "description": "TTL for the token in seconds", + "example": 3600 + }, + "expires": { + "type": "string", + "description": "ISO8601 compliant timestamp for the token expiry", + "format": "iso8601-timestamp", + "example": "2020-01-19T06:00:00+01:00" + }, + "token": { + "type": "string", + "description": "ready to use token for API queries", + "example": "ey..." + }, + "refreshToken": { + "type": "string", + "description": "refresh token to be used with `/auth/refresh`/ endpoint", + "example": "A0..." + }, + "user": { + "type": "object", + "description": "Firebase-type user object obtained by decoding the JWT token" + }, + "trace": { + "type": "string", + "example": null + } + } + }, + "errorBadRequest": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "request.body should have required property 'XYZ'" + }, + "errors": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "properties": { + "path": { + "type": "string", + "example": ".body.XYZ" + }, + "message": { + "type": "string", + "example": "should have required property 'XYZ'" + }, + "errorCode": { + "type": "string", + "example": "required.openapi.validation" + } + } + } + }, + "trace": { + "type": "string", + "example": null + } + } + }, + "errorUnauthorized": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "request.headers should have required property 'Authorization'" + }, + "errors": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "properties": { + "path": { + "type": "string", + "example": ".headers.authorization" + }, + "message": { + "type": "string", + "example": "should have required property 'authorization'" + }, + "errorCode": { + "type": "string", + "example": "required.user" + } + } + } + }, + "trace": { + "type": "string", + "example": null + } + } + }, + "errorNotFound": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "object 'object.name' not found" + }, + "trace": { + "type": "string", + "example": null + } + } + }, + "errorForbidden": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "user is missing required permission" + }, + "errors": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "properties": { + "path": { + "type": "string", + "example": ".headers.authorization" + }, + "message": { + "type": "string", + "example": "should have required permission" + }, + "errorCode": { + "type": "string", + "example": "required.user.permission" + } + } + } + }, + "trace": { + "type": "string", + "example": null + } + } + }, + "errorInternalServerError": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Internal Server Error" + }, + "trace": { + "type": "string", + "example": null + } + } + }, + "services": { + "type": "object", + "required": [ + "type", + "externalId", + "publisherId" + ], + "properties": { + "type": { + "type": "string", + "example": "PermanentLivestream", + "enum": [ + "EventLivestream", + "PermanentLivestream" + ] + }, + "externalId": { + "type": "string", + "example": "crid://swr.de/123450" + }, + "publisherId": { + "type": "string", + "description": "External ID or globally unique identifier (Core ID) for the associated publisher.\nWhen no Core ID is provided, the External ID will be converted by Eventhub.\n", + "example": "248000" + }, + "id": { + "type": "string", + "description": "Globally unique identifier, created by Eventhub", + "example": "urn:ard:permanent-livestream:49267f7d67be180d" + } + } + }, + "reference": { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "externalId" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "Episode", + "Section", + "Publication", + "Broadcast", + "Show", + "Season", + "Article" + ] + }, + "id": { + "type": "string", + "pattern": "^urn:ard:[a-z0-9-]+:[a-z0-9-]+$", + "example": "urn:ard:show:49267f7d67be180d" + }, + "externalId": { + "type": "string", + "example": "crid://swr.de/123450", + "pattern": "^(c|b)rid://.+$" + }, + "title": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + }, + "alternateIds": { + "type": "array", + "items": { + "type": "string", + "example": "https://normdb.ivz.cn.ard.de/sendereihe/427" + } + } + } + }, + "eventV1PostBody": { + "additionalProperties": false, + "required": [ + "type", + "start", + "title", + "services", + "playlistItemId" + ], + "type": "object", + "description": "**Please also note the details in the `POST /events/v1` endpoint above!**\n", + "properties": { + "event": { + "type": "string", + "description": "If set, it needs to match the URL event parameter", + "example": "de.ard.eventhub.v1.radio.track.playing", + "enum": [ + "de.ard.eventhub.v1.radio.track.playing", + "de.ard.eventhub.v1.radio.track.next" + ] + }, + "type": { + "type": "string", + "description": "The type of the element that triggered this event. See additional file in docs for details.", + "example": "music", + "enum": [ + "audio", + "commercial", + "jingle", + "live", + "music", + "news", + "traffic", + "weather" + ] + }, + "start": { + "type": "string", + "description": "ISO8601 compliant timestamp", + "format": "iso8601-timestamp", + "example": "2020-01-19T06:00:00+01:00" + }, + "length": { + "type": "number", + "format": "float", + "description": "Scheduled length of the element in seconds", + "example": 240, + "nullable": true + }, + "title": { + "type": "string", + "description": "Representative title for external use", + "example": "Song name" + }, + "artist": { + "type": "string", + "description": "Pre-formatted artist information", + "example": "Sam Feldt feat. Someone Else", + "nullable": true + }, + "contributors": { + "type": "array", + "description": "Full details about involved artists if available", + "nullable": true, + "items": { + "type": "object", + "required": [ + "name", + "role" + ], + "properties": { + "name": { + "type": "string", + "example": "Sam Feldt" + }, + "role": { + "type": "string", + "example": "artist", + "enum": [ + "artist", + "author", + "composer", + "performer" + ] + }, + "normDb": { + "type": "object", + "description": "Reference to an entity in ARD's Norm-DB catalog", + "nullable": true, + "properties": { + "type": { + "type": "string", + "example": "Person" + }, + "id": { + "type": "string", + "example": "1641010" + } + } + }, + "isni": { + "type": "string", + "description": "ISNI ID if available", + "nullable": true, + "externalDocs": { + "description": "International Standard Name Identifier", + "url": "https://kb.ddex.net/display/HBK/Communication+of+Identifiers+in+DDEX+Messages" + } + }, + "url": { + "type": "string", + "description": "Can link to external reference", + "nullable": true + } + } + } + }, + "services": { + "type": "array", + "description": "The playing stations unique Service-IDs. Do not include the Service-Type suffix.", + "items": { + "minItems": 1, + "allOf": [ + { + "$ref": "#/components/schemas/services" + } + ] + } + }, + "references": { + "type": "array", + "description": "related external entities", + "nullable": true, + "items": { + "minItems": 0, + "allOf": [ + { + "$ref": "#/components/schemas/reference" + } + ] + } + }, + "playlistItemId": { + "type": "string", + "description": "Unique identifier (within a publisher) to connect next and playing items if needed", + "example": "swr3-5678" + }, + "hfdbIds": { + "type": "array", + "description": "Can reference all available tracks in ARD HFDB instances. Should ideally at least include the common ZSK instance.", + "nullable": true, + "items": { + "type": "string" + }, + "example": [ + "swrhfdb1.KONF.12345", + "zskhfdb1.KONF.12345" + ] + }, + "externalId": { + "type": "string", + "description": "Can reference the original ID in the publisher's system", + "example": "M012345.001", + "nullable": true + }, + "isrc": { + "type": "string", + "description": "Appropriate ISRC code if track is a music element", + "example": "DE012345678", + "nullable": true + }, + "upc": { + "type": "string", + "description": "Corresponding reference to an album where such ISRC was published", + "nullable": true + }, + "mpn": { + "type": "string", + "description": "If available the reference to the original delivery from MPN", + "nullable": true + }, + "media": { + "type": "array", + "description": "Can contain an array of media files like cover, artist, etc.", + "nullable": true, + "items": { + "required": [ + "type", + "url", + "description" + ], + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "cover", + "artist", + "anchor", + "audio", + "video" + ], + "example": "cover" + }, + "url": { + "type": "string", + "example": "https://example.com/cover.jpg" + }, + "templateUrl": { + "type": "string", + "example": "https://example.com/cover.jpg?width={width}", + "nullable": true + }, + "description": { + "type": "string", + "example": "Cover Demo Artist" + }, + "attribution": { + "type": "string", + "example": "Photographer XYZ", + "nullable": true + } + } + } + }, + "plugins": { + "type": "array", + "description": "Highly optional field for future third-party metadata distribution or other connected services", + "nullable": true, + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "postToThirdPartyPlatformXYZ" + } + } + } + }, + "id": { + "type": "string", + "description": "ID gets inserted by Eventhub as string-formatted number, but might be a true string in the future, do not expect this string to remain numbers only!", + "example": "1234567890" + } + } + }, + "eventV1ResBody": { + "type": "object", + "properties": { + "statuses": { + "type": "object", + "properties": { + "published": { + "type": "integer", + "example": 1 + }, + "blocked": { + "type": "integer", + "example": 0 + }, + "failed": { + "type": "integer", + "example": 0 + } + } + }, + "event": { + "$ref": "#/components/schemas/eventV1PostBody" + }, + "trace": { + "type": "string", + "example": null + } + } + }, + "subscriptionPost": { + "required": [ + "type", + "method", + "url", + "contact", + "topic" + ], + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "PUBSUB" + ], + "example": "PUBSUB" + }, + "method": { + "type": "string", + "enum": [ + "PUSH" + ], + "example": "PUSH" + }, + "url": { + "type": "string", + "description": "Publicly accessible URL that should receive the events", + "example": "https://example.com/my/webhook/for/this/subscription" + }, + "contact": { + "type": "string", + "description": "Email address to be contacted in case of problems with this subscription", + "example": "my-emergency-and-notifications-contact@ard.de" + }, + "topic": { + "type": "string", + "description": "ID of the topic to subscribe to", + "example": "topic-id-to-subscribe-to" + } + } + }, + "subscriptionsList": { + "type": "array", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/subscriptionResponse" + } + ] + } + }, + "subscriptionResponse": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "PUBSUB" + ], + "example": "PUBSUB" + }, + "method": { + "type": "string", + "enum": [ + "PUSH" + ], + "example": "PUSH" + }, + "name": { + "type": "string", + "description": "ID of the subscription to be referenced in API calls", + "example": "de.ard.eventhub.subscription.subscription-id" + }, + "path": { + "type": "string", + "description": "Path of subscription in project", + "example": "projects/ard-eventhub/subscriptions/subscription-name" + }, + "topic": { + "type": "object", + "description": "Object of the subscribed topic", + "properties": { + "id": { + "type": "string", + "example": "urn:ard:permanent-livestream:topic-id" + }, + "name": { + "type": "string", + "example": "de.ard.eventhub.dev.urn%3Aard%3Apermanent-livestream%3Atopic-id" + }, + "path": { + "type": "string", + "example": "projects/ard-eventhub/topics/topic-name" + } + } + }, + "ackDeadlineSeconds": { + "type": "integer", + "example": 20 + }, + "retryPolicy": { + "type": "string", + "example": null + }, + "serviceAccount": { + "type": "string", + "example": "name-of-service-account" + }, + "url": { + "type": "string", + "description": "Publicly accessible URL that should receive the events", + "example": "https://example.com/my/webhook/for/this/subscription" + }, + "contact": { + "type": "string", + "description": "Email address to be contacted in case of problems with this subscription", + "example": "my-emergency-and-notifications-contact@ard.de" + }, + "institutionId": { + "type": "string", + "description": "ID of the institution the current user belongs to", + "example": "urn:ard:institution:institution-id" + } + } + }, + "subscriptionDeleted": { + "type": "object", + "properties": { + "valid": { + "type": "boolean", + "example": true + }, + "trace": { + "type": "string", + "example": null + } + } + }, + "topicResponse": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "PUBSUB" + ], + "example": "PUBSUB" + }, + "id": { + "type": "string", + "example": "urn:ard:permanent-livestream:topic-id" + }, + "name": { + "type": "string", + "example": "de.ard.eventhub.dev.urn%3Aard%3Apermanent-livestream%3Atopic-id" + }, + "path": { + "type": "string", + "example": "projects/ard-eventhub/topics/topic-name" + }, + "labels": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "1234567890" + }, + "creator-slug": { + "type": "string", + "example": "ard-eventhub-swr" + }, + "publisher-slug": { + "type": "string", + "example": "swr-rheinland-pfalz" + }, + "stage": { + "type": "string", + "example": "prod" + }, + "created": { + "type": "string", + "example": "2021-03-25" + }, + "institution-slug": { + "type": "string", + "example": "sudwestrundfunk" + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/openapi.yaml b/openapi.yaml index f48c4bbe..9a160bde 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -624,6 +624,9 @@ components: reference: type: object additionalProperties: false + required: + - type + - externalId properties: type: type: string @@ -653,8 +656,6 @@ components: items: type: string example: https://normdb.ivz.cn.ard.de/sendereihe/427 - required: - - type eventV1PostBody: additionalProperties: false @@ -765,24 +766,13 @@ components: allOf: - $ref: "#/components/schemas/services" references: - type: string + type: array description: related external entities nullable: true items: - oneOf: - - $ref: "#/components/schemas/reference" - required: - - type - - externalId - - $ref: "#/components/schemas/reference" - required: - - type - - id + minItems: 0 + allOf: - $ref: "#/components/schemas/reference" - required: - - type - - title - - url playlistItemId: type: string description: >- diff --git a/package.json b/package.json index aba4118a..48eedbd7 100644 --- a/package.json +++ b/package.json @@ -29,16 +29,16 @@ "author": "SWR Audio Lab ", "license": "EUPL-1.2", "dependencies": { - "@google-cloud/datastore": "^7.5.0", - "@google-cloud/pubsub": "^3.4.1", - "@google-cloud/secret-manager": "^4.2.1", + "@google-cloud/datastore": "7.5.1", + "@google-cloud/pubsub": "3.5.0", + "@google-cloud/secret-manager": "4.2.2", "@swrlab/utils": "1.1.2", "compression": "1.7.4", - "dd-trace": "3.16.0", + "dd-trace": "3.17.1", "dotenv": "16.0.3", "express": "4.18.2", "express-openapi-validator": "5.0.3", - "firebase-admin": "11.5.0", + "firebase-admin": "11.7.0", "google-auth-library": "8.7.0", "jsonwebtoken": "9.0.0", "luxon": "3.3.0", @@ -53,13 +53,13 @@ "chai": "^4.3.7", "chai-http": "^4.3.0", "docsify-cli": "^4.4.4", - "eslint": "^8.37.0", + "eslint": "^8.38.0", "eslint-plugin-chai-friendly": "^0.7.2", "license-compliance": "^1.2.5", "mocha": "^10.2.0", "nodemon": "^2.0.22", "prettier": "^2.8.7", - "typescript": "^5.0.3" + "typescript": "^5.0.4" }, "resolutions": { "ansi-regex": "^5.0.1", diff --git a/test/ingest.test.js b/test/ingest.test.js index d4d47f6b..c1eb8071 100644 --- a/test/ingest.test.js +++ b/test/ingest.test.js @@ -164,7 +164,24 @@ const event = { publisherId: '282310', }, ], - playlistItemId: 'unit-test-playlist', + playlistItemId: 'unit-test-id-in-playlist-567', + references: [ + { + type: 'Show', + externalId: 'crid://swr.de/my-show/1234567', + alternateIds: [ + 'https://normdb.ivz.cn.ard.de/sendereihe/427', + 'urn:ard:show:027708befb6bfe14', + 'brid://br.de/broadcastSeries/1235', + ], + }, + { + type: 'Article', + externalId: 'crid://dlf.de/article/1234567', + title: 'Kommerzielle US-Raumfahrt - Die neue Weltraumökonomie', + url: 'https://www.deutschlandfunkkultur.de/kommerzielle-us-raumfahrt-die-neue-weltraumoekonomie-100.html', + }, + ], } describe(`POST ${eventPath}`, () => { @@ -214,6 +231,18 @@ describe(`POST ${eventPath}`, () => { done() }) }) + + it('publish a new event with invalid externalId in references', (done) => { + event.references[1].externalId = null + chai.request(server) + .post(eventPath) + .set('Authorization', `Bearer ${accessToken}`) + .send(event) + .end((err, res) => { + testResponse(res, 400) + done() + }) + }) }) /* diff --git a/yarn.lock b/yarn.lock index 10afc6b9..64613483 100644 --- a/yarn.lock +++ b/yarn.lock @@ -66,10 +66,10 @@ dependencies: node-gyp-build "^4.5.0" -"@datadog/native-iast-taint-tracking@1.1.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@datadog/native-iast-taint-tracking/-/native-iast-taint-tracking-1.1.1.tgz#cbeace022b6c1f3a0a40dc0000cc40079c6d4895" - integrity sha512-VkESVYpVlLHqw38UHqqEYsJaJTp3+JpKIJhfB9nlQO13dYBc3Sgq/QJZNdPViU73SVsCJtuw4D0SXRyjTXP1IA== +"@datadog/native-iast-taint-tracking@1.3.1": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@datadog/native-iast-taint-tracking/-/native-iast-taint-tracking-1.3.1.tgz#49b3befc3049370f4034babcf57c3d67e9f4d56b" + integrity sha512-KWKmK4/GANisxqVZ1TtGlBIOw2RIXdUO0r7361QJHiBVUxwNKmKNVDVuCTKGpRRH/0GZcxY0yVgl38ee/6HM3A== dependencies: node-gyp-build "^3.9.0" @@ -125,12 +125,12 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@8.37.0": - version "8.37.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.37.0.tgz#cf1b5fa24217fe007f6487a26d765274925efa7d" - integrity sha512-x5vzdtOOGgFVDCUs81QRB2+liax8rFg3+7hqM+QhBG0/G3F1ZsoYl97UrqgHgQ9KKT7G6c4V+aTUCgu/n22v1A== +"@eslint/js@8.38.0": + version "8.38.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.38.0.tgz#73a8a0d8aa8a8e6fe270431c5e72ae91b5337892" + integrity sha512-IoD2MfUnOV58ghIHCiil01PcohxjbYR/qCxsoC+xNgUwh1EY8jOOrYmu3d3a71+tJJ23uscEV4X2HJWMsPJu4g== -"@fastify/busboy@^1.1.0": +"@fastify/busboy@^1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-1.2.1.tgz#9c6db24a55f8b803b5222753b24fe3aea2ba9ca3" integrity sha512-7PQA7EH43S0CxcOa9OeAnaeA0oQ+e/DHNPZwSQM9CQHW76jle5+OvLdibRp/Aafs9KXbLhxyjOTkRjWUbQEd3Q== @@ -155,7 +155,7 @@ "@firebase/util" "1.9.3" tslib "^2.1.0" -"@firebase/database-compat@^0.3.0": +"@firebase/database-compat@^0.3.4": version "0.3.4" resolved "https://registry.yarnpkg.com/@firebase/database-compat/-/database-compat-0.3.4.tgz#4e57932f7a5ba761cd5ac946ab6b6ab3f660522c" integrity sha512-kuAW+l+sLMUKBThnvxvUZ+Q1ZrF/vFJ58iUY9kAcbX48U03nVzIF6Tmkf0p3WVQwMqiXguSgtOPIB6ZCeF+5Gg== @@ -167,7 +167,7 @@ "@firebase/util" "1.9.3" tslib "^2.1.0" -"@firebase/database-types@0.10.4", "@firebase/database-types@^0.10.0": +"@firebase/database-types@0.10.4", "@firebase/database-types@^0.10.4": version "0.10.4" resolved "https://registry.yarnpkg.com/@firebase/database-types/-/database-types-0.10.4.tgz#47ba81113512dab637abace61cfb65f63d645ca7" integrity sha512-dPySn0vJ/89ZeBac70T+2tWWPiJXWbmRygYv0smT5TfE3hDrQ09eKMF3Y+vMlTdrMWq7mUdYW5REWPSGH4kAZQ== @@ -201,10 +201,10 @@ dependencies: tslib "^2.1.0" -"@google-cloud/datastore@^7.5.0": - version "7.5.0" - resolved "https://registry.yarnpkg.com/@google-cloud/datastore/-/datastore-7.5.0.tgz#88da05e11865322a5f95f86c92fd3e82b9c363b8" - integrity sha512-npOKzssHJrULQjmjfwMNOqPomFquPhhL9fjp+FAaRd59qT/36eTfVJ33+dptAZOFSCrlPFQlaZoVjPpD66sqaQ== +"@google-cloud/datastore@7.5.1": + version "7.5.1" + resolved "https://registry.yarnpkg.com/@google-cloud/datastore/-/datastore-7.5.1.tgz#89e862f17fe980dd2f1ee65399d89a59b0ec21bb" + integrity sha512-wIGg4X00ty/Dz9Fgl8EVm1lmpF2rH643yhE2/Whw8frfTRKQL+wT8LpGxasSGbvDb/NMedNUG1O9tohQNNHMvQ== dependencies: "@google-cloud/promisify" "^3.0.0" arrify "^2.0.1" @@ -215,7 +215,7 @@ split-array-stream "^2.0.0" stream-events "^1.0.5" -"@google-cloud/firestore@^6.4.0": +"@google-cloud/firestore@^6.5.0": version "6.5.0" resolved "https://registry.yarnpkg.com/@google-cloud/firestore/-/firestore-6.5.0.tgz#5049740e40eca75956264377ca9ffaab2c0f345d" integrity sha512-U0QwG6pEQxO5c0v0eUylswozmuvlvz7iXSW+I18jzqR2hAFrUq2Weu1wm3NaH8wGD4ZL7W9Be4cMHG5CYU8LuQ== @@ -261,10 +261,10 @@ resolved "https://registry.yarnpkg.com/@google-cloud/promisify/-/promisify-3.0.1.tgz#8d724fb280f47d1ff99953aee0c1669b25238c2e" integrity sha512-z1CjRjtQyBOYL+5Qr9DdYIfrdLBe746jRTYfaYU6MeXkqp7UfYs/jX16lFFVzZ7PGEJvqZNqYUEtb1mvDww4pA== -"@google-cloud/pubsub@^3.4.1": - version "3.4.1" - resolved "https://registry.yarnpkg.com/@google-cloud/pubsub/-/pubsub-3.4.1.tgz#290a67246a63f29207ad6870ec75fc751dcc923e" - integrity sha512-6sTJAFQYeUeAEyBiz2hMvEku3fUvFwNHKrkHjM3w6hcnN37xCH60NDlnZvGuT2rRXG24QBq6Fx3Pe/3nkaybUQ== +"@google-cloud/pubsub@3.5.0": + version "3.5.0" + resolved "https://registry.yarnpkg.com/@google-cloud/pubsub/-/pubsub-3.5.0.tgz#02c53f8d95865770ee9e984478fc6818128e35dc" + integrity sha512-4Up5J0DUdUMOfu8I2TdO6pzz0Vtm++wMW7qopgCTgVyYu9zow2sXdA7J0pCnovtB9qizx4Sr8aiaKC0vdMA1cA== dependencies: "@google-cloud/paginator" "^4.0.0" "@google-cloud/precise-date" "^3.0.0" @@ -283,14 +283,14 @@ lodash.snakecase "^4.1.1" p-defer "^3.0.0" -"@google-cloud/secret-manager@^4.2.1": - version "4.2.1" - resolved "https://registry.yarnpkg.com/@google-cloud/secret-manager/-/secret-manager-4.2.1.tgz#c2f04af967e4c70612077816bd4d3af5f85c6edf" - integrity sha512-PU7unr+L0Zx2ZErUzyfZ6VxgNQ2t1YfxSWZ1ELaObpRvoeLzsxQaeXH5dsNt0p5XEFu/tTyjDUfyF/64RxbFyQ== +"@google-cloud/secret-manager@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@google-cloud/secret-manager/-/secret-manager-4.2.2.tgz#4c0e7a169e91d3f6f27d31252e9065c85d8fe802" + integrity sha512-76yXN21ahrZMJKjs+gNoVWrSmioxqF2A2jKyDxRRq0DjSfoLHXb8POipjsTMErc1R1S7J7LToK2iLsi8lJyZqw== dependencies: - google-gax "^3.5.2" + google-gax "^3.5.8" -"@google-cloud/storage@^6.5.2", "@google-cloud/storage@^6.9.3": +"@google-cloud/storage@^6.9.3": version "6.9.4" resolved "https://registry.yarnpkg.com/@google-cloud/storage/-/storage-6.9.4.tgz#23f6b8b3335e517e0ab3b9c641e64ceac674887e" integrity sha512-5Li+0xRJ8wgc+vlf7Tgew8COKEJgRzRmC5ozdSYaBj7BK+X39aPPBP6ROsDTiCZ0MpAg7dxIc+HhKiCvQDplXQ== @@ -313,6 +313,29 @@ teeny-request "^8.0.0" uuid "^8.0.0" +"@google-cloud/storage@^6.9.5": + version "6.9.5" + resolved "https://registry.yarnpkg.com/@google-cloud/storage/-/storage-6.9.5.tgz#2df7e753b90dba22c7926ecbe16affbd7489939d" + integrity sha512-fcLsDA8YKcGuqvhk0XTjJGVpG9dzs5Em8IcUjSjspYvERuHYqMy9CMChWapSjv3Lyw//exa3mv4nUxPlV93BnA== + dependencies: + "@google-cloud/paginator" "^3.0.7" + "@google-cloud/projectify" "^3.0.0" + "@google-cloud/promisify" "^3.0.0" + abort-controller "^3.0.0" + async-retry "^1.3.3" + compressible "^2.0.12" + duplexify "^4.0.0" + ent "^2.2.0" + extend "^3.0.2" + gaxios "^5.0.0" + google-auth-library "^8.0.1" + mime "^3.0.0" + mime-types "^2.0.8" + p-limit "^3.0.1" + retry-request "^5.0.0" + teeny-request "^8.0.0" + uuid "^8.0.0" + "@grpc/grpc-js@~1.8.0": version "1.8.12" resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.8.12.tgz#bc0120859e8b153db764b473cc019ddf6bb2b414" @@ -1487,14 +1510,14 @@ cssesc@^3.0.0: resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== -dd-trace@3.16.0: - version "3.16.0" - resolved "https://registry.yarnpkg.com/dd-trace/-/dd-trace-3.16.0.tgz#baa76f9cfe047f16a995ace3d515d21d81944ed6" - integrity sha512-ogRsTotTF/dMzGVrULzEPShCf9MP6aHxNS5LQS0igvx7I/gJof0MTCXNGDhLY8X3KYRczolTkYTcJ3S4siiyGg== +dd-trace@3.17.1: + version "3.17.1" + resolved "https://registry.yarnpkg.com/dd-trace/-/dd-trace-3.17.1.tgz#4f38e056c7548312e6cedaab189fb37ebabd04d6" + integrity sha512-vlOHbVO6+je/ue04e//QGewPJdkGMpLRNeYH1+RVdutumYtyhAoODOUENYC03JNl/8L0o6Cjs0uNAWGXHzgRVQ== dependencies: "@datadog/native-appsec" "2.0.0" "@datadog/native-iast-rewriter" "2.0.1" - "@datadog/native-iast-taint-tracking" "1.1.1" + "@datadog/native-iast-taint-tracking" "1.3.1" "@datadog/native-metrics" "^1.5.0" "@datadog/pprof" "^2.1.0" "@datadog/sketches-js" "^2.1.0" @@ -1518,7 +1541,7 @@ dd-trace@3.16.0: path-to-regexp "^0.1.2" protobufjs "^7.1.2" retry "^0.10.1" - semver "^5.5.0" + semver "^7.3.8" debug@2.6.9: version "2.6.9" @@ -2017,15 +2040,15 @@ eslint-visitor-keys@^3.4.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz#c7f0f956124ce677047ddbc192a68f999454dedc" integrity sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ== -eslint@^8.37.0: - version "8.37.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.37.0.tgz#1f660ef2ce49a0bfdec0b0d698e0b8b627287412" - integrity sha512-NU3Ps9nI05GUoVMxcZx1J8CNR6xOvUT4jAUMH5+z8lpp3aEdPVCImKw6PWG4PY+Vfkpr+jvMpxs/qoE7wq0sPw== +eslint@^8.38.0: + version "8.38.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.38.0.tgz#a62c6f36e548a5574dd35728ac3c6209bd1e2f1a" + integrity sha512-pIdsD2jwlUGf/U38Jv97t8lq6HpaU/G9NKbYmpWpZGw3LdTNhZLbJePqxOXGB5+JEKfOPU/XLxYxFh03nr1KTg== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.4.0" "@eslint/eslintrc" "^2.0.2" - "@eslint/js" "8.37.0" + "@eslint/js" "8.38.0" "@humanwhocodes/config-array" "^0.11.8" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" @@ -2297,22 +2320,22 @@ find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" -firebase-admin@11.5.0: - version "11.5.0" - resolved "https://registry.yarnpkg.com/firebase-admin/-/firebase-admin-11.5.0.tgz#86aa97e45fd9868be1759dccf04614c488945093" - integrity sha512-bBdlYtNvXx8yZGdCd00NrfZl1o1A0aXOw5h8q5PwC8RXikOLNXq8vYtSKW44dj8zIaafVP6jFdcUXZem/LMsHA== +firebase-admin@11.7.0: + version "11.7.0" + resolved "https://registry.yarnpkg.com/firebase-admin/-/firebase-admin-11.7.0.tgz#ffcb17662f8652be9a11f676772a018cce149fe8" + integrity sha512-58y9dor/nrK4Dc/hwpmm7uGCy+oqld+k189mclWwhKu8IZJcZbXxPFHsNzW/yG7aVTH5sTD4rHnRXj8+t5AhPQ== dependencies: - "@fastify/busboy" "^1.1.0" - "@firebase/database-compat" "^0.3.0" - "@firebase/database-types" "^0.10.0" + "@fastify/busboy" "^1.2.1" + "@firebase/database-compat" "^0.3.4" + "@firebase/database-types" "^0.10.4" "@types/node" ">=12.12.47" jsonwebtoken "^9.0.0" jwks-rsa "^3.0.1" node-forge "^1.3.1" uuid "^9.0.0" optionalDependencies: - "@google-cloud/firestore" "^6.4.0" - "@google-cloud/storage" "^6.5.2" + "@google-cloud/firestore" "^6.5.0" + "@google-cloud/storage" "^6.9.5" flat-cache@^3.0.4: version "3.0.4" @@ -2575,6 +2598,27 @@ google-gax@^3.5.2, google-gax@^3.5.6, google-gax@^3.5.7: protobufjs-cli "1.1.1" retry-request "^5.0.0" +google-gax@^3.5.8: + version "3.6.0" + resolved "https://registry.yarnpkg.com/google-gax/-/google-gax-3.6.0.tgz#0f4ae350159737fe0aa289815c0d92838b19f6af" + integrity sha512-2fyb61vWxUonHiArRNJQmE4tx5oY1ni8VPo08fzII409vDSCWG7apDX4qNOQ2GXXT82gLBn3d3P1Dydh7pWjyw== + dependencies: + "@grpc/grpc-js" "~1.8.0" + "@grpc/proto-loader" "^0.7.0" + "@types/long" "^4.0.0" + "@types/rimraf" "^3.0.2" + abort-controller "^3.0.0" + duplexify "^4.0.0" + fast-text-encoding "^1.0.3" + google-auth-library "^8.0.2" + is-stream-ended "^0.1.4" + node-fetch "^2.6.1" + object-hash "^3.0.0" + proto3-json-serializer "^1.0.0" + protobufjs "7.2.3" + protobufjs-cli "1.1.1" + retry-request "^5.0.0" + google-p12-pem@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/google-p12-pem/-/google-p12-pem-4.0.1.tgz#82841798253c65b7dc2a4e5fe9df141db670172a" @@ -4204,6 +4248,24 @@ protobufjs@7.2.2, protobufjs@^7.0.0, protobufjs@^7.1.2: "@types/node" ">=13.7.0" long "^5.0.0" +protobufjs@7.2.3: + version "7.2.3" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.2.3.tgz#01af019e40d9c6133c49acbb3ff9e30f4f0f70b2" + integrity sha512-TtpvOqwB5Gdz/PQmOjgsrGH1nHjAQVCN7JG4A6r1sXRWESL5rNMAiRcBQlCAdKxZcAbstExQePYG8xof/JVRgg== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/node" ">=13.7.0" + long "^5.0.0" + proxy-addr@~2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" @@ -4527,7 +4589,7 @@ semver-diff@^3.1.1: dependencies: semver "^6.3.0" -"semver@2 || 3 || 4 || 5", semver@^5.5.0, semver@^5.7.1: +"semver@2 || 3 || 4 || 5", semver@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -5053,10 +5115,10 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== -typescript@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.3.tgz#fe976f0c826a88d0a382007681cbb2da44afdedf" - integrity sha512-xv8mOEDnigb/tN9PSMTwSEqAnUvkoXMQlicOb0IUVDBSQCgBSaAAROUZYy2IcUy5qU6XajK5jjjO7TMWqBTKZA== +typescript@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.4.tgz#b217fd20119bd61a94d4011274e0ab369058da3b" + integrity sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw== uc.micro@^1.0.1, uc.micro@^1.0.5: version "1.0.6" From b3df8359bf1e20096bc6c09346e4dfa5be60bb28 Mon Sep 17 00:00:00 2001 From: Daniel Freytag Date: Wed, 19 Apr 2023 16:12:34 +0200 Subject: [PATCH 9/9] fix: match json format --- openapi.json | 2774 ++++++++++++++++++++++++-------------------------- 1 file changed, 1356 insertions(+), 1418 deletions(-) diff --git a/openapi.json b/openapi.json index d05e5c72..3393998e 100644 --- a/openapi.json +++ b/openapi.json @@ -1,1419 +1,1357 @@ { - "openapi": "3.0.3", - "info": { - "title": "ARD-Eventhub", - "description": "ARD system to distribute real-time (live) metadata for primarily radio broadcasts.", - "termsOfService": "https://www.ard.de", - "contact": { - "email": "lab@swr.de" - }, - "license": { - "name": "European Union Public License 1.2", - "url": "https://spdx.org/licenses/EUPL-1.2.html" - }, - "version": "1.5.1" - }, - "externalDocs": { - "description": "ARD-Eventhub Documentation", - "url": "https://swrlab.github.io/ard-eventhub/" - }, - "servers": [ - { - "url": "/", - "description": "Local (domain-relative) environment" - } - ], - "tags": [ - { - "name": "auth", - "description": "Authentication services for Eventhub" - }, - { - "name": "events", - "description": "Manage events" - }, - { - "name": "subscriptions", - "description": "Access to subscription management" - }, - { - "name": "topics", - "description": "Access to topics details" - } - ], - "paths": { - "/auth/login": { - "post": { - "tags": [ - "auth" - ], - "summary": "Swap login credentials for a token", - "operationId": "authLoginPost", - "requestBody": { - "content": { - "application/json": { - "schema": { - "additionalProperties": false, - "type": "object", - "properties": { - "email": { - "type": "string", - "example": "my-email@example.com" - }, - "password": { - "type": "string", - "example": "my-password" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Authentication successful", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/authResponse" - } - } - } - }, - "400": { - "description": "Bad Request (invalid input)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorBadRequest" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorInternalServerError" - } - } - } - } - } - } - }, - "/auth/refresh": { - "post": { - "tags": [ - "auth" - ], - "summary": "Swap refresh token for new id token", - "operationId": "authRefreshPost", - "requestBody": { - "content": { - "application/json": { - "schema": { - "additionalProperties": false, - "type": "object", - "properties": { - "refreshToken": { - "type": "string", - "example": "abcXYZ..." - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Authentication successful", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/authResponse" - } - } - } - }, - "400": { - "description": "Bad Request (invalid input)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorBadRequest" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorInternalServerError" - } - } - } - } - } - } - }, - "/auth/reset": { - "post": { - "tags": [ - "auth" - ], - "summary": "Request password reset email", - "operationId": "authResetPost", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "email": { - "type": "string", - "example": "my-email@example.com" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Request successful", - "content": {} - }, - "400": { - "description": "Bad Request (invalid input)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorBadRequest" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorInternalServerError" - } - } - } - } - } - } - }, - "/events/de.ard.eventhub.v1.radio.track.next": { - "post": { - "tags": [ - "events" - ], - "summary": "Distribute a next track", - "operationId": "eventPostV1RadioTrackNext", - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "$ref": "#/components/requestBodies/eventV1RadioTrack" - }, - "responses": { - "201": { - "$ref": "#/components/responses/eventV1RadioTrack" - }, - "400": { - "description": "Bad Request (invalid input)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorBadRequest" - } - } - } - }, - "401": { - "description": "Missing authentication", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorUnauthorized" - } - } - } - }, - "403": { - "description": "Invalid authorization", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorForbidden" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorInternalServerError" - } - } - } - } - } - } - }, - "/events/de.ard.eventhub.v1.radio.track.playing": { - "post": { - "tags": [ - "events" - ], - "summary": "Distribute a now-playing track", - "operationId": "eventPostV1RadioTrackPlaying", - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "$ref": "#/components/requestBodies/eventV1RadioTrack" - }, - "responses": { - "201": { - "$ref": "#/components/responses/eventV1RadioTrack" - }, - "400": { - "description": "Bad Request (invalid input)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorBadRequest" - } - } - } - }, - "401": { - "description": "Missing authentication", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorUnauthorized" - } - } - } - }, - "403": { - "description": "Invalid authorization", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorForbidden" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorInternalServerError" - } - } - } - } - } - } - }, - "/subscriptions": { - "get": { - "tags": [ - "subscriptions" - ], - "summary": "List all subscriptions for this user", - "operationId": "subscriptionList", - "security": [ - { - "bearerAuth": [] - } - ], - "responses": { - "200": { - "description": "Subscriptions found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/subscriptionsList" - } - } - } - }, - "401": { - "description": "Missing authentication", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorUnauthorized" - } - } - } - }, - "403": { - "description": "Invalid authorization", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorForbidden" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorInternalServerError" - } - } - } - } - } - }, - "post": { - "tags": [ - "subscriptions" - ], - "summary": "Add a new subscription", - "operationId": "subscriptionPost", - "security": [ - { - "bearerAuth": [] - } - ], - "requestBody": { - "description": "New event to be distributed to subscribers.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/subscriptionPost" - } - } - }, - "required": true - }, - "responses": { - "201": { - "description": "Subscription created", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/subscriptionResponse" - } - } - } - }, - "400": { - "description": "Bad Request (invalid input)", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorBadRequest" - } - } - } - }, - "401": { - "description": "Missing authentication", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorUnauthorized" - } - } - } - }, - "403": { - "description": "Invalid authorization", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorForbidden" - } - } - } - }, - "404": { - "description": "Topic for subscription not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorNotFound" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorInternalServerError" - } - } - } - } - } - } - }, - "/subscriptions/{name}": { - "get": { - "tags": [ - "subscriptions" - ], - "summary": "Get details about a single subscription from this user", - "operationId": "subscriptionsGet", - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "name": "name", - "in": "path", - "description": "`name` of the desired subscription", - "required": true, - "style": "simple", - "explode": false, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Subscription found", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/subscriptionResponse" - } - } - } - } - }, - "401": { - "description": "Missing authentication", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorUnauthorized" - } - } - } - }, - "403": { - "description": "Invalid authorization", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorForbidden" - } - } - } - }, - "404": { - "description": "Subscription not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorNotFound" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorInternalServerError" - } - } - } - } - } - }, - "delete": { - "tags": [ - "subscriptions" - ], - "summary": "Remove a single subscription by this user", - "operationId": "subscriptionsDelete", - "security": [ - { - "bearerAuth": [] - } - ], - "parameters": [ - { - "name": "name", - "in": "path", - "description": "`name` of the desired subscription", - "required": true, - "style": "simple", - "explode": false, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Subscription deleted", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/subscriptionDeleted" - } - } - } - }, - "401": { - "description": "Missing authentication", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorUnauthorized" - } - } - } - }, - "403": { - "description": "Invalid authorization", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorForbidden" - } - } - } - }, - "404": { - "description": "Subscription not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorNotFound" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorInternalServerError" - } - } - } - } - } - } - }, - "/topics": { - "get": { - "tags": [ - "topics" - ], - "summary": "List all available topics", - "operationId": "topicsGet", - "security": [ - { - "bearerAuth": [] - } - ], - "responses": { - "200": { - "description": "Topics found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/topicResponse" - } - } - } - }, - "500": { - "description": "Internal server error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/errorInternalServerError" - } - } - } - } - } - } - } - }, - "components": { - "requestBodies": { - "eventV1RadioTrack": { - "description": "New event to be distributed to subscribers.\nThe Eventhub format validation expects only a subset of these variables as minimum set. All other fields are technically optional, but **highly encouraged** to be included, so a best-possible metadata exchange is possible.\nThe subset is defined in the list of required fields of Schemas `eventV1PostBody`, resulting in this body:\n```json\n{\n \"type\": \"music\",\n \"start\": \"2020-01-19T06:00:00+01:00\",\n \"title\": \"Song name\",\n \"services\": [ { ... } ],\n \"playlistItemId\": \"swr3-5678\"\n}\n```\nRequired fields not specified in the Schema, will cause your request to fail.\nThe `id` is inserted by Eventhub as string-formatted number, but might be a true string in the future, do not expect this string to remain numbers only!\n", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/eventV1PostBody" - } - } - }, - "required": true - } - }, - "responses": { - "eventV1RadioTrack": { - "description": "Event created\n*Note:* The first request of an event for an externalId that is not registered yet, will return the status `failed: 1`. This indicates that a new topic for the externalId has been created, and the request needs to be repeated:\n```json\n\"statuses\": {\n \"published\": 0,\n \"blocked\": 0,\n \"failed\": 1\n}\n```\nIf the request returns the status `blocked: 1`, it indicates that you are not allowed to publish events under the given publisherId.\n", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/eventV1ResBody" - } - } - } - } - }, - "securitySchemes": { - "bearerAuth": { - "type": "http", - "scheme": "bearer", - "bearerFormat": "JWT" - } - }, - "schemas": { - "authResponse": { - "type": "object", - "properties": { - "expiresIn": { - "type": "number", - "description": "TTL for the token in seconds", - "example": 3600 - }, - "expires": { - "type": "string", - "description": "ISO8601 compliant timestamp for the token expiry", - "format": "iso8601-timestamp", - "example": "2020-01-19T06:00:00+01:00" - }, - "token": { - "type": "string", - "description": "ready to use token for API queries", - "example": "ey..." - }, - "refreshToken": { - "type": "string", - "description": "refresh token to be used with `/auth/refresh`/ endpoint", - "example": "A0..." - }, - "user": { - "type": "object", - "description": "Firebase-type user object obtained by decoding the JWT token" - }, - "trace": { - "type": "string", - "example": null - } - } - }, - "errorBadRequest": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "request.body should have required property 'XYZ'" - }, - "errors": { - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "properties": { - "path": { - "type": "string", - "example": ".body.XYZ" - }, - "message": { - "type": "string", - "example": "should have required property 'XYZ'" - }, - "errorCode": { - "type": "string", - "example": "required.openapi.validation" - } - } - } - }, - "trace": { - "type": "string", - "example": null - } - } - }, - "errorUnauthorized": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "request.headers should have required property 'Authorization'" - }, - "errors": { - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "properties": { - "path": { - "type": "string", - "example": ".headers.authorization" - }, - "message": { - "type": "string", - "example": "should have required property 'authorization'" - }, - "errorCode": { - "type": "string", - "example": "required.user" - } - } - } - }, - "trace": { - "type": "string", - "example": null - } - } - }, - "errorNotFound": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "object 'object.name' not found" - }, - "trace": { - "type": "string", - "example": null - } - } - }, - "errorForbidden": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "user is missing required permission" - }, - "errors": { - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "properties": { - "path": { - "type": "string", - "example": ".headers.authorization" - }, - "message": { - "type": "string", - "example": "should have required permission" - }, - "errorCode": { - "type": "string", - "example": "required.user.permission" - } - } - } - }, - "trace": { - "type": "string", - "example": null - } - } - }, - "errorInternalServerError": { - "type": "object", - "properties": { - "message": { - "type": "string", - "example": "Internal Server Error" - }, - "trace": { - "type": "string", - "example": null - } - } - }, - "services": { - "type": "object", - "required": [ - "type", - "externalId", - "publisherId" - ], - "properties": { - "type": { - "type": "string", - "example": "PermanentLivestream", - "enum": [ - "EventLivestream", - "PermanentLivestream" - ] - }, - "externalId": { - "type": "string", - "example": "crid://swr.de/123450" - }, - "publisherId": { - "type": "string", - "description": "External ID or globally unique identifier (Core ID) for the associated publisher.\nWhen no Core ID is provided, the External ID will be converted by Eventhub.\n", - "example": "248000" - }, - "id": { - "type": "string", - "description": "Globally unique identifier, created by Eventhub", - "example": "urn:ard:permanent-livestream:49267f7d67be180d" - } - } - }, - "reference": { - "type": "object", - "additionalProperties": false, - "required": [ - "type", - "externalId" - ], - "properties": { - "type": { - "type": "string", - "enum": [ - "Episode", - "Section", - "Publication", - "Broadcast", - "Show", - "Season", - "Article" - ] - }, - "id": { - "type": "string", - "pattern": "^urn:ard:[a-z0-9-]+:[a-z0-9-]+$", - "example": "urn:ard:show:49267f7d67be180d" - }, - "externalId": { - "type": "string", - "example": "crid://swr.de/123450", - "pattern": "^(c|b)rid://.+$" - }, - "title": { - "type": "string" - }, - "url": { - "type": "string", - "format": "uri" - }, - "alternateIds": { - "type": "array", - "items": { - "type": "string", - "example": "https://normdb.ivz.cn.ard.de/sendereihe/427" - } - } - } - }, - "eventV1PostBody": { - "additionalProperties": false, - "required": [ - "type", - "start", - "title", - "services", - "playlistItemId" - ], - "type": "object", - "description": "**Please also note the details in the `POST /events/v1` endpoint above!**\n", - "properties": { - "event": { - "type": "string", - "description": "If set, it needs to match the URL event parameter", - "example": "de.ard.eventhub.v1.radio.track.playing", - "enum": [ - "de.ard.eventhub.v1.radio.track.playing", - "de.ard.eventhub.v1.radio.track.next" - ] - }, - "type": { - "type": "string", - "description": "The type of the element that triggered this event. See additional file in docs for details.", - "example": "music", - "enum": [ - "audio", - "commercial", - "jingle", - "live", - "music", - "news", - "traffic", - "weather" - ] - }, - "start": { - "type": "string", - "description": "ISO8601 compliant timestamp", - "format": "iso8601-timestamp", - "example": "2020-01-19T06:00:00+01:00" - }, - "length": { - "type": "number", - "format": "float", - "description": "Scheduled length of the element in seconds", - "example": 240, - "nullable": true - }, - "title": { - "type": "string", - "description": "Representative title for external use", - "example": "Song name" - }, - "artist": { - "type": "string", - "description": "Pre-formatted artist information", - "example": "Sam Feldt feat. Someone Else", - "nullable": true - }, - "contributors": { - "type": "array", - "description": "Full details about involved artists if available", - "nullable": true, - "items": { - "type": "object", - "required": [ - "name", - "role" - ], - "properties": { - "name": { - "type": "string", - "example": "Sam Feldt" - }, - "role": { - "type": "string", - "example": "artist", - "enum": [ - "artist", - "author", - "composer", - "performer" - ] - }, - "normDb": { - "type": "object", - "description": "Reference to an entity in ARD's Norm-DB catalog", - "nullable": true, - "properties": { - "type": { - "type": "string", - "example": "Person" - }, - "id": { - "type": "string", - "example": "1641010" - } - } - }, - "isni": { - "type": "string", - "description": "ISNI ID if available", - "nullable": true, - "externalDocs": { - "description": "International Standard Name Identifier", - "url": "https://kb.ddex.net/display/HBK/Communication+of+Identifiers+in+DDEX+Messages" - } - }, - "url": { - "type": "string", - "description": "Can link to external reference", - "nullable": true - } - } - } - }, - "services": { - "type": "array", - "description": "The playing stations unique Service-IDs. Do not include the Service-Type suffix.", - "items": { - "minItems": 1, - "allOf": [ - { - "$ref": "#/components/schemas/services" - } - ] - } - }, - "references": { - "type": "array", - "description": "related external entities", - "nullable": true, - "items": { - "minItems": 0, - "allOf": [ - { - "$ref": "#/components/schemas/reference" - } - ] - } - }, - "playlistItemId": { - "type": "string", - "description": "Unique identifier (within a publisher) to connect next and playing items if needed", - "example": "swr3-5678" - }, - "hfdbIds": { - "type": "array", - "description": "Can reference all available tracks in ARD HFDB instances. Should ideally at least include the common ZSK instance.", - "nullable": true, - "items": { - "type": "string" - }, - "example": [ - "swrhfdb1.KONF.12345", - "zskhfdb1.KONF.12345" - ] - }, - "externalId": { - "type": "string", - "description": "Can reference the original ID in the publisher's system", - "example": "M012345.001", - "nullable": true - }, - "isrc": { - "type": "string", - "description": "Appropriate ISRC code if track is a music element", - "example": "DE012345678", - "nullable": true - }, - "upc": { - "type": "string", - "description": "Corresponding reference to an album where such ISRC was published", - "nullable": true - }, - "mpn": { - "type": "string", - "description": "If available the reference to the original delivery from MPN", - "nullable": true - }, - "media": { - "type": "array", - "description": "Can contain an array of media files like cover, artist, etc.", - "nullable": true, - "items": { - "required": [ - "type", - "url", - "description" - ], - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "cover", - "artist", - "anchor", - "audio", - "video" - ], - "example": "cover" - }, - "url": { - "type": "string", - "example": "https://example.com/cover.jpg" - }, - "templateUrl": { - "type": "string", - "example": "https://example.com/cover.jpg?width={width}", - "nullable": true - }, - "description": { - "type": "string", - "example": "Cover Demo Artist" - }, - "attribution": { - "type": "string", - "example": "Photographer XYZ", - "nullable": true - } - } - } - }, - "plugins": { - "type": "array", - "description": "Highly optional field for future third-party metadata distribution or other connected services", - "nullable": true, - "items": { - "type": "object", - "properties": { - "type": { - "type": "string", - "example": "postToThirdPartyPlatformXYZ" - } - } - } - }, - "id": { - "type": "string", - "description": "ID gets inserted by Eventhub as string-formatted number, but might be a true string in the future, do not expect this string to remain numbers only!", - "example": "1234567890" - } - } - }, - "eventV1ResBody": { - "type": "object", - "properties": { - "statuses": { - "type": "object", - "properties": { - "published": { - "type": "integer", - "example": 1 - }, - "blocked": { - "type": "integer", - "example": 0 - }, - "failed": { - "type": "integer", - "example": 0 - } - } - }, - "event": { - "$ref": "#/components/schemas/eventV1PostBody" - }, - "trace": { - "type": "string", - "example": null - } - } - }, - "subscriptionPost": { - "required": [ - "type", - "method", - "url", - "contact", - "topic" - ], - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "PUBSUB" - ], - "example": "PUBSUB" - }, - "method": { - "type": "string", - "enum": [ - "PUSH" - ], - "example": "PUSH" - }, - "url": { - "type": "string", - "description": "Publicly accessible URL that should receive the events", - "example": "https://example.com/my/webhook/for/this/subscription" - }, - "contact": { - "type": "string", - "description": "Email address to be contacted in case of problems with this subscription", - "example": "my-emergency-and-notifications-contact@ard.de" - }, - "topic": { - "type": "string", - "description": "ID of the topic to subscribe to", - "example": "topic-id-to-subscribe-to" - } - } - }, - "subscriptionsList": { - "type": "array", - "items": { - "allOf": [ - { - "$ref": "#/components/schemas/subscriptionResponse" - } - ] - } - }, - "subscriptionResponse": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "PUBSUB" - ], - "example": "PUBSUB" - }, - "method": { - "type": "string", - "enum": [ - "PUSH" - ], - "example": "PUSH" - }, - "name": { - "type": "string", - "description": "ID of the subscription to be referenced in API calls", - "example": "de.ard.eventhub.subscription.subscription-id" - }, - "path": { - "type": "string", - "description": "Path of subscription in project", - "example": "projects/ard-eventhub/subscriptions/subscription-name" - }, - "topic": { - "type": "object", - "description": "Object of the subscribed topic", - "properties": { - "id": { - "type": "string", - "example": "urn:ard:permanent-livestream:topic-id" - }, - "name": { - "type": "string", - "example": "de.ard.eventhub.dev.urn%3Aard%3Apermanent-livestream%3Atopic-id" - }, - "path": { - "type": "string", - "example": "projects/ard-eventhub/topics/topic-name" - } - } - }, - "ackDeadlineSeconds": { - "type": "integer", - "example": 20 - }, - "retryPolicy": { - "type": "string", - "example": null - }, - "serviceAccount": { - "type": "string", - "example": "name-of-service-account" - }, - "url": { - "type": "string", - "description": "Publicly accessible URL that should receive the events", - "example": "https://example.com/my/webhook/for/this/subscription" - }, - "contact": { - "type": "string", - "description": "Email address to be contacted in case of problems with this subscription", - "example": "my-emergency-and-notifications-contact@ard.de" - }, - "institutionId": { - "type": "string", - "description": "ID of the institution the current user belongs to", - "example": "urn:ard:institution:institution-id" - } - } - }, - "subscriptionDeleted": { - "type": "object", - "properties": { - "valid": { - "type": "boolean", - "example": true - }, - "trace": { - "type": "string", - "example": null - } - } - }, - "topicResponse": { - "type": "array", - "items": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "PUBSUB" - ], - "example": "PUBSUB" - }, - "id": { - "type": "string", - "example": "urn:ard:permanent-livestream:topic-id" - }, - "name": { - "type": "string", - "example": "de.ard.eventhub.dev.urn%3Aard%3Apermanent-livestream%3Atopic-id" - }, - "path": { - "type": "string", - "example": "projects/ard-eventhub/topics/topic-name" - }, - "labels": { - "type": "object", - "properties": { - "id": { - "type": "string", - "example": "1234567890" - }, - "creator-slug": { - "type": "string", - "example": "ard-eventhub-swr" - }, - "publisher-slug": { - "type": "string", - "example": "swr-rheinland-pfalz" - }, - "stage": { - "type": "string", - "example": "prod" - }, - "created": { - "type": "string", - "example": "2021-03-25" - }, - "institution-slug": { - "type": "string", - "example": "sudwestrundfunk" - } - } - } - } - } - } - } - } -} \ No newline at end of file + "openapi": "3.0.3", + "info": { + "title": "ARD-Eventhub", + "description": "ARD system to distribute real-time (live) metadata for primarily radio broadcasts.", + "termsOfService": "https://www.ard.de", + "contact": { + "email": "lab@swr.de" + }, + "license": { + "name": "European Union Public License 1.2", + "url": "https://spdx.org/licenses/EUPL-1.2.html" + }, + "version": "1.5.1" + }, + "externalDocs": { + "description": "ARD-Eventhub Documentation", + "url": "https://swrlab.github.io/ard-eventhub/" + }, + "servers": [ + { + "url": "/", + "description": "Local (domain-relative) environment" + } + ], + "tags": [ + { + "name": "auth", + "description": "Authentication services for Eventhub" + }, + { + "name": "events", + "description": "Manage events" + }, + { + "name": "subscriptions", + "description": "Access to subscription management" + }, + { + "name": "topics", + "description": "Access to topics details" + } + ], + "paths": { + "/auth/login": { + "post": { + "tags": ["auth"], + "summary": "Swap login credentials for a token", + "operationId": "authLoginPost", + "requestBody": { + "content": { + "application/json": { + "schema": { + "additionalProperties": false, + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "my-email@example.com" + }, + "password": { + "type": "string", + "example": "my-password" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Authentication successful", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/authResponse" + } + } + } + }, + "400": { + "description": "Bad Request (invalid input)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorBadRequest" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorInternalServerError" + } + } + } + } + } + } + }, + "/auth/refresh": { + "post": { + "tags": ["auth"], + "summary": "Swap refresh token for new id token", + "operationId": "authRefreshPost", + "requestBody": { + "content": { + "application/json": { + "schema": { + "additionalProperties": false, + "type": "object", + "properties": { + "refreshToken": { + "type": "string", + "example": "abcXYZ..." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Authentication successful", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/authResponse" + } + } + } + }, + "400": { + "description": "Bad Request (invalid input)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorBadRequest" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorInternalServerError" + } + } + } + } + } + } + }, + "/auth/reset": { + "post": { + "tags": ["auth"], + "summary": "Request password reset email", + "operationId": "authResetPost", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string", + "example": "my-email@example.com" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Request successful", + "content": {} + }, + "400": { + "description": "Bad Request (invalid input)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorBadRequest" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorInternalServerError" + } + } + } + } + } + } + }, + "/events/de.ard.eventhub.v1.radio.track.next": { + "post": { + "tags": ["events"], + "summary": "Distribute a next track", + "operationId": "eventPostV1RadioTrackNext", + "security": [ + { + "bearerAuth": [] + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/eventV1RadioTrack" + }, + "responses": { + "201": { + "$ref": "#/components/responses/eventV1RadioTrack" + }, + "400": { + "description": "Bad Request (invalid input)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorBadRequest" + } + } + } + }, + "401": { + "description": "Missing authentication", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorUnauthorized" + } + } + } + }, + "403": { + "description": "Invalid authorization", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorForbidden" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorInternalServerError" + } + } + } + } + } + } + }, + "/events/de.ard.eventhub.v1.radio.track.playing": { + "post": { + "tags": ["events"], + "summary": "Distribute a now-playing track", + "operationId": "eventPostV1RadioTrackPlaying", + "security": [ + { + "bearerAuth": [] + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/eventV1RadioTrack" + }, + "responses": { + "201": { + "$ref": "#/components/responses/eventV1RadioTrack" + }, + "400": { + "description": "Bad Request (invalid input)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorBadRequest" + } + } + } + }, + "401": { + "description": "Missing authentication", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorUnauthorized" + } + } + } + }, + "403": { + "description": "Invalid authorization", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorForbidden" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorInternalServerError" + } + } + } + } + } + } + }, + "/subscriptions": { + "get": { + "tags": ["subscriptions"], + "summary": "List all subscriptions for this user", + "operationId": "subscriptionList", + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Subscriptions found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/subscriptionsList" + } + } + } + }, + "401": { + "description": "Missing authentication", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorUnauthorized" + } + } + } + }, + "403": { + "description": "Invalid authorization", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorForbidden" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorInternalServerError" + } + } + } + } + } + }, + "post": { + "tags": ["subscriptions"], + "summary": "Add a new subscription", + "operationId": "subscriptionPost", + "security": [ + { + "bearerAuth": [] + } + ], + "requestBody": { + "description": "New event to be distributed to subscribers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/subscriptionPost" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Subscription created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/subscriptionResponse" + } + } + } + }, + "400": { + "description": "Bad Request (invalid input)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorBadRequest" + } + } + } + }, + "401": { + "description": "Missing authentication", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorUnauthorized" + } + } + } + }, + "403": { + "description": "Invalid authorization", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorForbidden" + } + } + } + }, + "404": { + "description": "Topic for subscription not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorNotFound" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorInternalServerError" + } + } + } + } + } + } + }, + "/subscriptions/{name}": { + "get": { + "tags": ["subscriptions"], + "summary": "Get details about a single subscription from this user", + "operationId": "subscriptionsGet", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "name": "name", + "in": "path", + "description": "`name` of the desired subscription", + "required": true, + "style": "simple", + "explode": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Subscription found", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/subscriptionResponse" + } + } + } + } + }, + "401": { + "description": "Missing authentication", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorUnauthorized" + } + } + } + }, + "403": { + "description": "Invalid authorization", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorForbidden" + } + } + } + }, + "404": { + "description": "Subscription not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorNotFound" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorInternalServerError" + } + } + } + } + } + }, + "delete": { + "tags": ["subscriptions"], + "summary": "Remove a single subscription by this user", + "operationId": "subscriptionsDelete", + "security": [ + { + "bearerAuth": [] + } + ], + "parameters": [ + { + "name": "name", + "in": "path", + "description": "`name` of the desired subscription", + "required": true, + "style": "simple", + "explode": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Subscription deleted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/subscriptionDeleted" + } + } + } + }, + "401": { + "description": "Missing authentication", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorUnauthorized" + } + } + } + }, + "403": { + "description": "Invalid authorization", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorForbidden" + } + } + } + }, + "404": { + "description": "Subscription not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorNotFound" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorInternalServerError" + } + } + } + } + } + } + }, + "/topics": { + "get": { + "tags": ["topics"], + "summary": "List all available topics", + "operationId": "topicsGet", + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "Topics found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/topicResponse" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorInternalServerError" + } + } + } + } + } + } + } + }, + "components": { + "requestBodies": { + "eventV1RadioTrack": { + "description": "New event to be distributed to subscribers.\nThe Eventhub format validation expects only a subset of these variables as minimum set. All other fields are technically optional, but **highly encouraged** to be included, so a best-possible metadata exchange is possible.\nThe subset is defined in the list of required fields of Schemas `eventV1PostBody`, resulting in this body:\n```json\n{\n \"type\": \"music\",\n \"start\": \"2020-01-19T06:00:00+01:00\",\n \"title\": \"Song name\",\n \"services\": [ { ... } ],\n \"playlistItemId\": \"swr3-5678\"\n}\n```\nRequired fields not specified in the Schema, will cause your request to fail.\nThe `id` is inserted by Eventhub as string-formatted number, but might be a true string in the future, do not expect this string to remain numbers only!\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/eventV1PostBody" + } + } + }, + "required": true + } + }, + "responses": { + "eventV1RadioTrack": { + "description": "Event created\n*Note:* The first request of an event for an externalId that is not registered yet, will return the status `failed: 1`. This indicates that a new topic for the externalId has been created, and the request needs to be repeated:\n```json\n\"statuses\": {\n \"published\": 0,\n \"blocked\": 0,\n \"failed\": 1\n}\n```\nIf the request returns the status `blocked: 1`, it indicates that you are not allowed to publish events under the given publisherId.\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/eventV1ResBody" + } + } + } + } + }, + "securitySchemes": { + "bearerAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT" + } + }, + "schemas": { + "authResponse": { + "type": "object", + "properties": { + "expiresIn": { + "type": "number", + "description": "TTL for the token in seconds", + "example": 3600 + }, + "expires": { + "type": "string", + "description": "ISO8601 compliant timestamp for the token expiry", + "format": "iso8601-timestamp", + "example": "2020-01-19T06:00:00+01:00" + }, + "token": { + "type": "string", + "description": "ready to use token for API queries", + "example": "ey..." + }, + "refreshToken": { + "type": "string", + "description": "refresh token to be used with `/auth/refresh`/ endpoint", + "example": "A0..." + }, + "user": { + "type": "object", + "description": "Firebase-type user object obtained by decoding the JWT token" + }, + "trace": { + "type": "string", + "example": null + } + } + }, + "errorBadRequest": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "request.body should have required property 'XYZ'" + }, + "errors": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "properties": { + "path": { + "type": "string", + "example": ".body.XYZ" + }, + "message": { + "type": "string", + "example": "should have required property 'XYZ'" + }, + "errorCode": { + "type": "string", + "example": "required.openapi.validation" + } + } + } + }, + "trace": { + "type": "string", + "example": null + } + } + }, + "errorUnauthorized": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "request.headers should have required property 'Authorization'" + }, + "errors": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "properties": { + "path": { + "type": "string", + "example": ".headers.authorization" + }, + "message": { + "type": "string", + "example": "should have required property 'authorization'" + }, + "errorCode": { + "type": "string", + "example": "required.user" + } + } + } + }, + "trace": { + "type": "string", + "example": null + } + } + }, + "errorNotFound": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "object 'object.name' not found" + }, + "trace": { + "type": "string", + "example": null + } + } + }, + "errorForbidden": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "user is missing required permission" + }, + "errors": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "properties": { + "path": { + "type": "string", + "example": ".headers.authorization" + }, + "message": { + "type": "string", + "example": "should have required permission" + }, + "errorCode": { + "type": "string", + "example": "required.user.permission" + } + } + } + }, + "trace": { + "type": "string", + "example": null + } + } + }, + "errorInternalServerError": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Internal Server Error" + }, + "trace": { + "type": "string", + "example": null + } + } + }, + "services": { + "type": "object", + "required": ["type", "externalId", "publisherId"], + "properties": { + "type": { + "type": "string", + "example": "PermanentLivestream", + "enum": ["EventLivestream", "PermanentLivestream"] + }, + "externalId": { + "type": "string", + "example": "crid://swr.de/123450" + }, + "publisherId": { + "type": "string", + "description": "External ID or globally unique identifier (Core ID) for the associated publisher.\nWhen no Core ID is provided, the External ID will be converted by Eventhub.\n", + "example": "248000" + }, + "id": { + "type": "string", + "description": "Globally unique identifier, created by Eventhub", + "example": "urn:ard:permanent-livestream:49267f7d67be180d" + } + } + }, + "reference": { + "type": "object", + "additionalProperties": false, + "required": ["type", "externalId"], + "properties": { + "type": { + "type": "string", + "enum": [ + "Episode", + "Section", + "Publication", + "Broadcast", + "Show", + "Season", + "Article" + ] + }, + "id": { + "type": "string", + "pattern": "^urn:ard:[a-z0-9-]+:[a-z0-9-]+$", + "example": "urn:ard:show:49267f7d67be180d" + }, + "externalId": { + "type": "string", + "example": "crid://swr.de/123450", + "pattern": "^(c|b)rid://.+$" + }, + "title": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + }, + "alternateIds": { + "type": "array", + "items": { + "type": "string", + "example": "https://normdb.ivz.cn.ard.de/sendereihe/427" + } + } + } + }, + "eventV1PostBody": { + "additionalProperties": false, + "required": ["type", "start", "title", "services", "playlistItemId"], + "type": "object", + "description": "**Please also note the details in the `POST /events/v1` endpoint above!**\n", + "properties": { + "event": { + "type": "string", + "description": "If set, it needs to match the URL event parameter", + "example": "de.ard.eventhub.v1.radio.track.playing", + "enum": [ + "de.ard.eventhub.v1.radio.track.playing", + "de.ard.eventhub.v1.radio.track.next" + ] + }, + "type": { + "type": "string", + "description": "The type of the element that triggered this event. See additional file in docs for details.", + "example": "music", + "enum": [ + "audio", + "commercial", + "jingle", + "live", + "music", + "news", + "traffic", + "weather" + ] + }, + "start": { + "type": "string", + "description": "ISO8601 compliant timestamp", + "format": "iso8601-timestamp", + "example": "2020-01-19T06:00:00+01:00" + }, + "length": { + "type": "number", + "format": "float", + "description": "Scheduled length of the element in seconds", + "example": 240, + "nullable": true + }, + "title": { + "type": "string", + "description": "Representative title for external use", + "example": "Song name" + }, + "artist": { + "type": "string", + "description": "Pre-formatted artist information", + "example": "Sam Feldt feat. Someone Else", + "nullable": true + }, + "contributors": { + "type": "array", + "description": "Full details about involved artists if available", + "nullable": true, + "items": { + "type": "object", + "required": ["name", "role"], + "properties": { + "name": { + "type": "string", + "example": "Sam Feldt" + }, + "role": { + "type": "string", + "example": "artist", + "enum": [ + "artist", + "author", + "composer", + "performer" + ] + }, + "normDb": { + "type": "object", + "description": "Reference to an entity in ARD's Norm-DB catalog", + "nullable": true, + "properties": { + "type": { + "type": "string", + "example": "Person" + }, + "id": { + "type": "string", + "example": "1641010" + } + } + }, + "isni": { + "type": "string", + "description": "ISNI ID if available", + "nullable": true, + "externalDocs": { + "description": "International Standard Name Identifier", + "url": "https://kb.ddex.net/display/HBK/Communication+of+Identifiers+in+DDEX+Messages" + } + }, + "url": { + "type": "string", + "description": "Can link to external reference", + "nullable": true + } + } + } + }, + "services": { + "type": "array", + "description": "The playing stations unique Service-IDs. Do not include the Service-Type suffix.", + "items": { + "minItems": 1, + "allOf": [ + { + "$ref": "#/components/schemas/services" + } + ] + } + }, + "references": { + "type": "array", + "description": "related external entities", + "nullable": true, + "items": { + "minItems": 0, + "allOf": [ + { + "$ref": "#/components/schemas/reference" + } + ] + } + }, + "playlistItemId": { + "type": "string", + "description": "Unique identifier (within a publisher) to connect next and playing items if needed", + "example": "swr3-5678" + }, + "hfdbIds": { + "type": "array", + "description": "Can reference all available tracks in ARD HFDB instances. Should ideally at least include the common ZSK instance.", + "nullable": true, + "items": { + "type": "string" + }, + "example": ["swrhfdb1.KONF.12345", "zskhfdb1.KONF.12345"] + }, + "externalId": { + "type": "string", + "description": "Can reference the original ID in the publisher's system", + "example": "M012345.001", + "nullable": true + }, + "isrc": { + "type": "string", + "description": "Appropriate ISRC code if track is a music element", + "example": "DE012345678", + "nullable": true + }, + "upc": { + "type": "string", + "description": "Corresponding reference to an album where such ISRC was published", + "nullable": true + }, + "mpn": { + "type": "string", + "description": "If available the reference to the original delivery from MPN", + "nullable": true + }, + "media": { + "type": "array", + "description": "Can contain an array of media files like cover, artist, etc.", + "nullable": true, + "items": { + "required": ["type", "url", "description"], + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "cover", + "artist", + "anchor", + "audio", + "video" + ], + "example": "cover" + }, + "url": { + "type": "string", + "example": "https://example.com/cover.jpg" + }, + "templateUrl": { + "type": "string", + "example": "https://example.com/cover.jpg?width={width}", + "nullable": true + }, + "description": { + "type": "string", + "example": "Cover Demo Artist" + }, + "attribution": { + "type": "string", + "example": "Photographer XYZ", + "nullable": true + } + } + } + }, + "plugins": { + "type": "array", + "description": "Highly optional field for future third-party metadata distribution or other connected services", + "nullable": true, + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "example": "postToThirdPartyPlatformXYZ" + } + } + } + }, + "id": { + "type": "string", + "description": "ID gets inserted by Eventhub as string-formatted number, but might be a true string in the future, do not expect this string to remain numbers only!", + "example": "1234567890" + } + } + }, + "eventV1ResBody": { + "type": "object", + "properties": { + "statuses": { + "type": "object", + "properties": { + "published": { + "type": "integer", + "example": 1 + }, + "blocked": { + "type": "integer", + "example": 0 + }, + "failed": { + "type": "integer", + "example": 0 + } + } + }, + "event": { + "$ref": "#/components/schemas/eventV1PostBody" + }, + "trace": { + "type": "string", + "example": null + } + } + }, + "subscriptionPost": { + "required": ["type", "method", "url", "contact", "topic"], + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["PUBSUB"], + "example": "PUBSUB" + }, + "method": { + "type": "string", + "enum": ["PUSH"], + "example": "PUSH" + }, + "url": { + "type": "string", + "description": "Publicly accessible URL that should receive the events", + "example": "https://example.com/my/webhook/for/this/subscription" + }, + "contact": { + "type": "string", + "description": "Email address to be contacted in case of problems with this subscription", + "example": "my-emergency-and-notifications-contact@ard.de" + }, + "topic": { + "type": "string", + "description": "ID of the topic to subscribe to", + "example": "topic-id-to-subscribe-to" + } + } + }, + "subscriptionsList": { + "type": "array", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/subscriptionResponse" + } + ] + } + }, + "subscriptionResponse": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["PUBSUB"], + "example": "PUBSUB" + }, + "method": { + "type": "string", + "enum": ["PUSH"], + "example": "PUSH" + }, + "name": { + "type": "string", + "description": "ID of the subscription to be referenced in API calls", + "example": "de.ard.eventhub.subscription.subscription-id" + }, + "path": { + "type": "string", + "description": "Path of subscription in project", + "example": "projects/ard-eventhub/subscriptions/subscription-name" + }, + "topic": { + "type": "object", + "description": "Object of the subscribed topic", + "properties": { + "id": { + "type": "string", + "example": "urn:ard:permanent-livestream:topic-id" + }, + "name": { + "type": "string", + "example": "de.ard.eventhub.dev.urn%3Aard%3Apermanent-livestream%3Atopic-id" + }, + "path": { + "type": "string", + "example": "projects/ard-eventhub/topics/topic-name" + } + } + }, + "ackDeadlineSeconds": { + "type": "integer", + "example": 20 + }, + "retryPolicy": { + "type": "string", + "example": null + }, + "serviceAccount": { + "type": "string", + "example": "name-of-service-account" + }, + "url": { + "type": "string", + "description": "Publicly accessible URL that should receive the events", + "example": "https://example.com/my/webhook/for/this/subscription" + }, + "contact": { + "type": "string", + "description": "Email address to be contacted in case of problems with this subscription", + "example": "my-emergency-and-notifications-contact@ard.de" + }, + "institutionId": { + "type": "string", + "description": "ID of the institution the current user belongs to", + "example": "urn:ard:institution:institution-id" + } + } + }, + "subscriptionDeleted": { + "type": "object", + "properties": { + "valid": { + "type": "boolean", + "example": true + }, + "trace": { + "type": "string", + "example": null + } + } + }, + "topicResponse": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["PUBSUB"], + "example": "PUBSUB" + }, + "id": { + "type": "string", + "example": "urn:ard:permanent-livestream:topic-id" + }, + "name": { + "type": "string", + "example": "de.ard.eventhub.dev.urn%3Aard%3Apermanent-livestream%3Atopic-id" + }, + "path": { + "type": "string", + "example": "projects/ard-eventhub/topics/topic-name" + }, + "labels": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "1234567890" + }, + "creator-slug": { + "type": "string", + "example": "ard-eventhub-swr" + }, + "publisher-slug": { + "type": "string", + "example": "swr-rheinland-pfalz" + }, + "stage": { + "type": "string", + "example": "prod" + }, + "created": { + "type": "string", + "example": "2021-03-25" + }, + "institution-slug": { + "type": "string", + "example": "sudwestrundfunk" + } + } + } + } + } + } + } + } +}