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}