diff --git a/identity/migrations/20231108065148_user_deleted_at.down.sql b/identity/migrations/20231108065148_user_deleted_at.down.sql new file mode 100644 index 0000000..d2f607c --- /dev/null +++ b/identity/migrations/20231108065148_user_deleted_at.down.sql @@ -0,0 +1 @@ +-- Add down migration script here diff --git a/identity/migrations/20231108065148_user_deleted_at.up.sql b/identity/migrations/20231108065148_user_deleted_at.up.sql new file mode 100644 index 0000000..3a334e6 --- /dev/null +++ b/identity/migrations/20231108065148_user_deleted_at.up.sql @@ -0,0 +1,2 @@ +-- Add up migration script here +ALTER TABLE "user" ADD "deleted_at" TIMESTAMPTZ DEFAULT NULL; \ No newline at end of file diff --git a/identity/src/entities/mod.rs b/identity/src/entities/mod.rs index 9a4db8b..2dd4837 100644 --- a/identity/src/entities/mod.rs +++ b/identity/src/entities/mod.rs @@ -22,6 +22,7 @@ pub struct User { pub date_of_birth: Option>, pub created_at: chrono::DateTime, pub updated_at: chrono::DateTime, + pub deleted_at: chrono::DateTime, } #[derive(Debug, Clone, Deserialize, Serialize, Object, sqlx::FromRow)] @@ -36,6 +37,20 @@ pub struct Ministry { pub updated_at: chrono::DateTime, } +#[derive(Debug, Clone, Deserialize, Serialize, Object, sqlx::FromRow)] +pub struct UserConnectGroup { + pub user_id: String, + pub connect_group_id: Option, + pub user_role: String, +} + +#[derive(Debug, Clone, Deserialize, Serialize, Object, sqlx::FromRow)] +pub struct UserMinistry { + pub user_id: String, + pub ministry_id: Option, + pub user_role: String, +} + #[derive(Debug, Clone, Deserialize, Serialize, Object, sqlx::FromRow)] pub struct PastoralRole { pub id: String, diff --git a/identity/src/routes/mod.rs b/identity/src/routes/mod.rs index 9e3c97f..dd043f2 100644 --- a/identity/src/routes/mod.rs +++ b/identity/src/routes/mod.rs @@ -89,6 +89,23 @@ impl Routes { self._list_users(db).await } + /// List or search soft deleted users + /// + /// Retrieve a list of soft deleted users or search for deleted users given a query. + #[oai( + path = "/deleted-users", + method = "get", + operation_id = "list-deleted-users", + tag = "Tag::User" + )] + async fn list_deleted_users( + &self, + _auth: BearerAuth, + db: web::Data<&Database>, + ) -> Result { + self._list_deleted_users(db).await + } + /// Get a user /// /// Retrieve a user's details given its id. @@ -128,7 +145,7 @@ impl Routes { /// Delete a user /// - /// Deletes a user based on the id from the database. + /// Soft deletes a user based on the id from the database. The associated cg and ministry of the user will be deleted. #[oai( path = "/users/:id", method = "delete", diff --git a/identity/src/routes/users/delete.rs b/identity/src/routes/users/delete.rs index dfca2f9..c913559 100644 --- a/identity/src/routes/users/delete.rs +++ b/identity/src/routes/users/delete.rs @@ -30,7 +30,7 @@ impl crate::routes::Routes { let user = sqlx::query_as_unchecked!( entities::User, r#" - DELETE FROM "user" WHERE id = $1::TEXT RETURNING * + UPDATE "user" SET "deleted_at" = NOW() WHERE id = $1::TEXT RETURNING * "#, &*id ) @@ -45,6 +45,26 @@ impl crate::routes::Routes { ))), })?; + let _user_cg = sqlx::query_as_unchecked!( + entities::UserConnectGroup, + r#" + DELETE FROM "user_connect_group" WHERE user_id = $1::TEXT RETURNING * + "#, + &*id + ) + .fetch_one(&db.db) + .await; + + let _user_ministry = sqlx::query_as_unchecked!( + entities::UserMinistry, + r#" + DELETE FROM "user_ministry" WHERE user_id = $1::TEXT RETURNING * + "#, + &*id + ) + .fetch_one(&db.db) + .await; + Ok(Response::Ok(payload::Json(user))) } } diff --git a/identity/src/routes/users/list_deleted_users.rs b/identity/src/routes/users/list_deleted_users.rs new file mode 100644 index 0000000..7dff596 --- /dev/null +++ b/identity/src/routes/users/list_deleted_users.rs @@ -0,0 +1,39 @@ +use poem::web; +use poem_openapi::payload; + +use crate::{database::Database, entities, error::ErrorResponse}; + +#[derive(poem_openapi::ApiResponse)] +pub enum Response { + #[oai(status = 200)] + Ok(payload::Json>), +} + +#[derive(poem_openapi::ApiResponse)] +pub enum Error { + #[oai(status = 400)] + BadRequest(payload::Json), + + #[oai(status = 500)] + InternalServer(payload::Json), +} + +impl crate::routes::Routes { + pub async fn _list_deleted_users(&self, db: web::Data<&Database>) -> Result { + let users = sqlx::query_as_unchecked!( + entities::User, + r#" + SELECT * from "user" WHERE deleted_at IS NOT NULL + "#, + ) + .fetch_all(&db.db) + .await + .map_err(|e| match e { + _ => Error::InternalServer(payload::Json(ErrorResponse::from( + &e as &(dyn std::error::Error + Send + Sync), + ))), + })?; + + Ok(Response::Ok(payload::Json(users))) + } +} diff --git a/identity/src/routes/users/mod.rs b/identity/src/routes/users/mod.rs index f54ac83..8a47efd 100644 --- a/identity/src/routes/users/mod.rs +++ b/identity/src/routes/users/mod.rs @@ -6,4 +6,5 @@ pub mod get_ministries; pub mod get_ministry_roles; pub mod get_pastoral_roles; pub mod list; +pub mod list_deleted_users; pub mod update;