shoutdotdev/src/projects.rs

118 lines
3.7 KiB
Rust

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, Pg> = 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, Pg> = Channel::as_select();
let project_filter: Eq<channel_selections::dsl::project_id, &Uuid> =
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<Project> {
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
},
)
})
}
}