Skip to content

Commit

Permalink
Change mount_series endpoint to accept and move existing series
Browse files Browse the repository at this point in the history
This is necessary for an upcoming admin UI feature which will allow
admins to change the path of series pages with no other blocks.
  • Loading branch information
owi92 committed Aug 13, 2024
1 parent 63a9fbf commit a06ba4e
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 15 deletions.
22 changes: 13 additions & 9 deletions backend/src/api/model/realm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,18 @@ impl Realm {
self.is_user_realm() && self.parent_key.is_none()
}

pub(crate) async fn number_of_descendants(&self, context: &Context) -> ApiResult<i32> {
let count = context.db
.query_one(
"select count(*) from realms where full_path like $1 || '/%'",
&[&self.full_path],
)
.await?
.get::<_, i64>(0);

Ok(count.try_into().expect("number of descendants overflows i32"))
}

/// 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> {
Expand Down Expand Up @@ -440,15 +452,7 @@ impl Realm {
/// Returns the number of realms that are descendants of this one
/// (excluding this one). Returns a number ≥ 0.
async fn number_of_descendants(&self, context: &Context) -> ApiResult<i32> {
let count = context.db
.query_one(
"select count(*) from realms where full_path like $1 || '/%'",
&[&self.full_path],
)
.await?
.get::<_, i64>(0);

Ok(count.try_into().expect("number of descendants overflows i32"))
self.number_of_descendants(context).await
}

/// Returns whether the current user has the rights to add sub-pages, edit realm content,
Expand Down
7 changes: 7 additions & 0 deletions backend/src/api/model/realm/mutations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,13 @@ impl UpdatedRealmName {
block: Some(block),
}
}

pub(crate) fn plain(name: String) -> Self {
Self {
plain: Some(name),
block: None,
}
}
}

#[derive(juniper::GraphQLInputObject)]
Expand Down
2 changes: 1 addition & 1 deletion backend/src/api/model/series.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
45 changes: 42 additions & 3 deletions backend/src/api/mutation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,12 +232,20 @@ impl Mutation {
BlockValue::remove(id, context).await
}

/// Atomically mount a series into an (empty) realm.
/// Atomically mount or move 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.
///
/// If the series is already mounted in `current_realm_path`, we need to check
/// if that realm has any other blocks.
/// - If it does, moving the series is not allowed.
/// - Otherwise, the series is moved, but there is some cleanup necessary:
/// - If the realm has any sub-realms: only the series block is removed.
/// - Otherwise the whole realm is removed.
async fn mount_series(
series: NewSeries,
parent_realm_path: String,
current_realm_path: Option<String>,
#[graphql(default = vec![])]
new_realms: Vec<RealmSpecifier>,
context: &Context,
Expand Down Expand Up @@ -268,8 +276,6 @@ impl Mutation {
}
}

let series = Series::create(series, context).await?;

let target_realm = {
let mut target_realm = parent_realm;
for RealmSpecifier { name, path_segment } in new_realms {
Expand All @@ -285,6 +291,39 @@ impl Mutation {
target_realm
};

let series = if let Some(current_realm_path) = current_realm_path {
let old_realm = Realm::load_by_path(current_realm_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 {
// This is already checked and prohibited on the front end side (admin UI),
// but I suppose checking here doesn't hurt .
return Err(invalid_input!("series can only be moved if it's current realm has no other blocks"));
}

if old_realm.number_of_descendants(context).await? < 1 {
// The realm has no children, so it can be removed.
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?;
BlockValue::remove(blocks[0].id(), context).await?;
}

Series::load_by_opencast_id(series.opencast_id, context)
.await?
.ok_or_else(|| invalid_input!("`seriesId` does not refer to a valid series"))?
} else {
Series::create(series, context).await?
};

BlockValue::add_series(
Id::realm(target_realm.key),
0,
Expand Down
11 changes: 9 additions & 2 deletions frontend/src/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -559,11 +559,18 @@ type Mutation {
"Remove a block from a realm."
removeBlock(id: ID!): RemovedBlock!
"""
Atomically mount a series into an (empty) realm.
Atomically mount or move 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.
If the series is already mounted in `current_realm_path`, we need to check
if that realm has any other blocks.
- If it does, moving the series is not allowed.
- Otherwise, the series is moved, but there is some cleanup necessary:
- If the realm has any sub-realms: only the series block is removed.
- Otherwise the whole realm is removed.
"""
mountSeries(series: NewSeries!, parentRealmPath: String!, newRealms: [RealmSpecifier!]!): Realm!
mountSeries(series: NewSeries!, parentRealmPath: String!, currentRealmPath: String, newRealms: [RealmSpecifier!]!): Realm!
}

"Exactly one of `plain` or `block` has to be non-null."
Expand Down

0 comments on commit a06ba4e

Please sign in to comment.