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}