feat: inspect user data
This commit is contained in:
@@ -2,7 +2,7 @@ use std::sync::LazyLock;
|
|||||||
|
|
||||||
use async_compression::futures::bufread::BrotliDecoder;
|
use async_compression::futures::bufread::BrotliDecoder;
|
||||||
use capnp::message::ReaderOptions;
|
use capnp::message::ReaderOptions;
|
||||||
use futures::AsyncReadExt;
|
use futures::{AsyncReadExt, TryStreamExt};
|
||||||
use opendal::ErrorKind;
|
use opendal::ErrorKind;
|
||||||
use snafu::{OptionExt, Snafu};
|
use snafu::{OptionExt, Snafu};
|
||||||
use twilight_model::{
|
use twilight_model::{
|
||||||
@@ -175,4 +175,48 @@ pub async fn handle(state: State, interaction: Interaction) {
|
|||||||
.flags(MessageFlags::EPHEMERAL)
|
.flags(MessageFlags::EPHEMERAL)
|
||||||
.await
|
.await
|
||||||
.expect("TODO");
|
.expect("TODO");
|
||||||
|
|
||||||
|
let mut user_id_stream = state.user_data_manager.list().await.expect("TODO");
|
||||||
|
|
||||||
|
while let Some(user_id) = user_id_stream.try_next().await.expect("TODO") {
|
||||||
|
let (consent, notification_script) = state
|
||||||
|
.user_data_manager
|
||||||
|
.with(user_id, |user_data| {
|
||||||
|
let consent = user_data.get_voice_recording_consent().unwrap();
|
||||||
|
let notification_script = user_data.has_notification_script().then_some(
|
||||||
|
user_data
|
||||||
|
.get_notification_script()
|
||||||
|
.expect("TODO")
|
||||||
|
.to_string()
|
||||||
|
.expect("TODO"),
|
||||||
|
);
|
||||||
|
|
||||||
|
(consent, notification_script)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.expect("TODO");
|
||||||
|
|
||||||
|
let user_mention = format!("<@{user_id}>");
|
||||||
|
|
||||||
|
state
|
||||||
|
.discord_client
|
||||||
|
.interaction(state.discord_application_id)
|
||||||
|
.create_followup(&interaction.token)
|
||||||
|
.embeds(&[EmbedBuilder::new()
|
||||||
|
.title(user_mention)
|
||||||
|
.field(EmbedFieldBuilder::new("Consent", format!("{consent:?}")).build())
|
||||||
|
.field(
|
||||||
|
EmbedFieldBuilder::new(
|
||||||
|
"Notification Script",
|
||||||
|
format!("{notification_script:?}"),
|
||||||
|
)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.validate()
|
||||||
|
.unwrap()
|
||||||
|
.build()])
|
||||||
|
.flags(MessageFlags::EPHEMERAL)
|
||||||
|
.await
|
||||||
|
.expect("TODO");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use async_compression::futures::{bufread::BrotliDecoder, write::BrotliEncoder};
|
use async_compression::futures::{bufread::BrotliDecoder, write::BrotliEncoder};
|
||||||
use capnp::message::{TypedBuilder, TypedReader};
|
use capnp::message::{TypedBuilder, TypedReader};
|
||||||
use futures::{AsyncReadExt, AsyncWriteExt};
|
use futures::{AsyncReadExt, AsyncWriteExt, TryStream, TryStreamExt};
|
||||||
use opendal::Operator;
|
use opendal::Operator;
|
||||||
use snafu::{ResultExt, Snafu};
|
use snafu::{OptionExt as _, ResultExt as _, Snafu, ensure};
|
||||||
use twilight_model::id::{Id, marker::UserMarker};
|
use twilight_model::id::{Id, marker::UserMarker};
|
||||||
|
|
||||||
use crate::{OperatorExt, option_ext::OptionExt, user_capnp};
|
use crate::{OperatorExt, option_ext::OptionExt as _, user_capnp};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct UserDataManager {
|
pub struct UserDataManager {
|
||||||
@@ -16,10 +18,79 @@ impl UserDataManager {
|
|||||||
pub fn new(operator: Operator) -> Self {
|
pub fn new(operator: Operator) -> Self {
|
||||||
Self { operator }
|
Self { operator }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn path(id: Id<UserMarker>) -> String {
|
fn path(id: Id<UserMarker>) -> String {
|
||||||
format!("{id}/data.bin.brotli")
|
format!("{id}/data.bin.brotli")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Snafu)]
|
||||||
|
#[snafu(module)]
|
||||||
|
pub enum ParsePathError {
|
||||||
|
/// paths must have a / in them because that's how user data is stored, but this one doesn't have one
|
||||||
|
MissingSlashError,
|
||||||
|
|
||||||
|
/// if this isn't a directory, then the file must be "data.bin.brotli" but it was actually {actual:?}
|
||||||
|
WrongFileError { actual: String },
|
||||||
|
|
||||||
|
/// couldn't parse the directory as a user ID
|
||||||
|
ParseUserIdError {
|
||||||
|
source: <Id<UserMarker> as FromStr>::Err,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse(path: &str) -> Result<Id<UserMarker>, ParsePathError> {
|
||||||
|
let (directory, file) = path
|
||||||
|
.rsplit_once("/")
|
||||||
|
.context(parse_path_error::MissingSlashSnafu)?;
|
||||||
|
|
||||||
|
ensure!(
|
||||||
|
file == "" || file == "data.bin.brotli",
|
||||||
|
parse_path_error::WrongFileSnafu {
|
||||||
|
actual: file.to_owned()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let user_id = directory
|
||||||
|
.parse()
|
||||||
|
.context(parse_path_error::ParseUserIdSnafu)?;
|
||||||
|
|
||||||
|
Ok(user_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Snafu)]
|
||||||
|
#[snafu(module)]
|
||||||
|
pub enum ListError {
|
||||||
|
/// error creating a lister through the storage operator
|
||||||
|
CreateListerError { source: opendal::Error },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Snafu)]
|
||||||
|
#[snafu(module)]
|
||||||
|
pub enum EntryError {
|
||||||
|
/// failed to get an entry from the storage operator's lister
|
||||||
|
ReceiveEntryError { source: opendal::Error },
|
||||||
|
|
||||||
|
/// failed to parse the entry as an acceptable path
|
||||||
|
ParsePathError { source: ParsePathError },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UserDataManager {
|
||||||
|
pub async fn list(
|
||||||
|
&self,
|
||||||
|
) -> Result<impl TryStream<Ok = Id<UserMarker>, Error = EntryError> + Unpin, ListError> {
|
||||||
|
let lister = self
|
||||||
|
.operator
|
||||||
|
.lister("")
|
||||||
|
.await
|
||||||
|
.context(list_error::CreateListerSnafu)?;
|
||||||
|
|
||||||
|
Ok(lister
|
||||||
|
.map_err(|error| EntryError::ReceiveEntryError { source: error })
|
||||||
|
.and_then(|entry| {
|
||||||
|
std::future::ready(parse(entry.path()).context(entry_error::ParsePathSnafu))
|
||||||
|
}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Snafu)]
|
#[derive(Debug, Snafu)]
|
||||||
@@ -43,7 +114,7 @@ impl UserDataManager {
|
|||||||
) -> Result<R, WithError> {
|
) -> Result<R, WithError> {
|
||||||
let compressed_buffer = self
|
let compressed_buffer = self
|
||||||
.operator
|
.operator
|
||||||
.async_reader_if_exists(&UserDataManager::path(id))
|
.async_reader_if_exists(&path(id))
|
||||||
.await
|
.await
|
||||||
.context(with_error::ReadSnafu)?;
|
.context(with_error::ReadSnafu)?;
|
||||||
|
|
||||||
@@ -108,7 +179,7 @@ impl UserDataManager {
|
|||||||
id: Id<UserMarker>,
|
id: Id<UserMarker>,
|
||||||
f: impl FnOnce(user_capnp::user::Builder<'_>) -> R,
|
f: impl FnOnce(user_capnp::user::Builder<'_>) -> R,
|
||||||
) -> Result<R, UpdateError> {
|
) -> Result<R, UpdateError> {
|
||||||
let path = UserDataManager::path(id);
|
let path = path(id);
|
||||||
let compressed_buffer = self
|
let compressed_buffer = self
|
||||||
.operator
|
.operator
|
||||||
.async_reader_if_exists(&path)
|
.async_reader_if_exists(&path)
|
||||||
|
|||||||
Reference in New Issue
Block a user