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}