use anyhow::{Context as _, Result}; use derive_builder::Builder; use diesel::{ dsl::{auto_type, insert_into, AsSelect, Eq}, pg::Pg, prelude::*, }; use uuid::Uuid; use crate::{ channel_selections::{self, ChannelSelection}, channels::{self, Channel}, schema::projects, }; pub use crate::schema::projects::{dsl, table}; pub const DEFAULT_PROJECT_NAME: &str = "default"; /// A project maps approximately to an application service, and allows messages /// to be directed to an adjustable set of output channels. #[derive(Clone, Debug, Identifiable, Insertable, Queryable, Selectable)] #[diesel(table_name = projects)] pub struct Project { pub id: Uuid, pub team_id: Uuid, pub name: String, } impl Project { #[auto_type(no_type_alias)] pub fn all() -> _ { let select: AsSelect = Project::as_select(); table.select(select) } #[auto_type(no_type_alias)] pub fn with_id<'a>(project_id: &'a Uuid) -> _ { dsl::id.eq(project_id) } #[auto_type(no_type_alias)] pub fn with_team<'a>(team_id: &'a Uuid) -> _ { dsl::team_id.eq(team_id) } #[auto_type(no_type_alias)] pub fn with_name<'a>(name: &'a str) -> _ { dsl::name.eq(name) } #[auto_type(no_type_alias)] pub fn selected_channels(&self) -> _ { let select: AsSelect = Channel::as_select(); let project_filter: Eq = ChannelSelection::with_project(&self.id); channels::table .inner_join(channel_selections::table) .filter(project_filter) .select(select) } /// Lazily fetch a project from the database. That is, query for it, and /// insert if it does not exist yet. pub fn lazy_getter() -> LazyGetterBuilder { LazyGetterBuilder::default() } } #[derive(Builder)] #[builder(pattern = "owned", setter(prefix = "with"))] pub struct LazyGetter { team_id: Uuid, name: String, } impl LazyGetter { pub fn execute(self, db_conn: &mut PgConnection) -> Result { db_conn.transaction(move |conn| { Ok( if let Some(project) = Project::all() .filter(Project::with_team(&self.team_id)) .filter(Project::with_name(&self.name)) .first(conn) .optional() .context("failed to load project")? { project } else { let default_channels = Channel::all() .filter(Channel::with_team(&self.team_id)) .filter(Channel::where_enabled_by_default()) .load(conn) .context("failed to load default channels")?; let id: Uuid = Uuid::now_v7(); let project: Project = insert_into(table) .values(Project { id, team_id: self.team_id, name: self.name, }) .get_result(conn) .context("failed to insert project")?; for channel in default_channels { insert_into(channel_selections::table) .values(ChannelSelection { project_id: project.id, channel_id: channel.id, }) .execute(conn) .context("failed to insert channel selection")?; } project }, ) }) } }