shoutdotdev/src/projects.rs

119 lines
3.7 KiB
Rust
Raw Normal View History

2025-04-23 12:57:10 -07:00
use anyhow::{Context as _, Result};
use derive_builder::Builder;
2025-02-26 13:10:48 -08:00
use diesel::{
2025-03-12 23:16:22 -07:00
dsl::{auto_type, insert_into, AsSelect, Eq},
2025-02-26 13:10:48 -08:00
pg::Pg,
prelude::*,
};
2025-02-26 13:10:50 -08:00
use uuid::Uuid;
2025-02-26 13:10:45 -08:00
use crate::{
2025-04-23 12:57:10 -07:00
channel_selections::{self, ChannelSelection},
channels::{self, Channel},
schema::projects,
2025-02-26 13:10:45 -08:00
};
2025-02-26 13:10:50 -08:00
2025-04-23 12:57:10 -07:00
pub use crate::schema::projects::{dsl, table};
2025-03-14 13:04:57 -07:00
pub const DEFAULT_PROJECT_NAME: &str = "default";
2025-03-14 13:04:57 -07:00
/// A project maps approximately to an application service, and allows messages
/// to be directed to an adjustable set of output channels.
2025-04-23 12:57:10 -07:00
#[derive(Clone, Debug, Identifiable, Insertable, Queryable, Selectable)]
2025-02-26 13:10:47 -08:00
#[diesel(table_name = projects)]
2025-02-26 13:10:50 -08:00
pub struct Project {
pub id: Uuid,
pub team_id: Uuid,
pub name: String,
}
2025-02-26 13:10:48 -08:00
impl Project {
#[auto_type(no_type_alias)]
pub fn all() -> _ {
let select: AsSelect<Project, Pg> = Project::as_select();
2025-04-23 12:57:10 -07:00
table.select(select)
2025-02-26 13:10:47 -08:00
}
2025-02-26 13:10:45 -08:00
#[auto_type(no_type_alias)]
2025-03-14 13:04:57 -07:00
pub fn with_id<'a>(project_id: &'a Uuid) -> _ {
2025-04-23 12:57:10 -07:00
dsl::id.eq(project_id)
2025-02-26 13:10:45 -08:00
}
2025-02-26 13:10:47 -08:00
#[auto_type(no_type_alias)]
2025-03-14 13:04:57 -07:00
pub fn with_team<'a>(team_id: &'a Uuid) -> _ {
2025-04-23 12:57:10 -07:00
dsl::team_id.eq(team_id)
2025-02-26 13:10:47 -08:00
}
#[auto_type(no_type_alias)]
2025-03-14 13:04:57 -07:00
pub fn with_name<'a>(name: &'a str) -> _ {
2025-04-23 12:57:10 -07:00
dsl::name.eq(name)
2025-02-26 13:10:48 -08:00
}
2025-02-26 13:10:45 -08:00
#[auto_type(no_type_alias)]
pub fn selected_channels(&self) -> _ {
let select: AsSelect<Channel, Pg> = Channel::as_select();
2025-04-23 12:57:10 -07:00
let project_filter: Eq<channel_selections::dsl::project_id, &Uuid> =
ChannelSelection::with_project(&self.id);
2025-02-26 13:10:45 -08:00
channels::table
.inner_join(channel_selections::table)
.filter(project_filter)
.select(select)
}
2025-04-23 12:57:10 -07:00
/// 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
},
)
})
}
2025-02-26 13:10:48 -08:00
}