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, ToSchema)]
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 *
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_by_ids(
117    conn: &mut PgConnection,
118    organization_ids: &[Uuid],
119) -> ModelResult<Vec<DatabaseOrganization>> {
120    let organizations = sqlx::query_as!(
121        DatabaseOrganization,
122        "
123SELECT *
124FROM organizations
125WHERE id = ANY($1)
126  AND deleted_at IS NULL
127        ",
128        organization_ids,
129    )
130    .fetch_all(conn)
131    .await?;
132    Ok(organizations)
133}
134
135pub async fn get_organization_by_slug(
136    conn: &mut PgConnection,
137    organization_slug: &str,
138) -> ModelResult<DatabaseOrganization> {
139    let organization = sqlx::query_as!(
140        DatabaseOrganization,
141        "
142SELECT *
143FROM organizations
144WHERE slug = $1;
145        ",
146        organization_slug
147    )
148    .fetch_one(conn)
149    .await?;
150    Ok(organization)
151}
152
153pub async fn update_organization_image_path(
154    conn: &mut PgConnection,
155    organization_id: Uuid,
156    organization_image_path: Option<String>,
157) -> ModelResult<DatabaseOrganization> {
158    let updated_organization = sqlx::query_as!(
159        DatabaseOrganization,
160        "
161UPDATE organizations
162SET organization_image_path = $1
163WHERE id = $2
164RETURNING *;",
165        organization_image_path,
166        organization_id
167    )
168    .fetch_one(conn)
169    .await?;
170    Ok(updated_organization)
171}
172
173pub async fn update_name_and_hidden(
174    conn: &mut PgConnection,
175    id: Uuid,
176    name: &str,
177    hidden: bool,
178    slug: &str,
179) -> ModelResult<()> {
180    sqlx::query!(
181        r#"
182        UPDATE organizations
183        SET name = $1,
184            hidden = $2,
185            slug = $3
186        WHERE id = $4
187        "#,
188        name,
189        hidden,
190        slug,
191        id
192    )
193    .execute(conn)
194    .await?;
195
196    Ok(())
197}
198
199pub async fn soft_delete(conn: &mut PgConnection, id: Uuid) -> ModelResult<()> {
200    sqlx::query("UPDATE organizations SET deleted_at = now() WHERE id = $1 AND deleted_at IS NULL")
201        .bind(id)
202        .execute(conn)
203        .await?;
204    Ok(())
205}
206
207pub async fn all_organizations_include_hidden(
208    conn: &mut PgConnection,
209) -> ModelResult<Vec<DatabaseOrganization>> {
210    let organizations = sqlx::query_as!(
211        DatabaseOrganization,
212        r#"
213SELECT *
214FROM organizations
215WHERE deleted_at IS NULL
216ORDER BY name
217    "#
218    )
219    .fetch_all(conn)
220    .await?;
221    Ok(organizations)
222}
223
224#[cfg(test)]
225mod tests {
226    use super::*;
227    use crate::test_helper::Conn;
228
229    #[tokio::test]
230    async fn gets_organizations() {
231        let mut conn = Conn::init().await;
232        let mut tx = conn.begin().await;
233        let orgs_before = all_organizations(tx.as_mut()).await.unwrap();
234        insert(
235            tx.as_mut(),
236            PKeyPolicy::Fixed(Uuid::parse_str("8c34e601-b5db-4b33-a588-57cb6a5b1669").unwrap()),
237            "org",
238            "slug",
239            Some("description"),
240            false,
241        )
242        .await
243        .unwrap();
244        let orgs_after = all_organizations(tx.as_mut()).await.unwrap();
245        assert_eq!(orgs_before.len() + 1, orgs_after.len());
246    }
247}