Skip to main content

headless_lms_models/
organizations.rs

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