refactor: code cleanup

This commit is contained in:
Sebastian Pravda 2022-11-27 22:01:11 +01:00
parent 74b9ec3560
commit 9957ea232f
No known key found for this signature in database
GPG key ID: F3BC84F08EFA3F57
9 changed files with 85 additions and 143 deletions

View file

@ -13,6 +13,8 @@ use sea_orm_rocket::Connection;
use crate::{guards::request::{auth::AdminAuth}, pool::Db, requests}; use crate::{guards::request::{auth::AdminAuth}, pool::Db, requests};
use super::to_custom_error;
#[post("/login", data = "<login_form>")] #[post("/login", data = "<login_form>")]
pub async fn login( pub async fn login(
conn: Connection<'_, Db>, conn: Connection<'_, Db>,
@ -100,7 +102,7 @@ pub async fn create_candidate(
form.personal_id_number.clone(), form.personal_id_number.clone(),
) )
.await .await
.map_err(|e| Custom(Status::InternalServerError, e.to_string()))?; .map_err(to_custom_error)?;
Ok( Ok(
Json( Json(
@ -131,7 +133,7 @@ pub async fn list_candidates(
let candidates = CandidateService::list_candidates(private_key, db, field, page) let candidates = CandidateService::list_candidates(private_key, db, field, page)
.await .await
.map_err(|e| Custom(Status::from_code(e.code()).unwrap(), e.to_string()))?; .map_err(to_custom_error)?;
Ok(Json(candidates)) Ok(Json(candidates))
} }
@ -151,7 +153,7 @@ pub async fn get_candidate(
id id
) )
.await .await
.map_err(|e| Custom(Status::from_code(e.code()).unwrap(), e.to_string()))?; .map_err(to_custom_error)?;
Ok(Json(details)) Ok(Json(details))
} }
@ -167,7 +169,7 @@ pub async fn reset_candidate_password(
let response = CandidateService::reset_password(private_key, db, id) let response = CandidateService::reset_password(private_key, db, id)
.await .await
.map_err(|e| Custom(Status::from_code(e.code()).unwrap(), e.to_string()))?; .map_err(to_custom_error)?;
Ok( Ok(
Json(response) Json(response)
@ -183,7 +185,7 @@ pub async fn get_candidate_portfolio(
let portfolio = PortfolioService::get_portfolio(id, private_key) let portfolio = PortfolioService::get_portfolio(id, private_key)
.await .await
.map_err(|e| Custom(Status::from_code(e.code()).unwrap(), e.to_string()))?; .map_err(to_custom_error)?;
Ok(portfolio) Ok(portfolio)
} }

View file

@ -16,6 +16,8 @@ use crate::guards::data::letter::Letter;
use crate::guards::data::portfolio::Portfolio; use crate::guards::data::portfolio::Portfolio;
use crate::{guards::request::auth::CandidateAuth, pool::Db, requests}; use crate::{guards::request::auth::CandidateAuth, pool::Db, requests};
use super::to_custom_error;
#[post("/login", data = "<login_form>")] #[post("/login", data = "<login_form>")]
pub async fn login( pub async fn login(
conn: Connection<'_, Db>, conn: Connection<'_, Db>,
@ -25,31 +27,19 @@ pub async fn login(
) -> Result<String, Custom<String>> { ) -> Result<String, Custom<String>> {
let ip_addr: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0); let ip_addr: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0);
let db = conn.into_inner(); let db = conn.into_inner();
let session_token_key = CandidateService::login( let (session_token, private_key) = CandidateService::login(
db, db,
login_form.application_id, login_form.application_id,
login_form.password.to_string(), login_form.password.to_string(),
ip_addr.ip().to_string(), ip_addr.ip().to_string(),
) )
.await; .await
.map_err(to_custom_error)?;
let Ok(session_token_key) = session_token_key else {
let e = session_token_key.unwrap_err();
return Err(Custom(
Status::from_code(e.code()).unwrap_or(Status::InternalServerError),
e.to_string(),
));
};
let session_token = session_token_key.0;
let private_key = session_token_key.1;
cookies.add_private(Cookie::new("id", session_token.clone())); cookies.add_private(Cookie::new("id", session_token.clone()));
cookies.add_private(Cookie::new("key", private_key.clone())); cookies.add_private(Cookie::new("key", private_key.clone()));
let response = format!("{} {}", session_token, private_key); return Ok("".to_string());
return Ok(response);
} }
#[post("/logout")] #[post("/logout")]
@ -61,14 +51,14 @@ pub async fn logout(conn: Connection<'_, Db>, _session: CandidateAuth, cookies:
let session_id = Uuid::try_parse(cookie.value()) // unwrap would be safe here because of the auth guard let session_id = Uuid::try_parse(cookie.value()) // unwrap would be safe here because of the auth guard
.map_err(|e| Custom(Status::BadRequest, e.to_string()))?; .map_err(|e| Custom(Status::BadRequest, e.to_string()))?;
let res = CandidateService::logout(db, session_id) CandidateService::logout(db, session_id)
.await .await
.map_err(|e| Custom(Status::from_code(e.code()).unwrap_or(Status::InternalServerError), e.to_string()))?; .map_err(to_custom_error)?;
cookies.remove_private(Cookie::named("id")); cookies.remove_private(Cookie::named("id"));
cookies.remove_private(Cookie::named("key")); cookies.remove_private(Cookie::named("key"));
Ok(res) Ok(())
} }
#[get("/whoami")] #[get("/whoami")]
@ -86,19 +76,11 @@ pub async fn post_details(
) -> Result<Json<ApplicationDetails>, Custom<String>> { ) -> Result<Json<ApplicationDetails>, Custom<String>> {
let db = conn.into_inner(); let db = conn.into_inner();
let form = details.into_inner(); let form = details.into_inner();
let candidate: entity::candidate::Model = session.into(); // TODO: don't return candidate from session let candidate: entity::candidate::Model = session.into();
let candidate_parent = let _candidate_parent = ApplicationService::add_all_details(db, candidate.application, &form)
ApplicationService::add_all_details(db, candidate.application, &form).await; .await
.map_err(to_custom_error)?;
if candidate_parent.is_err() {
// TODO cleanup
let e = candidate_parent.err().unwrap();
return Err(Custom(
Status::from_code(e.code()).unwrap_or_default(),
e.to_string(),
));
}
Ok( Ok(
Json(form) Json(form)
@ -117,14 +99,10 @@ pub async fn get_details(
// let handle = tokio::spawn(async move { // let handle = tokio::spawn(async move {
let details = ApplicationService::decrypt_all_details(private_key, db, candidate.application) let details = ApplicationService::decrypt_all_details(private_key, db, candidate.application)
.await .await
.map_err(|e| { .map(|x| Json(x))
Custom( .map_err(to_custom_error);
Status::from_code(e.code()).unwrap_or_default(),
e.to_string(),
)
});
details.map(|d| Json(d)) details
} }
#[post("/cover_letter", data = "<letter>")] #[post("/cover_letter", data = "<letter>")]
pub async fn upload_cover_letter( pub async fn upload_cover_letter(
@ -133,40 +111,25 @@ pub async fn upload_cover_letter(
) -> Result<String, Custom<String>> { ) -> Result<String, Custom<String>> {
let candidate: entity::candidate::Model = session.into(); let candidate: entity::candidate::Model = session.into();
let candidate = PortfolioService::add_cover_letter_to_cache(candidate.application, letter.into())
PortfolioService::add_cover_letter_to_cache(candidate.application, letter.into()).await; .await
.map_err(to_custom_error)?;
if candidate.is_err() {
// TODO cleanup
let e = candidate.err().unwrap();
return Err(Custom(
Status::from_code(e.code()).unwrap_or_default(),
e.to_string(),
));
}
Ok("Letter added".to_string()) Ok("Letter added".to_string())
} }
#[get("/submission_progress")] #[get("/submission_progress")]
pub async fn submission_progress( pub async fn submission_progress(
conn: Connection<'_, Db>,
session: CandidateAuth session: CandidateAuth
) -> Result<Json<SubmissionProgress>, Custom<String>> { ) -> Result<Json<SubmissionProgress>, Custom<String>> {
let candidate: entity::candidate::Model = session.into(); let candidate: entity::candidate::Model = session.into();
let progress = PortfolioService::get_submission_progress(candidate.application) let progress = PortfolioService::get_submission_progress(candidate.application)
.await .await
.map_err(|e| { .map(|x| Json(x))
Custom( .map_err(to_custom_error);
Status::from_code(e.code()).unwrap_or_default(),
e.to_string(),
)
})?;
Ok( progress
Json(progress)
)
} }
// TODO: JSON // TODO: JSON
#[get["/is_cover_letter"]] #[get["/is_cover_letter"]]
@ -185,17 +148,9 @@ pub async fn upload_portfolio_letter(
) -> Result<String, Custom<String>> { ) -> Result<String, Custom<String>> {
let candidate: entity::candidate::Model = session.into(); let candidate: entity::candidate::Model = session.into();
let candidate = PortfolioService::add_portfolio_letter_to_cache(candidate.application, letter.into())
PortfolioService::add_portfolio_letter_to_cache(candidate.application, letter.into()).await; .await
.map_err(to_custom_error)?;
if candidate.is_err() {
// TODO cleanup
let e = candidate.err().unwrap();
return Err(Custom(
Status::from_code(e.code()).unwrap_or_default(),
e.to_string(),
));
}
Ok("Letter added".to_string()) Ok("Letter added".to_string())
} }
@ -217,17 +172,9 @@ pub async fn upload_portfolio_zip(
) -> Result<String, Custom<String>> { ) -> Result<String, Custom<String>> {
let candidate: entity::candidate::Model = session.into(); let candidate: entity::candidate::Model = session.into();
let candidate = PortfolioService::add_portfolio_zip_to_cache(candidate.application, portfolio.into())
PortfolioService::add_portfolio_zip_to_cache(candidate.application, portfolio.into()).await; .await
.map_err(to_custom_error)?;
if candidate.is_err() {
// TODO cleanup
let e = candidate.err().unwrap();
return Err(Custom(
Status::from_code(e.code()).unwrap_or_default(),
e.to_string(),
));
}
Ok("Portfolio added".to_string()) Ok("Portfolio added".to_string())
} }
@ -259,19 +206,15 @@ pub async fn submit_portfolio(
// TODO: Více kontrol? // TODO: Více kontrol?
if e.code() == 500 { if e.code() == 500 {
// Cleanup // Cleanup
PortfolioService::delete_portfolio(candidate.application) PortfolioService::delete_portfolio(candidate.application).await.unwrap();
.await
.unwrap();
} }
return Err(Custom( return Err(to_custom_error(e));
Status::from_code(e.code()).unwrap_or_default(),
e.to_string(),
));
} }
Ok("Portfolio submitted".to_string()) Ok("Portfolio submitted".to_string())
} }
#[deprecated = "Use /submission_progress instead"]
#[get("/is_prepared")] #[get("/is_prepared")]
pub async fn is_portfolio_prepared(session: CandidateAuth) -> Result<String, Custom<String>> { pub async fn is_portfolio_prepared(session: CandidateAuth) -> Result<String, Custom<String>> {
let candidate: entity::candidate::Model = session.into(); let candidate: entity::candidate::Model = session.into();
@ -289,6 +232,7 @@ pub async fn is_portfolio_prepared(session: CandidateAuth) -> Result<String, Cus
Ok("Portfolio ok".to_string()) Ok("Portfolio ok".to_string())
} }
#[deprecated = "Use /submission_progress instead"]
#[get("/is_submitted")] #[get("/is_submitted")]
pub async fn is_portfolio_submitted(session: CandidateAuth) -> Result<String, Custom<String>> { pub async fn is_portfolio_submitted(session: CandidateAuth) -> Result<String, Custom<String>> {
let candidate: entity::candidate::Model = session.into(); let candidate: entity::candidate::Model = session.into();
@ -311,17 +255,11 @@ pub async fn download_portfolio(session: CandidateAuth) -> Result<Vec<u8>, Custo
let private_key = session.get_private_key(); let private_key = session.get_private_key();
let candidate: entity::candidate::Model = session.into(); let candidate: entity::candidate::Model = session.into();
let file = PortfolioService::get_portfolio(candidate.application, private_key).await; let file = PortfolioService::get_portfolio(candidate.application, private_key)
.await
.map_err(to_custom_error);
if file.is_err() { file
let e = file.err().unwrap();
return Err(Custom(
Status::from_code(e.code()).unwrap_or_default(),
e.to_string(),
));
}
Ok(file.unwrap())
} }
#[cfg(test)] #[cfg(test)]

