headless_lms_models/
organizations.rs

1use std::path::PathBuf;
2
3use headless_lms_utils::{ApplicationConfiguration, file_store::FileStore};
4
5use crate::prelude::*;
6
7#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
8pub struct DatabaseOrganization {
9    pub id: Uuid,
10    pub slug: String,
11    pub created_at: DateTime<Utc>,
12    pub updated_at: DateTime<Utc>,
13    pub name: String,
14    pub description: Option<String>,
15    pub organization_image_path: Option<String>,
16    pub deleted_at: Option<DateTime<Utc>>,
17    pub hidden: bool,
18}
19
20#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
21#[cfg_attr(feature = "ts_rs", derive(TS))]
22pub struct Organization {
23    pub id: Uuid,
24    pub slug: String,
25    pub created_at: DateTime<Utc>,
26    pub updated_at: DateTime<Utc>,
27    pub name: String,
28    pub description: Option<String>,
29    pub organization_image_url: Option<String>,
30    pub deleted_at: Option<DateTime<Utc>>,
31    pub hidden: bool,
32}
33
34impl Organization {
35    pub fn from_database_organization(
36        organization: DatabaseOrganization,
37        file_store: &dyn FileStore,
38        app_conf: &ApplicationConfiguration,
39    ) -> Self {
40        let organization_image_url = organization.organization_image_path.as_ref().map(|image| {
41            let path = PathBuf::from(image);
42            file_store.get_download_url(path.as_path(), app_conf)
43        });
44        Self {
45            id: organization.id,
46            created_at: organization.created_at,
47            updated_at: organization.updated_at,
48            name: organization.name,
49            slug: organization.slug,
50            deleted_at: organization.deleted_at,
51            organization_image_url,
52            description: organization.description,
53            hidden: organization.hidden,
54        }
55    }
56}
57
58pub async fn insert(
59    conn: &mut PgConnection,
60    pkey_policy: PKeyPolicy<Uuid>,
61    name: &str,
62    slug: &str,
63    description: Option<&str>,
64    hidden: bool,
65) -> ModelResult<Uuid> {
66    let res = sqlx::query!(
67        "
68        INSERT INTO organizations (id, name, slug, description, hidden)
69        VALUES ($1, $2, $3, $4, $5)
70        RETURNING id
71        ",
72        pkey_policy.into_uuid(),
73        name,
74        slug,
75        description,
76        hidden,
77    )
78    .fetch_one(conn)
79    .await?;
80    Ok(res.id)
81}
82
83pub async fn all_organizations(conn: &mut PgConnection) -> ModelResult<Vec<DatabaseOrganization>> {
84    let organizations = sqlx::query_as!(
85        DatabaseOrganization,
86        r#"
87        SELECT *
88        FROM organizations
89        WHERE deleted_at IS NULL AND hidden = FALSE
90        ORDER BY name
91        "#
92    )
93    .fetch_all(conn)
94    .await?;
95    Ok(organizations)
96}
97
98pub async fn get_organization(
99    conn: &mut PgConnection,
100    organization_id: Uuid,
101) -> ModelResult<DatabaseOrganization> {
102    let org = sqlx::query_as!(
103        DatabaseOrganization,
104        "
105SELECT *
106from organizations
107where id = $1;",
108        organization_id,
109    )
110    .fetch_one(conn)
111    .await?;
112    Ok(org)
113}
114
115pub async fn get_organization_by_slug(
116    conn: &mut PgConnection,
117    organization_slug: &str,
118) -> ModelResult<DatabaseOrganization> {
119    let organization = sqlx::query_as!(
120        DatabaseOrganization,
121        "
122SELECT *
123FROM organizations
124WHERE slug = $1;
125        ",
126        organization_slug
127    )
128    .fetch_one(conn)
129    .await?;
130    Ok(organization)
131}
132
133pub async fn update_organization_image_path(
134    conn: &mut PgConnection,
135    organization_id: Uuid,
136    organization_image_path: Option<String>,
137) -> ModelResult<DatabaseOrganization> {
138    let updated_organization = sqlx::query_as!(
139        DatabaseOrganization,
140        "
141UPDATE organizations
142SET organization_image_path = $1
143WHERE id = $2
144RETURNING *;",
145        organization_image_path,
146        organization_id
147    )
148    .fetch_one(conn)
149    .await?;
150    Ok(updated_organization)
151}
152
153pub async fn update_name_and_hidden(
154    conn: &mut PgConnection,
155    id: Uuid,
156    name: &str,
157    hidden: bool,
158    slug: &str,
159) -> ModelResult<()> {
160    sqlx::query!(
161        r#"
162        UPDATE organizations
163        SET name = $1,
164            hidden = $2,
165            slug = $3
166        WHERE id = $4
167        "#,
168        name,
169        hidden,
170        slug,
171        id
172    )
173    .execute(conn)
174    .await?;
175
176    Ok(())
177}
178
179pub async fn soft_delete(conn: &mut PgConnection, id: Uuid) -> ModelResult<()> {
180    sqlx::query("UPDATE organizations SET deleted_at = now() WHERE id = $1")
181        .bind(id)
182        .execute(conn)
183        .await?;
184    Ok(())
185}
186
187pub async fn all_organizations_include_hidden(
188    conn: &mut PgConnection,
189) -> ModelResult<Vec<DatabaseOrganization>> {
190    let organizations = sqlx::query_as!(
191        DatabaseOrganization,
192        r#"
193SELECT *
194FROM organizations
195WHERE deleted_at IS NULL
196ORDER BY name
197    "#
198    )
199    .fetch_all(conn)
200    .await?;
201    Ok(organizations)
202}
203
204#[cfg(test)]
205mod tests {
206    use super::*;
207    use crate::test_helper::Conn;
208
209    #[tokio::test]
210    async fn gets_organizations() {
211        let mut conn = Conn::init().await;
212        let mut tx = conn.begin().await;
213        let orgs_before = all_organizations(tx.as_mut()).await.unwrap();
214        insert(
215            tx.as_mut(),
216            PKeyPolicy::Fixed(Uuid::parse_str("8c34e601-b5db-4b33-a588-57cb6a5b1669").unwrap()),
217            "org",
218            "slug",
219            Some("description"),
220            false,
221        )
222        .await
223        .unwrap();
224        let orgs_after = all_organizations(tx.as_mut()).await.unwrap();
225        assert_eq!(orgs_before.len() + 1, orgs_after.len());
226    }
227}