Skip to content

Commit

Permalink
Add createRealmLineage mutation to API
Browse files Browse the repository at this point in the history
This is supposed to make mounting series easier from external scripts.
The existing `mountSeries` API is (a) doing a lot of different things
and (b) requires the caller to distinguish between what realms already
exist and which still need to be created. This new API is idempotent and
more convenient. With this, it would be possible to simplify
`mountSeries`, but we can't simply break that API since the Admin UI
is using it. So we keep it for now. We might add an alternative API in
the future.
  • Loading branch information
LukasKalbertodt committed Jun 12, 2024
1 parent 3ea80fc commit 768d11d
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 2 deletions.
3 changes: 2 additions & 1 deletion backend/src/api/model/realm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ use super::block::{Block, BlockValue, SeriesBlock, VideoBlock};
mod mutations;

pub(crate) use mutations::{
ChildIndex, NewRealm, RemovedRealm, UpdateRealm, UpdatedPermissions, UpdatedRealmName, RealmSpecifier,
ChildIndex, NewRealm, RemovedRealm, UpdateRealm, UpdatedPermissions,
UpdatedRealmName, RealmSpecifier, RealmLineageComponent, CreateRealmLineageOutcome,
};


Expand Down
11 changes: 11 additions & 0 deletions backend/src/api/model/realm/mutations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -394,8 +394,19 @@ pub(crate) struct RealmSpecifier {
pub(crate) path_segment: String,
}

#[derive(Clone, juniper::GraphQLInputObject)]
pub(crate) struct RealmLineageComponent {
pub(crate) name: String,
pub(crate) path_segment: String,
}

#[derive(juniper::GraphQLObject)]
#[graphql(Context = Context)]
pub(crate) struct RemovedRealm {
parent: Option<Realm>,
}

#[derive(juniper::GraphQLObject)]
pub struct CreateRealmLineageOutcome {
pub num_created: i32,
}
45 changes: 44 additions & 1 deletion backend/src/api/mutation.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use juniper::graphql_object;

use crate::auth::AuthContext;
use crate::{api::err::map_db_err, auth::AuthContext};
use super::{
Context,
err::{ApiResult, invalid_input, not_authorized},
Expand All @@ -18,6 +18,8 @@ use super::{
UpdatedRealmName,
UpdateRealm,
RealmSpecifier,
RealmLineageComponent,
CreateRealmLineageOutcome,
},
block::{
BlockValue,
Expand Down Expand Up @@ -193,6 +195,47 @@ impl Mutation {
BlockValue::remove(id, context).await
}

/// Basically `mkdir -p` for realms: makes sure the given realm lineage
/// exists, creating the missing realms. Existing realms are *not* updated.
/// Each realm in the given list is the sub-realm of the previous item in
/// the list. The first item is sub-realm of the root realm.
async fn create_realm_lineage(
realms: Vec<RealmLineageComponent>,
context: &Context,
) -> ApiResult<CreateRealmLineageOutcome> {
if context.auth != AuthContext::TrustedExternal {
return Err(not_authorized!("only trusted external applications can use this mutation"));
}

if realms.len() == 0 {
return Ok(CreateRealmLineageOutcome { num_created: 0 });
}

if context.config.general.reserved_paths().any(|r| realms[0].path_segment == r) {
return Err(invalid_input!(key = "realm.path-is-reserved", "path is reserved and cannot be used"));
}

let mut parent_path = String::new();
let mut num_created = 0;
for realm in realms {
let sql = "\
insert into realms (parent, name, path_segment) \
values ((select id from realms where full_path = $1), $2, $3) \
on conflict do nothing";
let res = context.db.execute(sql, &[&parent_path, &realm.name, &realm.path_segment])
.await;
let affected = map_db_err!(res, {
if constraint == "valid_path" => invalid_input!("path invalid"),
})?;
num_created += affected as i32;

parent_path.push('/');
parent_path.push_str(&realm.path_segment);
}

Ok(CreateRealmLineageOutcome { num_created })
}

/// 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.
Expand Down
16 changes: 16 additions & 0 deletions frontend/src/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ input ChildIndex {
index: Int!
}

type CreateRealmLineageOutcome {
numCreated: Int!
}

type SyncedSeriesData {
description: String
}
Expand Down Expand Up @@ -487,6 +491,13 @@ type Mutation {
updateVideoBlock(id: ID!, set: UpdateVideoBlock!): Block!
"Remove a block from a realm."
removeBlock(id: ID!): RemovedBlock!
"""
Basically `mkdir -p` for realms: makes sure the given realm lineage
exists, creating the missing realms. Existing realms are *not* updated.
Each realm in the given list is the sub-realm of the previous item in
the list. The first item is sub-realm of the root realm.
"""
createRealmLineage(realms: [RealmLineageComponent!]!): CreateRealmLineageOutcome!
"""
Atomically mount a series into an (empty) realm.
Creates all the necessary realms on the path to the target
Expand Down Expand Up @@ -637,6 +648,11 @@ enum ItemType {
REALM
}

input RealmLineageComponent {
name: String!
pathSegment: String!
}

enum EventSortColumn {
TITLE
CREATED
Expand Down

0 comments on commit 768d11d

Please sign in to comment.