View file

@ -1,2 +1,12 @@
use portfolio_core::error::ServiceError;
use rocket::{response::status::Custom, http::Status};
pub mod admin; pub mod admin;
pub mod candidate; pub mod candidate;
pub fn to_custom_error(e: ServiceError) -> Custom<String> {
Custom(
Status::from_code(e.code()).unwrap_or_default(),
e.to_string()
)
}

View file

@ -11,7 +11,8 @@ pub const NAIVE_DATE_FMT: &str = "%Y-%m-%d";
pub struct EncryptedString(String); pub struct EncryptedString(String);
impl EncryptedString { impl EncryptedString {
pub async fn new(s: &str, recipients: &Vec<&str>) -> Result<Self, ServiceError> { pub async fn new(s: &str, recipients: &Vec<String>) -> Result<Self, ServiceError> {
let recipients = recipients.iter().map(|s| &**s).collect();
match crypto::encrypt_password_with_recipients(&s, &recipients).await { match crypto::encrypt_password_with_recipients(&s, &recipients).await {
Ok(encrypted) => Ok(Self(encrypted)), Ok(encrypted) => Ok(Self(encrypted)),
Err(_) => Err(ServiceError::CryptoEncryptFailed), Err(_) => Err(ServiceError::CryptoEncryptFailed),
@ -89,7 +90,7 @@ pub struct EncryptedApplicationDetails {
impl EncryptedApplicationDetails { impl EncryptedApplicationDetails {
pub async fn new( pub async fn new(
form: &ApplicationDetails, form: &ApplicationDetails,
recipients: Vec<&str>, recipients: Vec<String>,
) -> Result<EncryptedApplicationDetails, ServiceError> { ) -> Result<EncryptedApplicationDetails, ServiceError> {
let birthdate_str = form.birthdate.format(NAIVE_DATE_FMT).to_string(); let birthdate_str = form.birthdate.format(NAIVE_DATE_FMT).to_string();
let d = tokio::try_join!( let d = tokio::try_join!(
@ -250,13 +251,15 @@ pub struct ApplicationDetails {
pub mod tests { pub mod tests {
use std::sync::Mutex; use std::sync::Mutex;
use chrono::NaiveDate;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use crate::crypto; use crate::crypto;
use super::{ApplicationDetails, EncryptedApplicationDetails, EncryptedString}; use super::{ApplicationDetails, EncryptedApplicationDetails, EncryptedString};
const PUBLIC_KEY: &str = "age1u889gp407hsz309wn09kxx9anl6uns30m27lfwnctfyq9tq4qpus8tzmq5";
const PRIVATE_KEY: &str = "AGE-SECRET-KEY-14QG24502DMUUQDT2SPMX2YXPSES0X8UD6NT0PCTDAT6RH8V5Q3GQGSRXPS";
pub static APPLICATION_DETAILS: Lazy<Mutex<ApplicationDetails>> = Lazy::new(|| pub static APPLICATION_DETAILS: Lazy<Mutex<ApplicationDetails>> = Lazy::new(||
Mutex::new(ApplicationDetails { Mutex::new(ApplicationDetails {
name: "name".to_string(), name: "name".to_string(),
@ -297,12 +300,9 @@ pub mod tests {
#[tokio::test] #[tokio::test]
async fn test_encrypted_application_details_new() { async fn test_encrypted_application_details_new() {
const PUBLIC_KEY: &str = "age1u889gp407hsz309wn09kxx9anl6uns30m27lfwnctfyq9tq4qpus8tzmq5";
const PRIVATE_KEY: &str =
"AGE-SECRET-KEY-14QG24502DMUUQDT2SPMX2YXPSES0X8UD6NT0PCTDAT6RH8V5Q3GQGSRXPS";
let encrypted_details = EncryptedApplicationDetails::new( let encrypted_details = EncryptedApplicationDetails::new(
&APPLICATION_DETAILS.lock().unwrap().clone(), &APPLICATION_DETAILS.lock().unwrap().clone(),
vec![PUBLIC_KEY], vec![PUBLIC_KEY.to_string()],
) )
.await .await
.unwrap(); .unwrap();
@ -329,13 +329,9 @@ pub mod tests {
#[tokio::test] #[tokio::test]
async fn test_encrypted_application_details_decrypt() { async fn test_encrypted_application_details_decrypt() {
const PUBLIC_KEY: &str = "age1u889gp407hsz309wn09kxx9anl6uns30m27lfwnctfyq9tq4qpus8tzmq5";
const PRIVATE_KEY: &str =
"AGE-SECRET-KEY-14QG24502DMUUQDT2SPMX2YXPSES0X8UD6NT0PCTDAT6RH8V5Q3GQGSRXPS";
let encrypted_details = EncryptedApplicationDetails::new( let encrypted_details = EncryptedApplicationDetails::new(
&APPLICATION_DETAILS.lock().unwrap().clone(), &APPLICATION_DETAILS.lock().unwrap().clone(),
vec![PUBLIC_KEY], vec![PUBLIC_KEY.to_string()],
) )
.await .await
.unwrap(); .unwrap();
@ -377,7 +373,7 @@ pub mod tests {
const PRIVATE_KEY: &str = const PRIVATE_KEY: &str =
"AGE-SECRET-KEY-14QG24502DMUUQDT2SPMX2YXPSES0X8UD6NT0PCTDAT6RH8V5Q3GQGSRXPS"; "AGE-SECRET-KEY-14QG24502DMUUQDT2SPMX2YXPSES0X8UD6NT0PCTDAT6RH8V5Q3GQGSRXPS";
let encrypted = EncryptedString::new("test", &vec![PUBLIC_KEY]) let encrypted = EncryptedString::new("test", &vec![PUBLIC_KEY.to_string()])
.await .await
.unwrap(); .unwrap();
@ -395,7 +391,7 @@ pub mod tests {
const PRIVATE_KEY: &str = const PRIVATE_KEY: &str =
"AGE-SECRET-KEY-14QG24502DMUUQDT2SPMX2YXPSES0X8UD6NT0PCTDAT6RH8V5Q3GQGSRXPS"; "AGE-SECRET-KEY-14QG24502DMUUQDT2SPMX2YXPSES0X8UD6NT0PCTDAT6RH8V5Q3GQGSRXPS";
let encrypted = EncryptedString::new("test", &vec![PUBLIC_KEY]) let encrypted = EncryptedString::new("test", &vec![PUBLIC_KEY.to_string()])
.await .await
.unwrap(); .unwrap();

View file

@ -114,7 +114,7 @@ mod tests {
let encrypted_details: EncryptedApplicationDetails = EncryptedApplicationDetails::new( let encrypted_details: EncryptedApplicationDetails = EncryptedApplicationDetails::new(
&APPLICATION_DETAILS.lock().unwrap().clone(), &APPLICATION_DETAILS.lock().unwrap().clone(),
vec!["age1u889gp407hsz309wn09kxx9anl6uns30m27lfwnctfyq9tq4qpus8tzmq5"], vec!["age1u889gp407hsz309wn09kxx9anl6uns30m27lfwnctfyq9tq4qpus8tzmq5".to_string()],
).await.unwrap(); ).await.unwrap();
Mutation::add_candidate_details(&db, candidate, encrypted_details).await.unwrap(); Mutation::add_candidate_details(&db, candidate, encrypted_details).await.unwrap();

View file

@ -83,7 +83,7 @@ mod tests {
let encrypted_details: EncryptedApplicationDetails = EncryptedApplicationDetails::new( let encrypted_details: EncryptedApplicationDetails = EncryptedApplicationDetails::new(
&APPLICATION_DETAILS.lock().unwrap().clone(), &APPLICATION_DETAILS.lock().unwrap().clone(),
vec!["age1u889gp407hsz309wn09kxx9anl6uns30m27lfwnctfyq9tq4qpus8tzmq5"], vec!["age1u889gp407hsz309wn09kxx9anl6uns30m27lfwnctfyq9tq4qpus8tzmq5".to_string()],
) )
.await .await
.unwrap(); .unwrap();

View file

@ -1,7 +1,7 @@
use entity::{candidate, parent}; use entity::{candidate, parent};
use sea_orm::DbConn; use sea_orm::DbConn;
use crate::{error::ServiceError, candidate_details::{ApplicationDetails, EncryptedApplicationDetails}, Query}; use crate::{error::ServiceError, candidate_details::{ApplicationDetails, EncryptedApplicationDetails}, Query, util::get_recipients};
use super::{parent_service::ParentService, candidate_service::CandidateService}; use super::{parent_service::ParentService, candidate_service::CandidateService};
@ -41,13 +41,7 @@ impl ApplicationService {
.await? .await?
.ok_or(ServiceError::ParentNotFound)?; .ok_or(ServiceError::ParentNotFound)?;
let admin_public_keys = Query::get_all_admin_public_keys(db).await?; let recipients = get_recipients(db, &candidate.public_key).await?;
let mut admin_public_keys_refrence: Vec<&str> =
admin_public_keys.iter().map(|s| &**s).collect();
let mut recipients = vec![&*candidate.public_key];
recipients.append(&mut admin_public_keys_refrence);
let enc_details = EncryptedApplicationDetails::new(form, recipients).await?; let enc_details = EncryptedApplicationDetails::new(form, recipients).await?;

View file

@ -7,7 +7,7 @@ use crate::{
candidate_details::{EncryptedApplicationDetails, EncryptedString}, candidate_details::{EncryptedApplicationDetails, EncryptedString},
crypto::{self, hash_password}, crypto::{self, hash_password},
error::ServiceError, error::ServiceError,
Mutation, Query, responses::{BaseCandidateResponse, CreateCandidateResponse}, Mutation, Query, responses::{BaseCandidateResponse, CreateCandidateResponse}, util::get_recipients,
}; };
use super::{session_service::{AdminUser, SessionService}, application_service::ApplicationService}; use super::{session_service::{AdminUser, SessionService}, application_service::ApplicationService};
@ -69,8 +69,7 @@ impl CandidateService {
// Check if user with that application id already exists // Check if user with that application id already exists
if Query::find_candidate_by_id(db, application_id) if Query::find_candidate_by_id(db, application_id)
.await .await?
.unwrap()
.is_some() .is_some()
{ {
return Err(ServiceError::UserAlreadyExists); return Err(ServiceError::UserAlreadyExists);
@ -85,10 +84,7 @@ impl CandidateService {
crypto::encrypt_password(priv_key_plain_text, plain_text_password.to_string()).await?; crypto::encrypt_password(priv_key_plain_text, plain_text_password.to_string()).await?;
let admin_public_keys = Query::get_all_admin_public_keys(db).await?; let recipients = get_recipients(db, &pubkey).await?;
let mut admin_public_keys_ref = admin_public_keys.iter().map(|s| &**s).collect();
let mut recipients = vec![&*pubkey];
recipients.append(&mut admin_public_keys_ref);
let enc_personal_id_number = EncryptedString::new( let enc_personal_id_number = EncryptedString::new(
&personal_id_number, &personal_id_number,
@ -237,14 +233,10 @@ impl CandidateService {
let session_id = let session_id =
SessionService::new_session(db, Some(candidate_id), None, password.clone(), ip_addr) SessionService::new_session(db, Some(candidate_id), None, password.clone(), ip_addr)
.await; .await?;
match session_id {
Ok(session_id) => { let private_key = Self::decrypt_private_key(candidate, password).await?;
let private_key = Self::decrypt_private_key(candidate, password).await?; Ok((session_id, private_key))
Ok((session_id, private_key))
}
Err(e) => Err(e),
}
} }
pub async fn auth(db: &DbConn, session_uuid: Uuid) -> Result<candidate::Model, ServiceError> { pub async fn auth(db: &DbConn, session_uuid: Uuid) -> Result<candidate::Model, ServiceError> {

View file

@ -2,6 +2,16 @@ use entity::{admin, candidate, parent, session};
use sea_orm::{Schema, Database, DbConn}; use sea_orm::{Schema, Database, DbConn};
use sea_orm::{sea_query::TableCreateStatement, ConnectionTrait, DbBackend}; use sea_orm::{sea_query::TableCreateStatement, ConnectionTrait, DbBackend};
use crate::Query;
use crate::error::ServiceError;
pub async fn get_recipients(db: &DbConn, candidate_pubkey: &str) -> Result<Vec<String>, ServiceError> {
let mut admin_public_keys = Query::get_all_admin_public_keys(db).await?;
let mut recipients = vec![candidate_pubkey.to_string()];
recipients.append(&mut admin_public_keys);
Ok(recipients)
}
pub async fn get_memory_sqlite_connection() -> sea_orm::DbConn { pub async fn get_memory_sqlite_connection() -> sea_orm::DbConn {
let base_url = "sqlite::memory:"; let base_url = "sqlite::memory:";