diff --git a/core/src/helpers.rs b/core/src/helpers.rs index be416793f..e0df9ed55 100644 --- a/core/src/helpers.rs +++ b/core/src/helpers.rs @@ -4,6 +4,7 @@ use anyhow::{anyhow, Context}; use monitor_types::{ busy::Busy, entities::{ + alerter::Alerter, build::Build, builder::Builder, deployment::{Deployment, DockerContainerState}, @@ -304,6 +305,45 @@ impl State { Ok(repo.get_user_permissions(user_id)) } + pub async fn get_alerter(&self, alerter_id: &str) -> anyhow::Result { + self.db + .alerters + .find_one_by_id(alerter_id) + .await? + .context(format!("did not find any alerter with id {alerter_id}")) + } + + pub async fn get_alerter_check_permissions( + &self, + alerter_id: &str, + user: &RequestUser, + permission_level: PermissionLevel, + ) -> anyhow::Result { + let alerter = self + .db + .alerters + .find_one_by_id(alerter_id) + .await? + .context(format!("did not find any alerter with id {alerter_id}"))?; + let permissions = alerter.get_user_permissions(&user.id); + if user.is_admin || permissions >= permission_level { + Ok(alerter) + } else { + Err(anyhow!( + "user does not have required permissions on this alerter" + )) + } + } + + pub async fn get_user_permission_on_alerter( + &self, + user_id: &str, + alerter_id: &str, + ) -> anyhow::Result { + let alerter = self.get_alerter(alerter_id).await?; + Ok(alerter.get_user_permissions(user_id)) + } + pub async fn send_update(&self, update: Update) -> anyhow::Result<()> { self.update.sender.lock().await.send(update)?; Ok(()) diff --git a/core/src/requests/write/alerter.rs b/core/src/requests/write/alerter.rs index 8b44b3fbd..147971b70 100644 --- a/core/src/requests/write/alerter.rs +++ b/core/src/requests/write/alerter.rs @@ -1,6 +1,12 @@ +use anyhow::Context; use async_trait::async_trait; use monitor_types::{ - entities::alerter::Alerter, + entities::{ + alerter::Alerter, + update::{Log, ResourceTarget, Update}, + Operation, PermissionLevel, + }, + monitor_timestamp, requests::write::{CopyAlerter, CreateAlerter, DeleteAlerter, UpdateAlerter}, }; use resolver_api::Resolve; @@ -14,7 +20,51 @@ impl Resolve for State { CreateAlerter { name, config }: CreateAlerter, user: RequestUser, ) -> anyhow::Result { - todo!() + let start_ts = monitor_timestamp(); + let is_default = self.db.alerters.find_one(None, None).await?.is_none(); + let alerter = Alerter { + id: Default::default(), + name, + created_at: start_ts, + updated_at: start_ts, + permissions: [(user.id.clone(), PermissionLevel::Update)] + .into_iter() + .collect(), + description: Default::default(), + tags: Default::default(), + is_default, + config: config.into(), + }; + let alerter_id = self + .db + .alerters + .create_one(alerter) + .await + .context("failed to add alerter to db")?; + let alerter = self.get_alerter(&alerter_id).await?; + let update = Update { + target: ResourceTarget::Alerter(alerter_id), + operation: Operation::CreateAlerter, + start_ts, + end_ts: monitor_timestamp().into(), + operator: user.id.clone(), + success: true, + logs: vec![ + Log::simple( + "create alerter", + format!( + "created alerter\nid: {}\nname: {}", + alerter.id, alerter.name + ), + ), + Log::simple("config", format!("{:#?}", alerter.config)), + ], + ..Default::default() + }; + + self.add_update(update).await?; + + Ok(alerter) } } diff --git a/core/src/requests/write/builder.rs b/core/src/requests/write/builder.rs index c656ccef0..441759f85 100644 --- a/core/src/requests/write/builder.rs +++ b/core/src/requests/write/builder.rs @@ -39,7 +39,7 @@ impl Resolve for State { .builders .create_one(builder) .await - .context("failed to add build to db")?; + .context("failed to add builder to db")?; let builder = self.get_builder(&builder_id).await?; let update = Update { target: ResourceTarget::Builder(builder_id), diff --git a/core/src/requests/write/permissions.rs b/core/src/requests/write/permissions.rs index b8d4da304..ea31e9233 100644 --- a/core/src/requests/write/permissions.rs +++ b/core/src/requests/write/permissions.rs @@ -222,6 +222,28 @@ impl Resolve for State { user.username, permission, repo.name ) } + ResourceTarget::Alerter(id) => { + let alerter = self + .db + .alerters + .find_one_by_id(id) + .await + .context("failed at find alerter query")? + .ok_or(anyhow!("failed to find a alerter with id {id}"))?; + self.db + .alerters + .update_one( + id, + mungos::Update::Set(doc! { + format!("permissions.{}", user_id): permission.to_string() + }), + ) + .await?; + format!( + "user {} given {} permissions on alerter {}", + user.username, permission, alerter.name + ) + } }; let mut update = Update { operation: Operation::UpdateUserPermissionsOnTarget, diff --git a/core/src/ws.rs b/core/src/ws.rs index 24aab301a..e4ba87a78 100644 --- a/core/src/ws.rs +++ b/core/src/ws.rs @@ -10,7 +10,7 @@ use axum::{ }; use futures::{SinkExt, StreamExt}; use monitor_types::entities::{ - update::{ResourceTarget, Update}, + update::{ResourceTarget, Update, ResourceTargetVariant}, user::User, PermissionLevel, }; @@ -170,46 +170,32 @@ impl State { let permissions = self .get_user_permission_on_server(user_id, server_id) .await?; - (permissions, "server") + (permissions, ResourceTargetVariant::Server) } ResourceTarget::Deployment(deployment_id) => { let permissions = self .get_user_permission_on_deployment(user_id, deployment_id) .await?; - (permissions, "deployment") + (permissions, ResourceTargetVariant::Deployment) } ResourceTarget::Build(build_id) => { let permissions = self.get_user_permission_on_build(user_id, build_id).await?; - (permissions, "build") + (permissions, ResourceTargetVariant::Build) } ResourceTarget::Builder(builder_id) => { let permissions = self .get_user_permission_on_builder(user_id, builder_id) .await?; - (permissions, "builder") + (permissions, ResourceTargetVariant::Builder) } ResourceTarget::Repo(repo_id) => { let permissions = self.get_user_permission_on_repo(user_id, repo_id).await?; - (permissions, "repo") + (permissions, ResourceTargetVariant::Repo) + } + ResourceTarget::Alerter(alerter_id) => { + let permissions = self.get_user_permission_on_alerter(user_id, alerter_id).await?; + (permissions, ResourceTargetVariant::Alerter) } - // UpdateTarget::Procedure(procedure_id) => { - // let permissions = db_client - // .get_user_permission_on_procedure(user_id, procedure_id) - // .await?; - // (permissions, "procedure") - // } - // UpdateTarget::Group(group_id) => { - // let permissions = db_client - // .get_user_permission_on_group(user_id, group_id) - // .await?; - // (permissions, "group") - // } - // UpdateTarget::Command(command_id) => { - // let permissions = db_client - // .get_user_permission_on_command(user_id, command_id) - // .await?; - // (permissions, "command") - // } ResourceTarget::System => { return Err(anyhow!("user not admin, can't recieve system updates")) } diff --git a/lib/types/src/entities/alerter.rs b/lib/types/src/entities/alerter.rs index 3db36e2d4..841cd5553 100644 --- a/lib/types/src/entities/alerter.rs +++ b/lib/types/src/entities/alerter.rs @@ -47,7 +47,7 @@ pub struct Alerter { #[serde(default)] #[builder(default)] - pub default_alerter: bool, + pub is_default: bool, pub config: AlerterConfig, } @@ -76,6 +76,40 @@ pub enum PartialAlerterConfig { Slack(_PartialSlackAlerterConfig), } +impl From for AlerterConfig { + fn from(value: PartialAlerterConfig) -> AlerterConfig { + match value { + PartialAlerterConfig::Custom(config) => AlerterConfig::Custom(config.into()), + PartialAlerterConfig::Slack(config) => AlerterConfig::Slack(config.into()), + } + } +} + +impl AlerterConfig { + pub fn merge_partial(self, partial: PartialAlerterConfig) -> AlerterConfig { + match partial { + PartialAlerterConfig::Custom(partial) => match self { + AlerterConfig::Custom(config) => { + let config = CustomAlerterConfig { + url: partial.url.unwrap_or(config.url), + }; + AlerterConfig::Custom(config) + } + _ => AlerterConfig::Custom(partial.into()), + }, + PartialAlerterConfig::Slack(partial) => match self { + AlerterConfig::Slack(config) => { + let config = SlackAlerterConfig { + url: partial.url.unwrap_or(config.url), + }; + AlerterConfig::Slack(config) + } + _ => AlerterConfig::Slack(partial.into()), + }, + } + } +} + #[typeshare] #[derive(Serialize, Deserialize, Debug, Clone, Builder, Partial)] #[partial_derive(Serialize, Deserialize, Debug, Clone)] @@ -84,6 +118,14 @@ pub struct CustomAlerterConfig { pub url: String, } +impl From for CustomAlerterConfig { + fn from(value: PartialCustomAlerterConfig) -> CustomAlerterConfig { + CustomAlerterConfig { + url: value.url.unwrap_or(String::from("http://localhost:7000")), + } + } +} + #[typeshare] #[derive(Serialize, Deserialize, Debug, Clone, Builder, Partial)] #[partial_derive(Serialize, Deserialize, Debug, Clone)] @@ -91,3 +133,13 @@ pub struct CustomAlerterConfig { pub struct SlackAlerterConfig { pub url: String, } + +impl From for SlackAlerterConfig { + fn from(value: PartialSlackAlerterConfig) -> SlackAlerterConfig { + SlackAlerterConfig { + url: value.url.unwrap_or(String::from( + "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX", + )), + } + } +} diff --git a/lib/types/src/entities/build.rs b/lib/types/src/entities/build.rs index 2f5ef1a6a..3b010a33e 100644 --- a/lib/types/src/entities/build.rs +++ b/lib/types/src/entities/build.rs @@ -59,9 +59,6 @@ pub struct Build { #[derive(Serialize, Deserialize, Debug, Clone, Builder, Partial, MungosIndexed)] #[partial_derive(Serialize, Deserialize, Debug, Clone)] #[skip_serializing_none] -#[doc_index(doc! { "builder.type": 1 })] -#[sparse_doc_index(doc! { "builder.params.server_id": 1 })] -#[sparse_doc_index(doc! { "builder.params.builder_id": 1 })] pub struct BuildConfig { pub builder: BuildBuilderConfig, @@ -162,6 +159,9 @@ pub struct BuildActionState { #[derive(Serialize, Deserialize, Debug, Clone, MungosIndexed, EnumVariants)] #[variant_derive(Serialize, Deserialize, Debug, Clone, Copy, Display, EnumString)] #[serde(tag = "type", content = "params")] +#[doc_index(doc! { "type": 1 })] +#[sparse_doc_index(doc! { "params.server_id": 1 })] +#[sparse_doc_index(doc! { "params.builder_id": 1 })] pub enum BuildBuilderConfig { Server { server_id: String }, Builder { builder_id: String }, diff --git a/lib/types/src/entities/deployment.rs b/lib/types/src/entities/deployment.rs index 953b94155..9d3a9ba64 100644 --- a/lib/types/src/entities/deployment.rs +++ b/lib/types/src/entities/deployment.rs @@ -55,8 +55,6 @@ pub struct Deployment { #[derive(Serialize, Deserialize, Debug, Clone, Builder, Partial, MungosIndexed)] #[partial_derive(Serialize, Deserialize, Debug, Clone)] #[skip_serializing_none] -#[doc_index(doc! { "image.type": 1 })] -#[sparse_doc_index(doc! { "image.params.build_id": 1 })] pub struct DeploymentConfig { #[serde(default)] #[builder(default)] @@ -166,6 +164,8 @@ impl From for DeploymentConfig { #[typeshare] #[derive(Serialize, Deserialize, Debug, Clone, MungosIndexed, EnumVariants)] #[variant_derive(Serialize, Deserialize, Debug, Clone, Copy, Display, EnumString)] +#[doc_index(doc! { "type": 1 })] +#[sparse_doc_index(doc! { "params.build_id": 1 })] #[serde(tag = "type", content = "params")] pub enum DeploymentImage { Image { image: String }, diff --git a/lib/types/src/entities/mod.rs b/lib/types/src/entities/mod.rs index 417dd8173..dcfde03a9 100644 --- a/lib/types/src/entities/mod.rs +++ b/lib/types/src/entities/mod.rs @@ -301,21 +301,10 @@ pub enum Operation { CloneRepo, PullRepo, - // // procedure - // CreateProcedure, - // UpdateProcedure, - // DeleteProcedure, - - // // command - // CreateCommand, - // UpdateCommand, - // DeleteCommand, - // RunCommand, - - // // group - // CreateGroup, - // UpdateGroup, - // DeleteGroup, + // alerter + CreateAlerter, + UpdateAlerter, + DeleteAlerter, // user UpdateUserPermissions, diff --git a/lib/types/src/entities/update.rs b/lib/types/src/entities/update.rs index 05b52f475..5878043ad 100644 --- a/lib/types/src/entities/update.rs +++ b/lib/types/src/entities/update.rs @@ -8,7 +8,8 @@ use typeshare::typeshare; use crate::{entities::Operation, monitor_timestamp, MongoId, I64}; use super::{ - build::Build, builder::Builder, deployment::Deployment, repo::Repo, server::Server, Version, + alerter::Alerter, build::Build, builder::Builder, deployment::Deployment, repo::Repo, + server::Server, Version, }; #[typeshare] @@ -109,6 +110,7 @@ pub enum ResourceTarget { Deployment(String), Server(String), Repo(String), + Alerter(String), } impl From<&Build> for ResourceTarget { @@ -141,6 +143,12 @@ impl From<&Builder> for ResourceTarget { } } +impl From<&Alerter> for ResourceTarget { + fn from(alerter: &Alerter) -> Self { + Self::Alerter(alerter.id.clone()) + } +} + #[typeshare] #[derive( Serialize, diff --git a/lib/types/src/permissioned.rs b/lib/types/src/permissioned.rs index ece435906..76f46d7c9 100644 --- a/lib/types/src/permissioned.rs +++ b/lib/types/src/permissioned.rs @@ -1,6 +1,6 @@ use crate::entities::{ - build::Build, builder::Builder, deployment::Deployment, repo::Repo, server::Server, - PermissionLevel, PermissionsMap, + alerter::Alerter, build::Build, builder::Builder, deployment::Deployment, repo::Repo, + server::Server, PermissionLevel, PermissionsMap, }; pub trait Permissioned { @@ -40,3 +40,9 @@ impl Permissioned for Repo { &self.permissions } } + +impl Permissioned for Alerter { + fn permissions_map(&self) -> &PermissionsMap { + &self.permissions + } +}