forked from github-starred/komodo
1.5.1 move routes to /user
This commit is contained in:
20
Cargo.lock
generated
20
Cargo.lock
generated
@@ -32,7 +32,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alerter"
|
||||
version = "1.5.0"
|
||||
version = "1.5.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.5",
|
||||
@@ -1948,7 +1948,7 @@ checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
||||
|
||||
[[package]]
|
||||
name = "logger"
|
||||
version = "1.5.0"
|
||||
version = "1.5.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"monitor_client",
|
||||
@@ -2017,7 +2017,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "migrator"
|
||||
version = "1.5.0"
|
||||
version = "1.5.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@@ -2140,7 +2140,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "monitor_cli"
|
||||
version = "1.5.0"
|
||||
version = "1.5.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -2159,7 +2159,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "monitor_client"
|
||||
version = "1.5.0"
|
||||
version = "1.5.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async_timing_util",
|
||||
@@ -2191,7 +2191,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "monitor_core"
|
||||
version = "1.5.0"
|
||||
version = "1.5.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async_timing_util",
|
||||
@@ -2237,7 +2237,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "monitor_periphery"
|
||||
version = "1.5.0"
|
||||
version = "1.5.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async_timing_util",
|
||||
@@ -2569,7 +2569,7 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
|
||||
[[package]]
|
||||
name = "periphery_client"
|
||||
version = "1.5.0"
|
||||
version = "1.5.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"monitor_client",
|
||||
@@ -3547,7 +3547,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tests"
|
||||
version = "1.5.0"
|
||||
version = "1.5.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"dotenv",
|
||||
@@ -4090,7 +4090,7 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "update_logger"
|
||||
version = "1.5.0"
|
||||
version = "1.5.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"logger",
|
||||
|
||||
@@ -3,7 +3,7 @@ resolver = "2"
|
||||
members = ["bin/*", "lib/*", "client/core/rs", "client/periphery/rs"]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.5.0"
|
||||
version = "1.5.1"
|
||||
edition = "2021"
|
||||
authors = ["mbecker20 <becker.maxh@gmail.com>"]
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
@@ -2,3 +2,4 @@ pub mod auth;
|
||||
pub mod execute;
|
||||
pub mod read;
|
||||
pub mod write;
|
||||
pub mod user;
|
||||
217
bin/core/src/api/user.rs
Normal file
217
bin/core/src/api/user.rs
Normal file
@@ -0,0 +1,217 @@
|
||||
use std::{collections::VecDeque, time::Instant};
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use axum::{middleware, routing::post, Extension, Json, Router};
|
||||
use axum_extra::{headers::ContentType, TypedHeader};
|
||||
use mongo_indexed::doc;
|
||||
use monitor_client::{
|
||||
api::user::{
|
||||
CreateApiKey, CreateApiKeyResponse, DeleteApiKey,
|
||||
DeleteApiKeyResponse, PushRecentlyViewed,
|
||||
PushRecentlyViewedResponse, SetLastSeenUpdate,
|
||||
SetLastSeenUpdateResponse,
|
||||
},
|
||||
entities::{
|
||||
api_key::ApiKey, monitor_timestamp, update::ResourceTarget,
|
||||
user::User,
|
||||
},
|
||||
};
|
||||
use mungos::{by_id::update_one_by_id, mongodb::bson::to_bson};
|
||||
use resolver_api::{derive::Resolver, Resolve, Resolver};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
auth::{auth_request, random_string},
|
||||
helpers::query::get_user,
|
||||
state::{db_client, State},
|
||||
};
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Resolver)]
|
||||
#[resolver_target(State)]
|
||||
#[resolver_args(User)]
|
||||
#[serde(tag = "type", content = "params")]
|
||||
enum UserRequest {
|
||||
PushRecentlyViewed(PushRecentlyViewed),
|
||||
SetLastSeenUpdate(SetLastSeenUpdate),
|
||||
CreateApiKey(CreateApiKey),
|
||||
DeleteApiKey(DeleteApiKey),
|
||||
}
|
||||
|
||||
pub fn router() -> Router {
|
||||
Router::new()
|
||||
.route("/", post(handler))
|
||||
.layer(middleware::from_fn(auth_request))
|
||||
}
|
||||
|
||||
#[instrument(name = "UserHandler", level = "debug", skip(user))]
|
||||
async fn handler(
|
||||
Extension(user): Extension<User>,
|
||||
Json(request): Json<UserRequest>,
|
||||
) -> serror::Result<(TypedHeader<ContentType>, String)> {
|
||||
let timer = Instant::now();
|
||||
let req_id = Uuid::new_v4();
|
||||
debug!(
|
||||
"/user request {req_id} | user: {} ({})",
|
||||
user.username, user.id
|
||||
);
|
||||
let res =
|
||||
State
|
||||
.resolve_request(request, user)
|
||||
.await
|
||||
.map_err(|e| match e {
|
||||
resolver_api::Error::Serialization(e) => {
|
||||
anyhow!("{e:?}").context("response serialization error")
|
||||
}
|
||||
resolver_api::Error::Inner(e) => e,
|
||||
});
|
||||
if let Err(e) = &res {
|
||||
warn!("/user request {req_id} error: {e:#}");
|
||||
}
|
||||
let elapsed = timer.elapsed();
|
||||
debug!("/user request {req_id} | resolve time: {elapsed:?}");
|
||||
Ok((TypedHeader(ContentType::json()), res?))
|
||||
}
|
||||
|
||||
const RECENTLY_VIEWED_MAX: usize = 10;
|
||||
|
||||
impl Resolve<PushRecentlyViewed, User> for State {
|
||||
#[instrument(name = "PushRecentlyViewed", skip(self, user))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
PushRecentlyViewed { resource }: PushRecentlyViewed,
|
||||
user: User,
|
||||
) -> anyhow::Result<PushRecentlyViewedResponse> {
|
||||
let user = get_user(&user.id).await?;
|
||||
|
||||
let (recents, id, field) = match resource {
|
||||
ResourceTarget::Server(id) => {
|
||||
(user.recent_servers, id, "recent_servers")
|
||||
}
|
||||
ResourceTarget::Deployment(id) => {
|
||||
(user.recent_deployments, id, "recent_deployments")
|
||||
}
|
||||
ResourceTarget::Build(id) => {
|
||||
(user.recent_builds, id, "recent_builds")
|
||||
}
|
||||
ResourceTarget::Repo(id) => {
|
||||
(user.recent_repos, id, "recent_repos")
|
||||
}
|
||||
ResourceTarget::Procedure(id) => {
|
||||
(user.recent_procedures, id, "recent_procedures")
|
||||
}
|
||||
_ => return Ok(PushRecentlyViewedResponse {}),
|
||||
};
|
||||
|
||||
let mut recents = recents
|
||||
.into_iter()
|
||||
.filter(|_id| !id.eq(_id))
|
||||
.take(RECENTLY_VIEWED_MAX - 1)
|
||||
.collect::<VecDeque<_>>();
|
||||
recents.push_front(id);
|
||||
let update = doc! { field: to_bson(&recents)? };
|
||||
|
||||
update_one_by_id(
|
||||
&db_client().await.users,
|
||||
&user.id,
|
||||
mungos::update::Update::Set(update),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.with_context(|| format!("failed to update {field}"))?;
|
||||
|
||||
Ok(PushRecentlyViewedResponse {})
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<SetLastSeenUpdate, User> for State {
|
||||
#[instrument(name = "SetLastSeenUpdate", skip(self, user))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
SetLastSeenUpdate {}: SetLastSeenUpdate,
|
||||
user: User,
|
||||
) -> anyhow::Result<SetLastSeenUpdateResponse> {
|
||||
update_one_by_id(
|
||||
&db_client().await.users,
|
||||
&user.id,
|
||||
mungos::update::Update::Set(doc! {
|
||||
"last_update_view": monitor_timestamp()
|
||||
}),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.context("failed to update user last_update_view")?;
|
||||
Ok(SetLastSeenUpdateResponse {})
|
||||
}
|
||||
}
|
||||
|
||||
const SECRET_LENGTH: usize = 40;
|
||||
const BCRYPT_COST: u32 = 10;
|
||||
|
||||
impl Resolve<CreateApiKey, User> for State {
|
||||
#[instrument(
|
||||
name = "CreateApiKey",
|
||||
level = "debug",
|
||||
skip(self, user)
|
||||
)]
|
||||
async fn resolve(
|
||||
&self,
|
||||
CreateApiKey { name, expires }: CreateApiKey,
|
||||
user: User,
|
||||
) -> anyhow::Result<CreateApiKeyResponse> {
|
||||
let user = get_user(&user.id).await?;
|
||||
|
||||
let key = format!("K-{}", random_string(SECRET_LENGTH));
|
||||
let secret = format!("S-{}", random_string(SECRET_LENGTH));
|
||||
let secret_hash = bcrypt::hash(&secret, BCRYPT_COST)
|
||||
.context("failed at hashing secret string")?;
|
||||
|
||||
let api_key = ApiKey {
|
||||
name,
|
||||
key: key.clone(),
|
||||
secret: secret_hash,
|
||||
user_id: user.id.clone(),
|
||||
created_at: monitor_timestamp(),
|
||||
expires,
|
||||
};
|
||||
db_client()
|
||||
.await
|
||||
.api_keys
|
||||
.insert_one(api_key, None)
|
||||
.await
|
||||
.context("failed to create api key on db")?;
|
||||
Ok(CreateApiKeyResponse { key, secret })
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<DeleteApiKey, User> for State {
|
||||
#[instrument(
|
||||
name = "DeleteApiKey",
|
||||
level = "debug",
|
||||
skip(self, user)
|
||||
)]
|
||||
async fn resolve(
|
||||
&self,
|
||||
DeleteApiKey { key }: DeleteApiKey,
|
||||
user: User,
|
||||
) -> anyhow::Result<DeleteApiKeyResponse> {
|
||||
let client = db_client().await;
|
||||
let key = client
|
||||
.api_keys
|
||||
.find_one(doc! { "key": &key }, None)
|
||||
.await
|
||||
.context("failed at db query")?
|
||||
.context("no api key with key found")?;
|
||||
if user.id != key.user_id {
|
||||
return Err(anyhow!("api key does not belong to user"));
|
||||
}
|
||||
client
|
||||
.api_keys
|
||||
.delete_one(doc! { "key": key.key }, None)
|
||||
.await
|
||||
.context("failed to delete api key from db")?;
|
||||
Ok(DeleteApiKeyResponse {})
|
||||
}
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
use anyhow::{anyhow, Context};
|
||||
use monitor_client::{
|
||||
api::write::*,
|
||||
entities::{
|
||||
api_key::ApiKey,
|
||||
monitor_timestamp,
|
||||
user::{User, UserConfig},
|
||||
},
|
||||
};
|
||||
use mungos::{by_id::find_one_by_id, mongodb::bson::doc};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
auth::random_string,
|
||||
helpers::query::get_user,
|
||||
state::{db_client, State},
|
||||
};
|
||||
|
||||
const SECRET_LENGTH: usize = 40;
|
||||
const BCRYPT_COST: u32 = 10;
|
||||
|
||||
impl Resolve<CreateApiKey, User> for State {
|
||||
#[instrument(
|
||||
name = "CreateApiKey",
|
||||
level = "debug",
|
||||
skip(self, user)
|
||||
)]
|
||||
async fn resolve(
|
||||
&self,
|
||||
CreateApiKey { name, expires }: CreateApiKey,
|
||||
user: User,
|
||||
) -> anyhow::Result<CreateApiKeyResponse> {
|
||||
let user = get_user(&user.id).await?;
|
||||
|
||||
let key = format!("K-{}", random_string(SECRET_LENGTH));
|
||||
let secret = format!("S-{}", random_string(SECRET_LENGTH));
|
||||
let secret_hash = bcrypt::hash(&secret, BCRYPT_COST)
|
||||
.context("failed at hashing secret string")?;
|
||||
|
||||
let api_key = ApiKey {
|
||||
name,
|
||||
key: key.clone(),
|
||||
secret: secret_hash,
|
||||
user_id: user.id.clone(),
|
||||
created_at: monitor_timestamp(),
|
||||
expires,
|
||||
};
|
||||
db_client()
|
||||
.await
|
||||
.api_keys
|
||||
.insert_one(api_key, None)
|
||||
.await
|
||||
.context("failed to create api key on db")?;
|
||||
Ok(CreateApiKeyResponse { key, secret })
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<DeleteApiKey, User> for State {
|
||||
#[instrument(
|
||||
name = "DeleteApiKey",
|
||||
level = "debug",
|
||||
skip(self, user)
|
||||
)]
|
||||
async fn resolve(
|
||||
&self,
|
||||
DeleteApiKey { key }: DeleteApiKey,
|
||||
user: User,
|
||||
) -> anyhow::Result<DeleteApiKeyResponse> {
|
||||
let client = db_client().await;
|
||||
let key = client
|
||||
.api_keys
|
||||
.find_one(doc! { "key": &key }, None)
|
||||
.await
|
||||
.context("failed at db query")?
|
||||
.context("no api key with key found")?;
|
||||
if user.id != key.user_id {
|
||||
return Err(anyhow!("api key does not belong to user"));
|
||||
}
|
||||
client
|
||||
.api_keys
|
||||
.delete_one(doc! { "key": key.key }, None)
|
||||
.await
|
||||
.context("failed to delete api key from db")?;
|
||||
Ok(DeleteApiKeyResponse {})
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<CreateApiKeyForServiceUser, User> for State {
|
||||
#[instrument(name = "CreateApiKeyForServiceUser", skip(self, user))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
CreateApiKeyForServiceUser {
|
||||
user_id,
|
||||
name,
|
||||
expires,
|
||||
}: CreateApiKeyForServiceUser,
|
||||
user: User,
|
||||
) -> anyhow::Result<CreateApiKeyForServiceUserResponse> {
|
||||
if !user.admin {
|
||||
return Err(anyhow!("user not admin"));
|
||||
}
|
||||
let service_user =
|
||||
find_one_by_id(&db_client().await.users, &user_id)
|
||||
.await
|
||||
.context("failed to query db for user")?
|
||||
.context("no user found with id")?;
|
||||
let UserConfig::Service { .. } = &service_user.config else {
|
||||
return Err(anyhow!("user is not service user"));
|
||||
};
|
||||
self
|
||||
.resolve(CreateApiKey { name, expires }, service_user)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<DeleteApiKeyForServiceUser, User> for State {
|
||||
#[instrument(name = "DeleteApiKeyForServiceUser", skip(self, user))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
DeleteApiKeyForServiceUser { key }: DeleteApiKeyForServiceUser,
|
||||
user: User,
|
||||
) -> anyhow::Result<DeleteApiKeyForServiceUserResponse> {
|
||||
if !user.admin {
|
||||
return Err(anyhow!("user not admin"));
|
||||
}
|
||||
let db = db_client().await;
|
||||
let api_key = db
|
||||
.api_keys
|
||||
.find_one(doc! { "key": &key }, None)
|
||||
.await
|
||||
.context("failed to query db for api key")?
|
||||
.context("did not find matching api key")?;
|
||||
let service_user =
|
||||
find_one_by_id(&db_client().await.users, &api_key.user_id)
|
||||
.await
|
||||
.context("failed to query db for user")?
|
||||
.context("no user found with id")?;
|
||||
let UserConfig::Service { .. } = &service_user.config else {
|
||||
return Err(anyhow!("user is not service user"));
|
||||
};
|
||||
db.api_keys
|
||||
.delete_one(doc! { "key": key }, None)
|
||||
.await
|
||||
.context("failed to delete api key on db")?;
|
||||
Ok(DeleteApiKeyForServiceUserResponse {})
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,6 @@ use uuid::Uuid;
|
||||
use crate::{auth::auth_request, state::State};
|
||||
|
||||
mod alerter;
|
||||
mod api_key;
|
||||
mod build;
|
||||
mod builder;
|
||||
mod deployment;
|
||||
@@ -23,8 +22,8 @@ mod procedure;
|
||||
mod repo;
|
||||
mod server;
|
||||
mod server_template;
|
||||
mod service_user;
|
||||
mod tag;
|
||||
mod user;
|
||||
mod user_group;
|
||||
mod variable;
|
||||
|
||||
@@ -34,17 +33,11 @@ mod variable;
|
||||
#[resolver_args(User)]
|
||||
#[serde(tag = "type", content = "params")]
|
||||
enum WriteRequest {
|
||||
// ==== API KEY ====
|
||||
CreateApiKey(CreateApiKey),
|
||||
DeleteApiKey(DeleteApiKey),
|
||||
CreateApiKeyForServiceUser(CreateApiKeyForServiceUser),
|
||||
DeleteApiKeyForServiceUser(DeleteApiKeyForServiceUser),
|
||||
|
||||
// ==== USER ====
|
||||
PushRecentlyViewed(PushRecentlyViewed),
|
||||
SetLastSeenUpdate(SetLastSeenUpdate),
|
||||
// ==== SERVICE USER ====
|
||||
CreateServiceUser(CreateServiceUser),
|
||||
UpdateServiceUserDescription(UpdateServiceUserDescription),
|
||||
CreateApiKeyForServiceUser(CreateApiKeyForServiceUser),
|
||||
DeleteApiKeyForServiceUser(DeleteApiKeyForServiceUser),
|
||||
|
||||
// ==== USER GROUP ====
|
||||
CreateUserGroup(CreateUserGroup),
|
||||
|
||||
@@ -1,101 +1,29 @@
|
||||
use std::{collections::VecDeque, str::FromStr};
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use monitor_client::{
|
||||
api::write::{
|
||||
CreateServiceUser, CreateServiceUserResponse, PushRecentlyViewed,
|
||||
PushRecentlyViewedResponse, SetLastSeenUpdate,
|
||||
SetLastSeenUpdateResponse, UpdateServiceUserDescription,
|
||||
UpdateServiceUserDescriptionResponse,
|
||||
api::{
|
||||
user::CreateApiKey,
|
||||
write::{
|
||||
CreateApiKeyForServiceUser, CreateApiKeyForServiceUserResponse,
|
||||
CreateServiceUser, CreateServiceUserResponse,
|
||||
DeleteApiKeyForServiceUser, DeleteApiKeyForServiceUserResponse,
|
||||
UpdateServiceUserDescription,
|
||||
UpdateServiceUserDescriptionResponse,
|
||||
},
|
||||
},
|
||||
entities::{
|
||||
monitor_timestamp,
|
||||
update::ResourceTarget,
|
||||
user::{User, UserConfig},
|
||||
},
|
||||
};
|
||||
use mungos::{
|
||||
by_id::update_one_by_id,
|
||||
mongodb::bson::{doc, oid::ObjectId, to_bson},
|
||||
by_id::find_one_by_id,
|
||||
mongodb::bson::{doc, oid::ObjectId},
|
||||
};
|
||||
use resolver_api::Resolve;
|
||||
|
||||
use crate::{
|
||||
helpers::query::get_user,
|
||||
state::{db_client, State},
|
||||
};
|
||||
|
||||
const RECENTLY_VIEWED_MAX: usize = 10;
|
||||
|
||||
impl Resolve<PushRecentlyViewed, User> for State {
|
||||
#[instrument(name = "PushRecentlyViewed", skip(self, user))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
PushRecentlyViewed { resource }: PushRecentlyViewed,
|
||||
user: User,
|
||||
) -> anyhow::Result<PushRecentlyViewedResponse> {
|
||||
let user = get_user(&user.id).await?;
|
||||
|
||||
let (recents, id, field) = match resource {
|
||||
ResourceTarget::Server(id) => {
|
||||
(user.recent_servers, id, "recent_servers")
|
||||
}
|
||||
ResourceTarget::Deployment(id) => {
|
||||
(user.recent_deployments, id, "recent_deployments")
|
||||
}
|
||||
ResourceTarget::Build(id) => {
|
||||
(user.recent_builds, id, "recent_builds")
|
||||
}
|
||||
ResourceTarget::Repo(id) => {
|
||||
(user.recent_repos, id, "recent_repos")
|
||||
}
|
||||
ResourceTarget::Procedure(id) => {
|
||||
(user.recent_procedures, id, "recent_procedures")
|
||||
}
|
||||
_ => return Ok(PushRecentlyViewedResponse {}),
|
||||
};
|
||||
|
||||
let mut recents = recents
|
||||
.into_iter()
|
||||
.filter(|_id| !id.eq(_id))
|
||||
.take(RECENTLY_VIEWED_MAX - 1)
|
||||
.collect::<VecDeque<_>>();
|
||||
recents.push_front(id);
|
||||
let update = doc! { field: to_bson(&recents)? };
|
||||
|
||||
update_one_by_id(
|
||||
&db_client().await.users,
|
||||
&user.id,
|
||||
mungos::update::Update::Set(update),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.with_context(|| format!("failed to update {field}"))?;
|
||||
|
||||
Ok(PushRecentlyViewedResponse {})
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<SetLastSeenUpdate, User> for State {
|
||||
#[instrument(name = "SetLastSeenUpdate", skip(self, user))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
SetLastSeenUpdate {}: SetLastSeenUpdate,
|
||||
user: User,
|
||||
) -> anyhow::Result<SetLastSeenUpdateResponse> {
|
||||
update_one_by_id(
|
||||
&db_client().await.users,
|
||||
&user.id,
|
||||
mungos::update::Update::Set(doc! {
|
||||
"last_update_view": monitor_timestamp()
|
||||
}),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.context("failed to update user last_update_view")?;
|
||||
Ok(SetLastSeenUpdateResponse {})
|
||||
}
|
||||
}
|
||||
use crate::state::{db_client, State};
|
||||
|
||||
impl Resolve<CreateServiceUser, User> for State {
|
||||
#[instrument(name = "CreateServiceUser", skip(self, user))]
|
||||
@@ -185,3 +113,64 @@ impl Resolve<UpdateServiceUserDescription, User> for State {
|
||||
.context("user with username not found")
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<CreateApiKeyForServiceUser, User> for State {
|
||||
#[instrument(name = "CreateApiKeyForServiceUser", skip(self, user))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
CreateApiKeyForServiceUser {
|
||||
user_id,
|
||||
name,
|
||||
expires,
|
||||
}: CreateApiKeyForServiceUser,
|
||||
user: User,
|
||||
) -> anyhow::Result<CreateApiKeyForServiceUserResponse> {
|
||||
if !user.admin {
|
||||
return Err(anyhow!("user not admin"));
|
||||
}
|
||||
let service_user =
|
||||
find_one_by_id(&db_client().await.users, &user_id)
|
||||
.await
|
||||
.context("failed to query db for user")?
|
||||
.context("no user found with id")?;
|
||||
let UserConfig::Service { .. } = &service_user.config else {
|
||||
return Err(anyhow!("user is not service user"));
|
||||
};
|
||||
self
|
||||
.resolve(CreateApiKey { name, expires }, service_user)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve<DeleteApiKeyForServiceUser, User> for State {
|
||||
#[instrument(name = "DeleteApiKeyForServiceUser", skip(self, user))]
|
||||
async fn resolve(
|
||||
&self,
|
||||
DeleteApiKeyForServiceUser { key }: DeleteApiKeyForServiceUser,
|
||||
user: User,
|
||||
) -> anyhow::Result<DeleteApiKeyForServiceUserResponse> {
|
||||
if !user.admin {
|
||||
return Err(anyhow!("user not admin"));
|
||||
}
|
||||
let db = db_client().await;
|
||||
let api_key = db
|
||||
.api_keys
|
||||
.find_one(doc! { "key": &key }, None)
|
||||
.await
|
||||
.context("failed to query db for api key")?
|
||||
.context("did not find matching api key")?;
|
||||
let service_user =
|
||||
find_one_by_id(&db_client().await.users, &api_key.user_id)
|
||||
.await
|
||||
.context("failed to query db for user")?
|
||||
.context("no user found with id")?;
|
||||
let UserConfig::Service { .. } = &service_user.config else {
|
||||
return Err(anyhow!("user is not service user"));
|
||||
};
|
||||
db.api_keys
|
||||
.delete_one(doc! { "key": key }, None)
|
||||
.await
|
||||
.context("failed to delete api key on db")?;
|
||||
Ok(DeleteApiKeyForServiceUserResponse {})
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,7 @@ async fn app() -> anyhow::Result<()> {
|
||||
|
||||
let app = Router::new()
|
||||
.nest("/auth", api::auth::router())
|
||||
.nest("/user", api::user::router())
|
||||
.nest("/read", api::read::router())
|
||||
.nest("/write", api::write::router())
|
||||
.nest("/execute", api::execute::router())
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
//!
|
||||
//! All calls share some common HTTP params:
|
||||
//! - Method: `POST`
|
||||
//! - Path: `/auth`, `/read`, `/write`, `/execute`
|
||||
//! - Path: `/auth`, `/user`, `/read`, `/write`, `/execute`
|
||||
//! - Headers:
|
||||
//! - Content-Type: `application/json`
|
||||
//! - Authorication: `your_jwt`
|
||||
@@ -30,7 +30,8 @@
|
||||
//!
|
||||
//! ## Modules
|
||||
//!
|
||||
//! - [auth]: Requests relating to loggins in / obtaining authentication tokens.
|
||||
//! - [auth]: Requests relating to logging in / obtaining authentication tokens.
|
||||
//! - [user]: User self-management actions (manage api keys, etc.)
|
||||
//! - [read]: Read only requests which retrieve data from Monitor.
|
||||
//! - [execute]: Run actions on monitor resources, eg [execute::RunBuild].
|
||||
//! - [mod@write]: Requests which alter data, like create / update / delete resources.
|
||||
@@ -52,4 +53,5 @@
|
||||
pub mod auth;
|
||||
pub mod execute;
|
||||
pub mod read;
|
||||
pub mod user;
|
||||
pub mod write;
|
||||
|
||||
97
client/core/rs/src/api/user.rs
Normal file
97
client/core/rs/src/api/user.rs
Normal file
@@ -0,0 +1,97 @@
|
||||
use derive_empty_traits::EmptyTraits;
|
||||
use resolver_api::{derive::Request, HasResponse};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::entities::{update::ResourceTarget, NoData, I64};
|
||||
|
||||
pub trait MonitorUserRequest: HasResponse {}
|
||||
|
||||
//
|
||||
|
||||
/// Push a resource to the front of the users 10 most recently viewed resources.
|
||||
/// Response: [NoData].
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(MonitorUserRequest)]
|
||||
#[response(PushRecentlyViewedResponse)]
|
||||
pub struct PushRecentlyViewed {
|
||||
/// The target to push.
|
||||
pub resource: ResourceTarget,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
pub type PushRecentlyViewedResponse = NoData;
|
||||
|
||||
//
|
||||
|
||||
/// Set the time the user last opened the UI updates.
|
||||
/// Used for unseen notification dot.
|
||||
/// Response: [NoData]
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(MonitorUserRequest)]
|
||||
#[response(SetLastSeenUpdateResponse)]
|
||||
pub struct SetLastSeenUpdate {}
|
||||
|
||||
#[typeshare]
|
||||
pub type SetLastSeenUpdateResponse = NoData;
|
||||
|
||||
//
|
||||
|
||||
/// Create an api key for the calling user.
|
||||
/// Response: [CreateApiKeyResponse].
|
||||
///
|
||||
/// Note. After the response is served, there will be no way
|
||||
/// to get the secret later.
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(MonitorUserRequest)]
|
||||
#[response(CreateApiKeyResponse)]
|
||||
pub struct CreateApiKey {
|
||||
/// The name for the api key.
|
||||
pub name: String,
|
||||
|
||||
/// A unix timestamp in millseconds specifying api key expire time.
|
||||
/// Default is 0, which means no expiry.
|
||||
#[serde(default)]
|
||||
pub expires: I64,
|
||||
}
|
||||
|
||||
/// Response for [CreateApiKey].
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct CreateApiKeyResponse {
|
||||
/// X-API-KEY
|
||||
pub key: String,
|
||||
|
||||
/// X-API-SECRET
|
||||
///
|
||||
/// Note.
|
||||
/// There is no way to get the secret again after it is distributed in this message
|
||||
pub secret: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
/// Delete an api key for the calling user.
|
||||
/// Response: [NoData]
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(MonitorUserRequest)]
|
||||
#[response(DeleteApiKeyResponse)]
|
||||
pub struct DeleteApiKey {
|
||||
/// The key which the user intends to delete.
|
||||
pub key: String,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
pub type DeleteApiKeyResponse = NoData;
|
||||
@@ -3,67 +3,15 @@ use resolver_api::derive::Request;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::entities::{NoData, I64};
|
||||
use crate::{
|
||||
api::user::CreateApiKeyResponse,
|
||||
entities::{NoData, I64},
|
||||
};
|
||||
|
||||
use super::MonitorWriteRequest;
|
||||
|
||||
//
|
||||
|
||||
/// Create an api key for the calling user.
|
||||
/// Response: [CreateApiKeyResponse].
|
||||
///
|
||||
/// Note. After the response is served, there will be no way
|
||||
/// to get the secret later.
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(MonitorWriteRequest)]
|
||||
#[response(CreateApiKeyResponse)]
|
||||
pub struct CreateApiKey {
|
||||
/// The name for the api key.
|
||||
pub name: String,
|
||||
|
||||
/// A unix timestamp in millseconds specifying api key expire time.
|
||||
/// Default is 0, which means no expiry.
|
||||
#[serde(default)]
|
||||
pub expires: I64,
|
||||
}
|
||||
|
||||
/// Response for [CreateApiKey].
|
||||
#[typeshare]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct CreateApiKeyResponse {
|
||||
/// X-API-KEY
|
||||
pub key: String,
|
||||
|
||||
/// X-API-SECRET
|
||||
///
|
||||
/// Note.
|
||||
/// There is no way to get the secret again after it is distributed in this message
|
||||
pub secret: String,
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
/// Delete an api key for the calling user.
|
||||
/// Response: [NoData]
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(MonitorWriteRequest)]
|
||||
#[response(DeleteApiKeyResponse)]
|
||||
pub struct DeleteApiKey {
|
||||
/// The key which the user intends to delete.
|
||||
pub key: String,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
pub type DeleteApiKeyResponse = NoData;
|
||||
|
||||
//
|
||||
|
||||
/// Admin only method to create an api key for a service user.
|
||||
/// Response: [CreateApiKeyResponse].
|
||||
#[typeshare]
|
||||
|
||||
@@ -3,46 +3,12 @@ use resolver_api::derive::Request;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::entities::{update::ResourceTarget, user::User, NoData};
|
||||
use crate::entities::user::User;
|
||||
|
||||
use super::MonitorWriteRequest;
|
||||
|
||||
//
|
||||
|
||||
/// Push a resource to the front of the users 10 most recently viewed resources.
|
||||
/// Response: [NoData].
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(MonitorWriteRequest)]
|
||||
#[response(PushRecentlyViewedResponse)]
|
||||
pub struct PushRecentlyViewed {
|
||||
/// The target to push.
|
||||
pub resource: ResourceTarget,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
pub type PushRecentlyViewedResponse = NoData;
|
||||
|
||||
//
|
||||
|
||||
/// Set the time the user last opened the UI updates.
|
||||
/// Used for unseen notification dot.
|
||||
/// Response: [NoData]
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
|
||||
)]
|
||||
#[empty_traits(MonitorWriteRequest)]
|
||||
#[response(SetLastSeenUpdateResponse)]
|
||||
pub struct SetLastSeenUpdate {}
|
||||
|
||||
#[typeshare]
|
||||
pub type SetLastSeenUpdateResponse = NoData;
|
||||
|
||||
//
|
||||
|
||||
/// **Admin only.** Create a service user.
|
||||
/// Response: [User].
|
||||
#[typeshare]
|
||||
|
||||
@@ -6,8 +6,7 @@ use serror::deserialize_error;
|
||||
|
||||
use crate::{
|
||||
api::{
|
||||
auth::MonitorAuthRequest, execute::MonitorExecuteRequest,
|
||||
read::MonitorReadRequest, write::MonitorWriteRequest,
|
||||
auth::MonitorAuthRequest, execute::MonitorExecuteRequest, read::MonitorReadRequest, user::MonitorUserRequest, write::MonitorWriteRequest
|
||||
},
|
||||
MonitorClient,
|
||||
};
|
||||
@@ -29,6 +28,22 @@ impl MonitorClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub async fn user<T: MonitorUserRequest>(
|
||||
&self,
|
||||
request: T,
|
||||
) -> anyhow::Result<T::Response> {
|
||||
self
|
||||
.post(
|
||||
"/auth",
|
||||
json!({
|
||||
"type": T::req_type(),
|
||||
"params": request
|
||||
}),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub async fn read<T: MonitorReadRequest>(
|
||||
&self,
|
||||
|
||||
@@ -3,12 +3,14 @@ import {
|
||||
AuthResponses,
|
||||
ExecuteResponses,
|
||||
ReadResponses,
|
||||
UserResponses,
|
||||
WriteResponses,
|
||||
} from "./responses";
|
||||
import {
|
||||
AuthRequest,
|
||||
ExecuteRequest,
|
||||
ReadRequest,
|
||||
UserRequest,
|
||||
WriteRequest,
|
||||
} from "./types";
|
||||
|
||||
@@ -39,6 +41,9 @@ export function MonitorClient(url: string, options: InitOptions) {
|
||||
const auth = async <Req extends AuthRequest>(req: Req) =>
|
||||
await request<Req, AuthResponses[Req["type"]]>("/auth", req);
|
||||
|
||||
const user = async <Req extends UserRequest>(req: Req) =>
|
||||
await request<Req, UserResponses[Req["type"]]>("/user", req);
|
||||
|
||||
const read = async <Req extends ReadRequest>(req: Req) =>
|
||||
await request<Req, ReadResponses[Req["type"]]>("/read", req);
|
||||
|
||||
@@ -48,5 +53,5 @@ export function MonitorClient(url: string, options: InitOptions) {
|
||||
const execute = async <Req extends ExecuteRequest>(req: Req) =>
|
||||
await request<Req, ExecuteResponses[Req["type"]]>("/execute", req);
|
||||
|
||||
return { request, auth, read, write, execute };
|
||||
return { request, auth, user, read, write, execute };
|
||||
}
|
||||
|
||||
@@ -8,6 +8,13 @@ export type AuthResponses = {
|
||||
GetUser: Types.GetUserResponse;
|
||||
};
|
||||
|
||||
export type UserResponses = {
|
||||
PushRecentlyViewed: Types.PushRecentlyViewedResponse;
|
||||
SetLastSeenUpdate: Types.SetLastSeenUpdateResponse;
|
||||
CreateApiKey: Types.CreateApiKeyResponse;
|
||||
DeleteApiKey: Types.DeleteApiKeyResponse;
|
||||
};
|
||||
|
||||
export type ReadResponses = {
|
||||
GetVersion: Types.GetVersionResponse;
|
||||
GetCoreInfo: Types.GetCoreInfoResponse;
|
||||
@@ -118,18 +125,12 @@ export type ReadResponses = {
|
||||
};
|
||||
|
||||
export type WriteResponses = {
|
||||
// ==== API KEY ====
|
||||
CreateApiKey: Types.CreateApiKeyResponse;
|
||||
DeleteApiKey: Types.DeleteApiKeyResponse;
|
||||
// ==== SERVICE USER ====
|
||||
CreateServiceUser: Types.CreateServiceUserResponse;
|
||||
UpdateServiceUserDescription: Types.UpdateServiceUserDescriptionResponse;
|
||||
CreateApiKeyForServiceUser: Types.CreateApiKeyForServiceUserResponse;
|
||||
DeleteApiKeyForServiceUser: Types.DeleteApiKeyForServiceUserResponse;
|
||||
|
||||
// ==== USER ====
|
||||
PushRecentlyViewed: Types.PushRecentlyViewedResponse;
|
||||
SetLastSeenUpdate: Types.SetLastSeenUpdateResponse;
|
||||
CreateServiceUser: Types.CreateServiceUserResponse;
|
||||
UpdateServiceUserDescription: Types.UpdateServiceUserDescription;
|
||||
|
||||
// ==== USER GROUP ====
|
||||
CreateUserGroup: Types.UserGroup;
|
||||
RenameUserGroup: Types.UserGroup;
|
||||
|
||||
@@ -1301,6 +1301,10 @@ export interface Variable {
|
||||
|
||||
export type GetVariableResponse = Variable;
|
||||
|
||||
export type PushRecentlyViewedResponse = NoData;
|
||||
|
||||
export type SetLastSeenUpdateResponse = NoData;
|
||||
|
||||
export type DeleteApiKeyResponse = NoData;
|
||||
|
||||
/** Response for [CreateApiKey]. */
|
||||
@@ -1336,10 +1340,6 @@ export type UpdateProcedureResponse = Procedure;
|
||||
|
||||
export type UpdateTagsOnResourceResponse = NoData;
|
||||
|
||||
export type PushRecentlyViewedResponse = NoData;
|
||||
|
||||
export type SetLastSeenUpdateResponse = NoData;
|
||||
|
||||
export type CreateServiceUserResponse = User;
|
||||
|
||||
export type UpdateServiceUserDescriptionResponse = User;
|
||||
@@ -2591,6 +2591,49 @@ export interface ListVariablesResponse {
|
||||
secrets: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a resource to the front of the users 10 most recently viewed resources.
|
||||
* Response: [NoData].
|
||||
*/
|
||||
export interface PushRecentlyViewed {
|
||||
/** The target to push. */
|
||||
resource: ResourceTarget;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the time the user last opened the UI updates.
|
||||
* Used for unseen notification dot.
|
||||
* Response: [NoData]
|
||||
*/
|
||||
export interface SetLastSeenUpdate {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an api key for the calling user.
|
||||
* Response: [CreateApiKeyResponse].
|
||||
*
|
||||
* Note. After the response is served, there will be no way
|
||||
* to get the secret later.
|
||||
*/
|
||||
export interface CreateApiKey {
|
||||
/** The name for the api key. */
|
||||
name: string;
|
||||
/**
|
||||
* A unix timestamp in millseconds specifying api key expire time.
|
||||
* Default is 0, which means no expiry.
|
||||
*/
|
||||
expires?: I64;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an api key for the calling user.
|
||||
* Response: [NoData]
|
||||
*/
|
||||
export interface DeleteApiKey {
|
||||
/** The key which the user intends to delete. */
|
||||
key: string;
|
||||
}
|
||||
|
||||
export type PartialAlerterConfig =
|
||||
| { type: "Custom", params: _PartialCustomAlerterConfig }
|
||||
| { type: "Slack", params: _PartialSlackAlerterConfig };
|
||||
@@ -2638,32 +2681,6 @@ export interface UpdateAlerter {
|
||||
config: PartialAlerterConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an api key for the calling user.
|
||||
* Response: [CreateApiKeyResponse].
|
||||
*
|
||||
* Note. After the response is served, there will be no way
|
||||
* to get the secret later.
|
||||
*/
|
||||
export interface CreateApiKey {
|
||||
/** The name for the api key. */
|
||||
name: string;
|
||||
/**
|
||||
* A unix timestamp in millseconds specifying api key expire time.
|
||||
* Default is 0, which means no expiry.
|
||||
*/
|
||||
expires?: I64;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an api key for the calling user.
|
||||
* Response: [NoData]
|
||||
*/
|
||||
export interface DeleteApiKey {
|
||||
/** The key which the user intends to delete. */
|
||||
key: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin only method to create an api key for a service user.
|
||||
* Response: [CreateApiKeyResponse].
|
||||
@@ -3120,23 +3137,6 @@ export interface UpdateTagsOnResource {
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a resource to the front of the users 10 most recently viewed resources.
|
||||
* Response: [NoData].
|
||||
*/
|
||||
export interface PushRecentlyViewed {
|
||||
/** The target to push. */
|
||||
resource: ResourceTarget;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the time the user last opened the UI updates.
|
||||
* Used for unseen notification dot.
|
||||
* Response: [NoData]
|
||||
*/
|
||||
export interface SetLastSeenUpdate {
|
||||
}
|
||||
|
||||
/**
|
||||
* **Admin only.** Create a service user.
|
||||
* Response: [User].
|
||||
@@ -3571,15 +3571,17 @@ export type ReadRequest =
|
||||
| { type: "GetVariable", params: GetVariable }
|
||||
| { type: "ListVariables", params: ListVariables };
|
||||
|
||||
export type WriteRequest =
|
||||
| { type: "CreateApiKey", params: CreateApiKey }
|
||||
| { type: "DeleteApiKey", params: DeleteApiKey }
|
||||
| { type: "CreateApiKeyForServiceUser", params: CreateApiKeyForServiceUser }
|
||||
| { type: "DeleteApiKeyForServiceUser", params: DeleteApiKeyForServiceUser }
|
||||
export type UserRequest =
|
||||
| { type: "PushRecentlyViewed", params: PushRecentlyViewed }
|
||||
| { type: "SetLastSeenUpdate", params: SetLastSeenUpdate }
|
||||
| { type: "CreateApiKey", params: CreateApiKey }
|
||||
| { type: "DeleteApiKey", params: DeleteApiKey };
|
||||
|
||||
export type WriteRequest =
|
||||
| { type: "CreateServiceUser", params: CreateServiceUser }
|
||||
| { type: "UpdateServiceUserDescription", params: UpdateServiceUserDescription }
|
||||
| { type: "CreateApiKeyForServiceUser", params: CreateApiKeyForServiceUser }
|
||||
| { type: "DeleteApiKeyForServiceUser", params: DeleteApiKeyForServiceUser }
|
||||
| { type: "CreateUserGroup", params: CreateUserGroup }
|
||||
| { type: "RenameUserGroup", params: RenameUserGroup }
|
||||
| { type: "DeleteUserGroup", params: DeleteUserGroup }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useRead, useUser, useUserInvalidate, useWrite } from "@lib/hooks";
|
||||
import { useManageUser, useRead, useUser, useUserInvalidate } from "@lib/hooks";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@@ -25,7 +25,7 @@ export const TopbarUpdates = () => {
|
||||
);
|
||||
|
||||
const userInvalidate = useUserInvalidate();
|
||||
const { mutate } = useWrite("SetLastSeenUpdate", {
|
||||
const { mutate } = useManageUser("SetLastSeenUpdate", {
|
||||
onSuccess: userInvalidate,
|
||||
});
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
AuthResponses,
|
||||
ExecuteResponses,
|
||||
ReadResponses,
|
||||
UserResponses,
|
||||
WriteResponses,
|
||||
} from "@monitor/client/dist/responses";
|
||||
import {
|
||||
@@ -80,6 +81,35 @@ export const useInvalidate = () => {
|
||||
) => keys.forEach((key) => qc.invalidateQueries({ queryKey: key }));
|
||||
};
|
||||
|
||||
export const useManageUser = <
|
||||
T extends Types.UserRequest["type"],
|
||||
R extends Extract<Types.UserRequest, { type: T }>,
|
||||
P extends R["params"],
|
||||
C extends Omit<
|
||||
UseMutationOptions<UserResponses[T], unknown, P, unknown>,
|
||||
"mutationKey" | "mutationFn"
|
||||
>
|
||||
>(
|
||||
type: T,
|
||||
config?: C
|
||||
) => {
|
||||
const { toast } = useToast();
|
||||
return useMutation({
|
||||
mutationKey: [type],
|
||||
mutationFn: (params: P) => client().user({ type, params } as R),
|
||||
onError: (e, v, c) => {
|
||||
console.log("useManageUser error:", e);
|
||||
toast({
|
||||
title: `Request ${type} Failed`,
|
||||
description: "See console for details",
|
||||
variant: "destructive",
|
||||
});
|
||||
config?.onError && config.onError(e, v, c);
|
||||
},
|
||||
...config,
|
||||
});
|
||||
};
|
||||
|
||||
export const useWrite = <
|
||||
T extends Types.WriteRequest["type"],
|
||||
R extends Extract<Types.WriteRequest, { type: T }>,
|
||||
@@ -96,12 +126,16 @@ export const useWrite = <
|
||||
return useMutation({
|
||||
mutationKey: [type],
|
||||
mutationFn: (params: P) => client().write({ type, params } as R),
|
||||
...config,
|
||||
onError: (e, v, c) => {
|
||||
console.log("useWrite error:", e);
|
||||
toast({ title: `Request ${type} Failed`, description: "See console for details", variant: "destructive" });
|
||||
toast({
|
||||
title: `Request ${type} Failed`,
|
||||
description: "See console for details",
|
||||
variant: "destructive",
|
||||
});
|
||||
config?.onError && config.onError(e, v, c);
|
||||
},
|
||||
...config,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -116,12 +150,23 @@ export const useExecute = <
|
||||
>(
|
||||
type: T,
|
||||
config?: C
|
||||
) =>
|
||||
useMutation({
|
||||
) => {
|
||||
const { toast } = useToast();
|
||||
return useMutation({
|
||||
mutationKey: [type],
|
||||
mutationFn: (params: P) => client().execute({ type, params } as R),
|
||||
onError: (e, v, c) => {
|
||||
console.log("useExecute error:", e);
|
||||
toast({
|
||||
title: `Request ${type} Failed`,
|
||||
description: "See console for details",
|
||||
variant: "destructive",
|
||||
});
|
||||
config?.onError && config.onError(e, v, c);
|
||||
},
|
||||
...config,
|
||||
});
|
||||
};
|
||||
|
||||
export const useAuth = <
|
||||
T extends Types.AuthRequest["type"],
|
||||
@@ -153,7 +198,7 @@ export const useResourceParamType = () => {
|
||||
export const usePushRecentlyViewed = ({ type, id }: Types.ResourceTarget) => {
|
||||
const userInvalidate = useUserInvalidate();
|
||||
|
||||
const push = useWrite("PushRecentlyViewed", {
|
||||
const push = useManageUser("PushRecentlyViewed", {
|
||||
onSuccess: userInvalidate,
|
||||
}).mutate;
|
||||
|
||||
@@ -244,4 +289,4 @@ export const useFilterResources = <Info>(
|
||||
: true)
|
||||
) ?? []
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Page } from "@components/layouts";
|
||||
import { ConfirmButton, CopyButton } from "@components/util";
|
||||
import { useInvalidate, useRead, useSetTitle, useWrite } from "@lib/hooks";
|
||||
import { useInvalidate, useManageUser, useRead, useSetTitle } from "@lib/hooks";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -47,7 +47,7 @@ const CreateKey = () => {
|
||||
const [expires, setExpires] = useState<ExpiresOptions>("never");
|
||||
const [submitted, setSubmitted] = useState<{ key: string; secret: string }>();
|
||||
const invalidate = useInvalidate();
|
||||
const { mutate, isPending } = useWrite("CreateApiKey", {
|
||||
const { mutate, isPending } = useManageUser("CreateApiKey", {
|
||||
onSuccess: ({ key, secret }) => {
|
||||
invalidate(["ListApiKeys"]);
|
||||
setSubmitted({ key, secret });
|
||||
@@ -159,7 +159,7 @@ const CreateKey = () => {
|
||||
const DeleteKey = ({ api_key }: { api_key: string }) => {
|
||||
const invalidate = useInvalidate();
|
||||
const { toast } = useToast();
|
||||
const { mutate, isPending } = useWrite("DeleteApiKey", {
|
||||
const { mutate, isPending } = useManageUser("DeleteApiKey", {
|
||||
onSuccess: () => {
|
||||
invalidate(["ListApiKeys"]);
|
||||
toast({ title: "Api Key Deleted" });
|
||||
|
||||
Reference in New Issue
Block a user