diff --git a/.deployment/monitoring/requirements.yml b/.deployment/monitoring/requirements.yml index ad768112f..902dc1544 100644 --- a/.deployment/monitoring/requirements.yml +++ b/.deployment/monitoring/requirements.yml @@ -3,4 +3,4 @@ collections: - name: prometheus.prometheus version: 0.16.3 - name: grafana.grafana - version: 5.2.0 + version: 5.5.0 diff --git a/.deployment/monitoring/setup.yml b/.deployment/monitoring/setup.yml index 4dc8b39e4..b06d88add 100644 --- a/.deployment/monitoring/setup.yml +++ b/.deployment/monitoring/setup.yml @@ -22,10 +22,7 @@ 34313337343261363739653632663736613763613763636561363633653362613238333638313733 3265616634663435390a306566656530373362623562333232373364393261353864636665316339 36303161333735313639333735613132626364346536613133626534633063376139 - # The latest grafana ansible role does not support grafana version 11 (or later) for now - # due to braking configuration changes. Please update the grafana ansible collection - # and remove this setting to upgrade to the newest grafana version. - grafana_version: 10.4.3 + grafana_version: 11.2.0 tasks: - name: deploy nginx vhosts diff --git a/backend/src/api/model/realm/mod.rs b/backend/src/api/model/realm/mod.rs index 2e28a1b2c..6d2788769 100644 --- a/backend/src/api/model/realm/mod.rs +++ b/backend/src/api/model/realm/mod.rs @@ -22,6 +22,7 @@ mod mutations; pub(crate) use mutations::{ ChildIndex, NewRealm, RemovedRealm, UpdateRealm, UpdatedPermissions, UpdatedRealmName, RealmSpecifier, RealmLineageComponent, CreateRealmLineageOutcome, + RemoveMountedSeriesOutcome, }; @@ -231,6 +232,28 @@ impl Realm { self.is_user_realm() && self.parent_key.is_none() } + /// Returns all immediate children of this realm. The children are always + /// ordered by the internal index. If `childOrder` returns an ordering + /// different from `BY_INDEX`, the frontend is supposed to sort the + /// children. + pub(crate) async fn children(&self, context: &Context) -> ApiResult> { + let selection = Self::select(); + let query = format!( + "select {selection} \ + from realms \ + where realms.parent = $1 \ + order by index", + ); + context.db + .query_mapped( + &query, + &[&self.key], + |row| Self::from_row_start(&row), + ) + .await? + .pipe(Ok) + } + /// Returns the username of the user owning this realm tree IF it is a user /// realm. Otherwise returns `None`. pub(crate) fn owning_user(&self) -> Option<&str> { @@ -412,21 +435,7 @@ impl Realm { /// different from `BY_INDEX`, the frontend is supposed to sort the /// children. async fn children(&self, context: &Context) -> ApiResult> { - let selection = Self::select(); - let query = format!( - "select {selection} \ - from realms \ - where realms.parent = $1 \ - order by index", - ); - context.db - .query_mapped( - &query, - &[&self.key], - |row| Self::from_row_start(&row), - ) - .await? - .pipe(Ok) + self.children(context).await } /// Returns the (content) blocks of this realm. diff --git a/backend/src/api/model/realm/mutations.rs b/backend/src/api/model/realm/mutations.rs index a32bf62a7..c02090294 100644 --- a/backend/src/api/model/realm/mutations.rs +++ b/backend/src/api/model/realm/mutations.rs @@ -1,9 +1,13 @@ use std::collections::{HashMap, HashSet}; use crate::{ - api::{Context, Id, err::{ApiResult, invalid_input, map_db_err}}, + api::{ + err::{invalid_input, map_db_err, ApiResult}, + model::block::RemovedBlock, Context, Id, + }, + auth::AuthContext, db::types::Key, - prelude::*, auth::AuthContext, + prelude::*, }; use super::{Realm, RealmOrder}; @@ -379,6 +383,13 @@ impl UpdatedRealmName { block: Some(block), } } + + pub(crate) fn plain(name: String) -> Self { + Self { + plain: Some(name), + block: None, + } + } } #[derive(juniper::GraphQLInputObject)] @@ -410,3 +421,10 @@ pub(crate) struct RemovedRealm { pub struct CreateRealmLineageOutcome { pub num_created: i32, } + +#[derive(juniper::GraphQLObject)] +#[graphql(Context = Context)] +pub(crate) struct RemoveMountedSeriesOutcome { + pub removed_realm: Option, + pub removed_block: Option, +} diff --git a/backend/src/api/model/series.rs b/backend/src/api/model/series.rs index 645a4290d..511707bec 100644 --- a/backend/src/api/model/series.rs +++ b/backend/src/api/model/series.rs @@ -176,7 +176,7 @@ impl Node for Series { #[derive(GraphQLInputObject)] pub(crate) struct NewSeries { - opencast_id: String, + pub(crate) opencast_id: String, title: String, // TODO In the future this `struct` can be extended with additional // (potentially optional) fields. For now we only need these. @@ -184,3 +184,8 @@ pub(crate) struct NewSeries { // in some way, and since passing stuff like metadata isn't trivial either // I think it's okay to leave it at that for now. } + +#[derive(juniper::GraphQLObject)] +pub struct PreparedSeries { + pub id: Id, +} diff --git a/backend/src/api/mutation.rs b/backend/src/api/mutation.rs index fd926f934..62b676873 100644 --- a/backend/src/api/mutation.rs +++ b/backend/src/api/mutation.rs @@ -10,7 +10,7 @@ use super::{ id::Id, Node, model::{ - series::{Series, NewSeries}, + series::{Series, NewSeries, PreparedSeries}, realm::{ ChildIndex, NewRealm, @@ -23,6 +23,7 @@ use super::{ RealmSpecifier, RealmLineageComponent, CreateRealmLineageOutcome, + RemoveMountedSeriesOutcome, }, block::{ BlockValue, @@ -275,6 +276,94 @@ impl Mutation { Ok(CreateRealmLineageOutcome { num_created }) } + async fn prepare_series(series: NewSeries, context: &Context) -> ApiResult { + if context.auth != AuthContext::TrustedExternal { + return Err(not_authorized!("only trusted external applications can use this mutation")); + } + + let series = Series::create(series, context).await?; + + Ok(PreparedSeries { id: series.id() }) + } + + async fn add_series_mount_point( + series_oc_id: String, + target_path: String, + context: &Context, + ) -> ApiResult { + if context.auth != AuthContext::TrustedExternal { + return Err(not_authorized!("only trusted external applications can use this mutation")); + } + + let series = Series::load_by_opencast_id(series_oc_id, context) + .await? + .ok_or_else(|| invalid_input!("`seriesId` does not refer to a valid series"))?; + + let target_realm = Realm::load_by_path(target_path, context) + .await? + .ok_or_else(|| invalid_input!("`targetRealmPath` does not refer to a valid realm"))?; + + BlockValue::add_series( + Id::realm(target_realm.key), + 0, + NewSeriesBlock { + series: series.id(), + show_title: false, + show_metadata: true, + order: VideoListOrder::NewToOld, + layout: VideoListLayout::Gallery, + }, + context, + ).await?; + + let block = &BlockValue::load_for_realm(target_realm.key, context).await?[0]; + + Realm::rename( + target_realm.id(), + UpdatedRealmName::from_block(block.id()), + context, + ).await + } + + async fn remove_series_mount_point( + path: String, + context: &Context, + ) -> ApiResult { + if context.auth != AuthContext::TrustedExternal { + return Err(not_authorized!("only trusted external applications can use this mutation")); + } + + let old_realm = Realm::load_by_path(path, context) + .await? + .ok_or_else(|| invalid_input!("`currentRealmPath` does not refer to a valid realm"))?; + + let blocks = BlockValue::load_for_realm(old_realm.key, context).await?; + + if blocks.len() > 1 { + return Err(invalid_input!("series can only be moved if its current realm has no other blocks")); + } + + let mut removed_realm = None; + let mut removed_block = None; + + if old_realm.children(context).await?.len() == 0 { + // The realm has no children, so it can be removed. + removed_realm = Some(Realm::remove(old_realm.id(), context).await?); + } else { + // At this point we can be certain that there is only one block, which is the series block. + // Ideally we would restore the previous title, but that's not stored anywhere. So the realm + // gets the name of its path segment. + Realm::rename( + old_realm.id(), + UpdatedRealmName::plain(old_realm.path_segment), + context, + ).await?; + removed_block = Some(BlockValue::remove(blocks[0].id(), context).await?); + } + + Ok(RemoveMountedSeriesOutcome { removed_realm, removed_block }) + } + /// Atomically mount a series into an (empty) realm. /// Creates all the necessary realms on the path to the target /// and adds a block with the given series at the leaf. diff --git a/docs/package-lock.json b/docs/package-lock.json index 21332f3ef..3e7d0f5b8 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -4762,9 +4762,9 @@ } }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -4774,7 +4774,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -6299,9 +6299,9 @@ } }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "engines": { "node": ">= 0.8" } @@ -6538,36 +6538,36 @@ } }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", + "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -6608,9 +6608,9 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/express/node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, "node_modules/express/node_modules/range-parser": { "version": "1.2.1", @@ -6782,12 +6782,12 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -8598,9 +8598,12 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge-stream": { "version": "2.0.0", @@ -8939,9 +8942,12 @@ } }, "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -10064,11 +10070,11 @@ "integrity": "sha512-QFADYnsVoBMw1srW7OVKEYjG+MbIa49s54w1MA1EDY6r2r/sTcKKYqRX1f4GYvnXP7eN/Pe9HFcX+hwzmrXRHA==" }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -11229,9 +11235,9 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -11264,6 +11270,14 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -11376,14 +11390,14 @@ } }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" @@ -16827,9 +16841,9 @@ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" }, "body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "requires": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -16839,7 +16853,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -17897,9 +17911,9 @@ "integrity": "sha512-SNujglcLTTg+lDAcApPNgEdudaqQFiAbJCqzjNxJkvN9vAwCGi0uu8IUVvx+f16h+V44KCY6Y2yboroc9pilHg==" }, "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" }, "end-of-stream": { "version": "1.4.4", @@ -18061,36 +18075,36 @@ } }, "express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", + "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -18125,9 +18139,9 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, "range-parser": { "version": "1.2.1", @@ -18265,12 +18279,12 @@ } }, "finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "requires": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -19573,9 +19587,9 @@ } }, "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==" }, "merge-stream": { "version": "2.0.0", @@ -19803,9 +19817,9 @@ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" }, "object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==" }, "object-keys": { "version": "1.1.1", @@ -20549,11 +20563,11 @@ "integrity": "sha512-QFADYnsVoBMw1srW7OVKEYjG+MbIa49s54w1MA1EDY6r2r/sTcKKYqRX1f4GYvnXP7eN/Pe9HFcX+hwzmrXRHA==" }, "qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "requires": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" } }, "queue": { @@ -21400,9 +21414,9 @@ } }, "send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "requires": { "debug": "2.6.9", "depd": "2.0.0", @@ -21434,6 +21448,11 @@ } } }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + }, "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -21537,14 +21556,14 @@ } }, "serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "requires": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" } }, "set-function-length": { diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 99fa76534..ee33ba37a 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -26,7 +26,7 @@ "i18next-browser-languagedetector": "^7.2.0", "lucide-react": "^0.439.0", "paella-basic-plugins": "1.44.10", - "paella-core": "1.49.3", + "paella-core": "1.49.6", "paella-mp4multiquality-plugin": "1.47.1", "paella-skins": "1.48.0", "paella-slide-plugins": "1.48.1", @@ -7690,9 +7690,10 @@ } }, "node_modules/paella-core": { - "version": "1.49.3", - "resolved": "https://registry.npmjs.org/paella-core/-/paella-core-1.49.3.tgz", - "integrity": "sha512-h558NyjOM7WcdxCZQUxUw12GjDJr4wADKGDMQK7dJ0w2QENxdFvaNaeUmopzzUDOBwWSYrZ2QKy+4zcfonk81A==", + "version": "1.49.6", + "resolved": "https://registry.npmjs.org/paella-core/-/paella-core-1.49.6.tgz", + "integrity": "sha512-90tZ+0AuAukCrTMykvCx4FbVhzSsS96nEgGvGr6DHp/hf7fW87ncye0GzntaAaB+ndmAs+FQl8oHRiZtUuYgbw==", + "license": "ECL-2.0", "dependencies": { "core-js": "^3.8.2", "hls.js": "^1.0.4" diff --git a/frontend/package.json b/frontend/package.json index cb2c82c63..172fa468a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -42,7 +42,7 @@ "i18next-browser-languagedetector": "^7.2.0", "lucide-react": "^0.439.0", "paella-basic-plugins": "1.44.10", - "paella-core": "1.49.3", + "paella-core": "1.49.6", "paella-mp4multiquality-plugin": "1.47.1", "paella-skins": "1.48.0", "paella-slide-plugins": "1.48.1", diff --git a/frontend/src/schema.graphql b/frontend/src/schema.graphql index 422a33395..9bb7eada4 100644 --- a/frontend/src/schema.graphql +++ b/frontend/src/schema.graphql @@ -478,6 +478,10 @@ type Realm implements Node { canCurrentUserModerate: Boolean! } +type PreparedSeries { + id: ID! +} + "A block just showing the list of videos in an Opencast playlist" type PlaylistBlock implements Block & RealmNameSourceBlock { playlist: Playlist @@ -597,6 +601,9 @@ type Mutation { the list. The first item is sub-realm of the root realm. """ createRealmLineage(realms: [RealmLineageComponent!]!): CreateRealmLineageOutcome! + prepareSeries(series: NewSeries!): PreparedSeries! + addSeriesMountPoint(seriesOcId: String!, targetPath: String!): Realm! + removeSeriesMountPoint(path: String!): RemoveMountedSeriesOutcome! """ Atomically mount a series into an (empty) realm. Creates all the necessary realms on the path to the target @@ -727,6 +734,11 @@ type EmptyQuery { dummy: Boolean } +type RemoveMountedSeriesOutcome { + removedRealm: RemovedRealm + removedBlock: RemovedBlock +} + union PlaylistSearchOutcome = SearchUnavailable | PlaylistSearchResults "A string in different languages"