mirror of
https://github.com/danbulant/Portfolio
synced 2026-06-24 17:11:49 +00:00
Merge pull request #31 from EETagent/refactor_admin
Massive admin & candidate sessions rework, put admin back to own table
This commit is contained in:
commit
56e040b4a0
16 changed files with 356 additions and 134 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
use entity::candidate::Model as Admin;
|
use entity::admin::Model as Admin;
|
||||||
use portfolio_core::sea_orm::prelude::Uuid;
|
use portfolio_core::sea_orm::prelude::Uuid;
|
||||||
use portfolio_core::services::admin_service::AdminService;
|
use portfolio_core::services::admin_service::AdminService;
|
||||||
use rocket::http::Status;
|
use rocket::http::Status;
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,15 @@ use crate::Mutation;
|
||||||
impl Mutation {
|
impl Mutation {
|
||||||
pub async fn insert_session(
|
pub async fn insert_session(
|
||||||
db: &DbConn,
|
db: &DbConn,
|
||||||
user_id: i32,
|
user_id: Option<i32>,
|
||||||
|
admin_id: Option<i32>,
|
||||||
random_uuid: Uuid,
|
random_uuid: Uuid,
|
||||||
ip_addr: String,
|
ip_addr: String,
|
||||||
) -> Result<session::Model, DbErr> {
|
) -> Result<session::Model, DbErr> {
|
||||||
session::ActiveModel {
|
session::ActiveModel {
|
||||||
id: Set(random_uuid),
|
id: Set(random_uuid),
|
||||||
user_id: Set(user_id),
|
user_id: Set(user_id),
|
||||||
|
admin_id: Set(admin_id),
|
||||||
ip_address: Set(ip_addr),
|
ip_address: Set(ip_addr),
|
||||||
created_at: Set(Utc::now().naive_local()),
|
created_at: Set(Utc::now().naive_local()),
|
||||||
expires_at: Set(Utc::now()
|
expires_at: Set(Utc::now()
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
use crate::Query;
|
use crate::Query;
|
||||||
|
|
||||||
use ::entity::{candidate, candidate::Entity as Admin};
|
use ::entity::{admin, admin::Entity as Admin};
|
||||||
use sea_orm::*;
|
use sea_orm::*;
|
||||||
|
|
||||||
impl Query {
|
impl Query {
|
||||||
pub async fn find_admin_by_id(db: &DbConn, id: i32) -> Result<Option<candidate::Model>, DbErr> {
|
pub async fn find_admin_by_id(db: &DbConn, id: i32) -> Result<Option<admin::Model>, DbErr> {
|
||||||
Admin::find_by_id(id).one(db).await
|
Admin::find_by_id(id).one(db).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,26 @@
|
||||||
use crate::Query;
|
use crate::Query;
|
||||||
|
|
||||||
use ::entity::{session, session::Entity as Session};
|
use ::entity::{session, session::Entity as Session};
|
||||||
use sea_orm::*;
|
|
||||||
use sea_orm::prelude::Uuid;
|
use sea_orm::prelude::Uuid;
|
||||||
|
use sea_orm::*;
|
||||||
|
|
||||||
impl Query {
|
impl Query {
|
||||||
pub async fn find_session_by_uuid(db: &DbConn, uuid: Uuid) -> Result<Option<session::Model>, DbErr> {
|
pub async fn find_session_by_uuid(
|
||||||
|
db: &DbConn,
|
||||||
|
uuid: Uuid,
|
||||||
|
) -> Result<Option<session::Model>, DbErr> {
|
||||||
Session::find_by_id(uuid).one(db).await
|
Session::find_by_id(uuid).one(db).await
|
||||||
}
|
}
|
||||||
|
|
||||||
// find session by user id
|
// find session by user id
|
||||||
pub async fn find_sessions_by_user_id(db: &DbConn, user_id: i32) -> Result<Vec<session::Model>, DbErr> {
|
pub async fn find_sessions_by_user_id(
|
||||||
|
db: &DbConn,
|
||||||
|
user_id: Option<i32>,
|
||||||
|
admin_id: Option<i32>,
|
||||||
|
) -> Result<Vec<session::Model>, DbErr> {
|
||||||
Session::find()
|
Session::find()
|
||||||
.filter(session::Column::UserId.eq(user_id))
|
.filter(session::Column::UserId.eq(user_id))
|
||||||
|
.filter(session::Column::AdminId.eq(admin_id))
|
||||||
.all(db)
|
.all(db)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
@ -20,18 +28,20 @@ impl Query {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use sea_orm::DbConn;
|
|
||||||
use entity::candidate;
|
use entity::candidate;
|
||||||
use sea_orm::{Schema, Database, DbBackend, sea_query::TableCreateStatement, ConnectionTrait};
|
use sea_orm::DbConn;
|
||||||
|
use sea_orm::{sea_query::TableCreateStatement, ConnectionTrait, Database, DbBackend, Schema};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
async fn get_memory_sqlite_connection() -> DbConn {
|
async fn get_memory_sqlite_connection() -> DbConn {
|
||||||
let base_url = "sqlite::memory:";
|
let base_url = "sqlite::memory:";
|
||||||
let db: DbConn = Database::connect(base_url).await.unwrap();
|
let db: DbConn = Database::connect(base_url).await.unwrap();
|
||||||
|
|
||||||
let schema = Schema::new(DbBackend::Sqlite);
|
let schema = Schema::new(DbBackend::Sqlite);
|
||||||
let stmt: TableCreateStatement = schema.create_table_from_entity(candidate::Entity);
|
let stmt: TableCreateStatement = schema.create_table_from_entity(candidate::Entity);
|
||||||
db.execute(db.get_database_backend().build(&stmt)).await.unwrap();
|
db.execute(db.get_database_backend().build(&stmt))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
db
|
db
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,29 @@
|
||||||
use entity::candidate;
|
use entity::admin;
|
||||||
use sea_orm::{DbConn, prelude::Uuid};
|
use sea_orm::{prelude::Uuid, DbConn};
|
||||||
|
|
||||||
use crate::error::ServiceError;
|
use crate::error::ServiceError;
|
||||||
|
|
||||||
use super::session_service::SessionService;
|
use super::session_service::{SessionService, AdminUser};
|
||||||
|
|
||||||
pub struct AdminService;
|
pub struct AdminService;
|
||||||
|
|
||||||
impl AdminService {
|
impl AdminService {
|
||||||
pub async fn login(
|
pub async fn login(
|
||||||
db: &DbConn,
|
db: &DbConn,
|
||||||
user_id: i32,
|
admin_id: i32,
|
||||||
password: String,
|
password: String,
|
||||||
ip_addr: String
|
ip_addr: String,
|
||||||
) -> Result<String, ServiceError> {
|
) -> Result<String, ServiceError> {
|
||||||
SessionService::new_session(db, user_id, password, ip_addr).await
|
SessionService::new_session(db, None, Some(admin_id), password, ip_addr).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn auth(
|
pub async fn auth(db: &DbConn, session_uuid: Uuid) -> Result<admin::Model, ServiceError> {
|
||||||
db: &DbConn,
|
|
||||||
session_uuid: Uuid,
|
|
||||||
) -> Result<candidate::Model, ServiceError> {
|
|
||||||
match SessionService::auth_user_session(db, session_uuid).await {
|
match SessionService::auth_user_session(db, session_uuid).await {
|
||||||
Ok(user) => {
|
Ok(user) => match user {
|
||||||
if user.is_admin {
|
AdminUser::Admin(admin) => Ok(admin),
|
||||||
Ok(user)
|
AdminUser::User(_) => Err(ServiceError::DbError),
|
||||||
} else {
|
|
||||||
Err(ServiceError::Forbidden)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Err(e) => Err(e)
|
Err(e) => Err(e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ use crate::{
|
||||||
Mutation, Query,
|
Mutation, Query,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::session_service::SessionService;
|
use super::session_service::{AdminUser, SessionService};
|
||||||
|
|
||||||
const FIELD_OF_STUDY_PREFIXES: [&str; 3] = ["101", "102", "103"];
|
const FIELD_OF_STUDY_PREFIXES: [&str; 3] = ["101", "102", "103"];
|
||||||
|
|
||||||
|
|
@ -150,11 +150,17 @@ impl CandidateService {
|
||||||
password: String,
|
password: String,
|
||||||
ip_addr: String,
|
ip_addr: String,
|
||||||
) -> Result<String, ServiceError> {
|
) -> Result<String, ServiceError> {
|
||||||
SessionService::new_session(db, user_id, password, ip_addr).await
|
SessionService::new_session(db, Some(user_id), None, password, ip_addr).await
|
||||||
}
|
}
|
||||||
|
|
||||||
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> {
|
||||||
SessionService::auth_user_session(db, session_uuid).await
|
match SessionService::auth_user_session(db, session_uuid).await {
|
||||||
|
Ok(user) => match user {
|
||||||
|
AdminUser::User(candidate) => Ok(candidate),
|
||||||
|
AdminUser::Admin(_) => Err(ServiceError::DbError),
|
||||||
|
},
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_application_id_valid(application_id: i32) -> bool {
|
fn is_application_id_valid(application_id: i32) -> bool {
|
||||||
|
|
@ -188,7 +194,7 @@ mod tests {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
async fn get_memory_sqlite_connection() -> DbConn {
|
async fn get_memory_sqlite_connection() -> DbConn {
|
||||||
use entity::candidate;
|
use entity::{admin, candidate};
|
||||||
use sea_orm::Schema;
|
use sea_orm::Schema;
|
||||||
use sea_orm::{sea_query::TableCreateStatement, ConnectionTrait, DbBackend};
|
use sea_orm::{sea_query::TableCreateStatement, ConnectionTrait, DbBackend};
|
||||||
|
|
||||||
|
|
@ -197,9 +203,15 @@ mod tests {
|
||||||
|
|
||||||
let schema = Schema::new(DbBackend::Sqlite);
|
let schema = Schema::new(DbBackend::Sqlite);
|
||||||
let stmt: TableCreateStatement = schema.create_table_from_entity(candidate::Entity);
|
let stmt: TableCreateStatement = schema.create_table_from_entity(candidate::Entity);
|
||||||
|
|
||||||
|
let stmt2: TableCreateStatement = schema.create_table_from_entity(admin::Entity);
|
||||||
|
|
||||||
db.execute(db.get_database_backend().build(&stmt))
|
db.execute(db.get_database_backend().build(&stmt))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
db.execute(db.get_database_backend().build(&stmt2))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
db
|
db
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,39 @@
|
||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
|
|
||||||
use entity::candidate;
|
use entity::{admin, candidate};
|
||||||
use sea_orm::{DatabaseConnection, prelude::Uuid, ModelTrait};
|
use sea_orm::{prelude::Uuid, DatabaseConnection, ModelTrait};
|
||||||
|
|
||||||
use crate::{crypto::{self}, Query, error::{ServiceError}, Mutation};
|
use crate::{
|
||||||
|
crypto::{self},
|
||||||
|
error::ServiceError,
|
||||||
|
Mutation, Query,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub enum AdminUser {
|
||||||
|
Admin(entity::admin::Model),
|
||||||
|
User(entity::candidate::Model),
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: generics
|
|
||||||
pub(in crate::services) struct SessionService;
|
pub(in crate::services) struct SessionService;
|
||||||
|
|
||||||
impl SessionService {
|
impl SessionService {
|
||||||
/// Delete n old sessions for user
|
/// Delete n old sessions for user
|
||||||
async fn delete_old_sessions(db: &DatabaseConnection, user_id: i32, keep_n_recent: usize) -> Result<(), ServiceError> {
|
async fn delete_old_sessions(
|
||||||
let mut sessions = Query::find_sessions_by_user_id(db, user_id).await.unwrap();
|
db: &DatabaseConnection,
|
||||||
|
user_id: Option<i32>,
|
||||||
sessions.sort_by_key(|s| s.created_at);
|
admin_id: Option<i32>,
|
||||||
|
keep_n_recent: usize,
|
||||||
|
) -> Result<(), ServiceError> {
|
||||||
|
let mut sessions = Query::find_sessions_by_user_id(db, user_id, admin_id)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
sessions.sort_by_key(|s| s.created_at);
|
||||||
for session in sessions.iter().take(sessions.len() - min(sessions.len(), keep_n_recent)) {
|
|
||||||
|
for session in sessions
|
||||||
|
.iter()
|
||||||
|
.take(sessions.len() - min(sessions.len(), keep_n_recent))
|
||||||
|
{
|
||||||
Mutation::delete_session(db, session.id).await.unwrap();
|
Mutation::delete_session(db, session.id).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -24,49 +41,92 @@ impl SessionService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Authenticate user by application id and password and generate a new session
|
/// Authenticate user by application id and password and generate a new session
|
||||||
pub async fn new_session(db: &DatabaseConnection, user_id: i32, password: String, ip_addr: String) -> Result<String, ServiceError> {
|
pub async fn new_session(
|
||||||
let candidate = match Query::find_candidate_by_id(db, user_id).await {
|
db: &DatabaseConnection,
|
||||||
Ok(candidate) => match candidate {
|
user_id: Option<i32>,
|
||||||
Some(candidate) => candidate,
|
admin_id: Option<i32>,
|
||||||
None => return Err(ServiceError::UserNotFound)
|
password: String,
|
||||||
},
|
ip_addr: String,
|
||||||
Err(_) => {return Err(ServiceError::DbError)}
|
) -> Result<String, ServiceError> {
|
||||||
};
|
if user_id.is_none() && admin_id.is_none() {
|
||||||
|
return Err(ServiceError::UserNotFoundBySessionId);
|
||||||
// compare passwords
|
}
|
||||||
match crypto::verify_password(password,candidate.code.clone()).await {
|
|
||||||
Ok(valid) => {
|
if admin_id.is_none() {
|
||||||
if !valid {
|
// unwrap is safe here
|
||||||
return Err(ServiceError::InvalidCredentials)
|
let candidate = match Query::find_candidate_by_id(db, user_id.unwrap()).await {
|
||||||
}
|
Ok(candidate) => match candidate {
|
||||||
},
|
Some(candidate) => candidate,
|
||||||
Err(_) => {return Err(ServiceError::InvalidCredentials)}
|
None => return Err(ServiceError::UserNotFound),
|
||||||
|
},
|
||||||
|
Err(_) => return Err(ServiceError::DbError),
|
||||||
|
};
|
||||||
|
|
||||||
|
// compare passwords
|
||||||
|
match crypto::verify_password(password.clone(), candidate.code.clone()).await {
|
||||||
|
Ok(valid) => {
|
||||||
|
if !valid {
|
||||||
|
return Err(ServiceError::InvalidCredentials);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => return Err(ServiceError::InvalidCredentials),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if user_id.is_none() {
|
||||||
|
// unwrap is safe here
|
||||||
|
let admin = match Query::find_admin_by_id(db, admin_id.unwrap()).await {
|
||||||
|
Ok(admin) => match admin {
|
||||||
|
Some(admin) => admin,
|
||||||
|
None => return Err(ServiceError::UserNotFound),
|
||||||
|
},
|
||||||
|
Err(_) => return Err(ServiceError::DbError),
|
||||||
|
};
|
||||||
|
|
||||||
|
// compare passwords
|
||||||
|
match crypto::verify_password(password.clone(), admin.password.clone()).await {
|
||||||
|
Ok(valid) => {
|
||||||
|
if !valid {
|
||||||
|
return Err(ServiceError::InvalidCredentials);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => return Err(ServiceError::InvalidCredentials),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// user is authenticated, generate a new session
|
// user is authenticated, generate a new session
|
||||||
let random_uuid: Uuid = Uuid::new_v4();
|
let random_uuid: Uuid = Uuid::new_v4();
|
||||||
|
|
||||||
let session = match Mutation::insert_session(db, user_id, random_uuid, ip_addr).await {
|
let session =
|
||||||
Ok(session) => session,
|
match Mutation::insert_session(db, user_id, admin_id, random_uuid, ip_addr).await {
|
||||||
Err(_) => return Err(ServiceError::DbError)
|
Ok(session) => session,
|
||||||
};
|
Err(e) => {
|
||||||
|
eprintln!("Error creating session: {}", e);
|
||||||
|
return Err(ServiceError::DbError);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// delete old sessions
|
// delete old sessions
|
||||||
SessionService::delete_old_sessions(db, candidate.application, 3).await.ok(); // TODO move to dotenv
|
SessionService::delete_old_sessions(db, user_id, admin_id, 3)
|
||||||
|
.await
|
||||||
|
.ok(); // TODO move to dotenv
|
||||||
|
|
||||||
Ok(session.id.to_string())
|
Ok(session.id.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Authenticate user by session id
|
/// Authenticate user by session id
|
||||||
/// Return user model if session is valid
|
/// Return user model if session is valid
|
||||||
pub async fn auth_user_session(db: &DatabaseConnection, uuid: Uuid) -> Result<candidate::Model, ServiceError> {
|
|
||||||
|
pub async fn auth_user_session(
|
||||||
|
db: &DatabaseConnection,
|
||||||
|
uuid: Uuid,
|
||||||
|
) -> Result<AdminUser, ServiceError> {
|
||||||
let session = match Query::find_session_by_uuid(db, uuid).await {
|
let session = match Query::find_session_by_uuid(db, uuid).await {
|
||||||
Ok(session) => match session {
|
Ok(session) => match session {
|
||||||
Some(session) => session,
|
Some(session) => session,
|
||||||
None => return Err(ServiceError::UserNotFoundBySessionId)
|
None => return Err(ServiceError::UserNotFoundBySessionId),
|
||||||
},
|
},
|
||||||
Err(_) => {return Err(ServiceError::DbError)}
|
Err(_) => return Err(ServiceError::DbError),
|
||||||
};
|
};
|
||||||
|
|
||||||
let now = chrono::Utc::now().naive_utc();
|
let now = chrono::Utc::now().naive_utc();
|
||||||
|
|
@ -74,90 +134,133 @@ impl SessionService {
|
||||||
if now > session.expires_at {
|
if now > session.expires_at {
|
||||||
// delete session
|
// delete session
|
||||||
Mutation::delete_session(db, session.id).await.unwrap();
|
Mutation::delete_session(db, session.id).await.unwrap();
|
||||||
return Err(ServiceError::ExpiredSession)
|
return Err(ServiceError::ExpiredSession);
|
||||||
}
|
}
|
||||||
|
|
||||||
let candidate = match session.find_related(candidate::Entity).one(db).await {
|
let candidate = session.find_related(candidate::Entity).one(db).await;
|
||||||
Ok(candidate) => match candidate {
|
let admin = session.find_related(admin::Entity).one(db).await;
|
||||||
Some(candidate) => candidate,
|
|
||||||
None => return Err(ServiceError::UserNotFoundBySessionId)
|
|
||||||
},
|
|
||||||
Err(_) => {return Err(ServiceError::DbError)}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(candidate)
|
if candidate.is_err() || admin.is_err() {
|
||||||
|
eprintln!("Kurva");
|
||||||
|
return Err(ServiceError::UserNotFoundBySessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if candidate.is_ok() {
|
||||||
|
if let Some(candidate) = candidate.unwrap() {
|
||||||
|
return Ok(AdminUser::User(candidate));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if admin.is_ok() {
|
||||||
|
if let Some(admin) = admin.unwrap() {
|
||||||
|
return Ok(AdminUser::Admin(admin));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Err(ServiceError::UserNotFoundBySessionId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use entity::{candidate};
|
use entity::{admin, candidate, session};
|
||||||
use sea_orm::{DbConn, Database, sea_query::TableCreateStatement, DbBackend, Schema, ConnectionTrait, prelude::Uuid};
|
|
||||||
|
|
||||||
use crate::{crypto, services::{session_service::SessionService, candidate_service::CandidateService}};
|
use sea_orm::{
|
||||||
|
prelude::Uuid, sea_query::TableCreateStatement, ConnectionTrait, Database, DbBackend,
|
||||||
|
DbConn, Schema,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
crypto,
|
||||||
|
services::{candidate_service::CandidateService, session_service::SessionService},
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
async fn get_memory_sqlite_connection() -> DbConn {
|
async fn get_memory_sqlite_connection() -> DbConn {
|
||||||
use entity::session;
|
|
||||||
|
|
||||||
let base_url = "sqlite::memory:";
|
let base_url = "sqlite::memory:";
|
||||||
let db: DbConn = Database::connect(base_url).await.unwrap();
|
let db: DbConn = Database::connect(base_url).await.unwrap();
|
||||||
|
|
||||||
let schema = Schema::new(DbBackend::Sqlite);
|
let schema = Schema::new(DbBackend::Sqlite);
|
||||||
let stmt: TableCreateStatement = schema.create_table_from_entity(candidate::Entity);
|
let stmt: TableCreateStatement = schema.create_table_from_entity(candidate::Entity);
|
||||||
let stmt2: TableCreateStatement = schema.create_table_from_entity(session::Entity);
|
let stmt2: TableCreateStatement = schema.create_table_from_entity(admin::Entity);
|
||||||
db.execute(db.get_database_backend().build(&stmt)).await.unwrap();
|
let stmt3: TableCreateStatement = schema.create_table_from_entity(session::Entity);
|
||||||
db.execute(db.get_database_backend().build(&stmt2)).await.unwrap();
|
db.execute(db.get_database_backend().build(&stmt))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
db.execute(db.get_database_backend().build(&stmt2))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
db.execute(db.get_database_backend().build(&stmt3))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
db
|
db
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_create_candidate() {
|
async fn test_create_candidate() {
|
||||||
const SECRET: &str = "Tajny_kod";
|
const SECRET: &str = "Tajny_kod";
|
||||||
|
|
||||||
let db = get_memory_sqlite_connection().await;
|
let db = get_memory_sqlite_connection().await;
|
||||||
|
|
||||||
let candidate = CandidateService::create(&db, 103151, &SECRET.to_string(), "".to_string()).await.ok().unwrap();
|
let candidate = CandidateService::create(&db, 103151, &SECRET.to_string(), "".to_string())
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(candidate.application, 103151);
|
assert_eq!(candidate.application, 103151);
|
||||||
assert_ne!(candidate.code, SECRET.to_string());
|
assert_ne!(candidate.code, SECRET.to_string());
|
||||||
assert!(crypto::verify_password(SECRET.to_string(), candidate.code).await.ok().unwrap());
|
assert!(crypto::verify_password(SECRET.to_string(), candidate.code)
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_candidate_session_correct_password() {
|
async fn test_candidate_session_correct_password() {
|
||||||
let db = &get_memory_sqlite_connection().await;
|
let db = &get_memory_sqlite_connection().await;
|
||||||
|
|
||||||
CandidateService::create(&db, 103151, &"Tajny_kod".to_string(), "".to_string()).await.ok().unwrap();
|
CandidateService::create(&db, 103151, &"Tajny_kod".to_string(), "".to_string())
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// correct password
|
// correct password
|
||||||
let session = SessionService::new_session(
|
let session = SessionService::new_session(
|
||||||
db,
|
db,
|
||||||
103151,
|
Some(103151),
|
||||||
"Tajny_kod".to_string(),
|
None,
|
||||||
"127.0.0.1".to_string(),
|
"Tajny_kod".to_string(),
|
||||||
)
|
"127.0.0.1".to_string(),
|
||||||
.await.ok().unwrap();
|
)
|
||||||
// println!("{}", session.err().unwrap().1);
|
.await
|
||||||
|
.ok()
|
||||||
|
.unwrap();
|
||||||
|
// println!("{}", session.err().unwrap().1);
|
||||||
assert!(
|
assert!(
|
||||||
SessionService::auth_user_session(db, Uuid::parse_str(&session).unwrap())
|
SessionService::auth_user_session(db, Uuid::parse_str(&session).unwrap())
|
||||||
.await
|
.await
|
||||||
.is_ok()
|
.is_ok()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_candidate_session_incorrect_password() {
|
async fn test_candidate_session_incorrect_password() {
|
||||||
let db = &get_memory_sqlite_connection().await;
|
let db = &get_memory_sqlite_connection().await;
|
||||||
|
|
||||||
let candidate_form = CandidateService::create(&db, 103151, &"Tajny_kod".to_string(), "".to_string()).await.ok().unwrap();
|
let candidate_form =
|
||||||
|
CandidateService::create(&db, 103151, &"Tajny_kod".to_string(), "".to_string())
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// incorrect password
|
// incorrect password
|
||||||
assert!(
|
assert!(SessionService::new_session(
|
||||||
SessionService::new_session(db, candidate_form.application, "Spatny_kod".to_string(), "127.0.0.1".to_string()).await.is_err()
|
db,
|
||||||
);
|
Some(candidate_form.application),
|
||||||
|
None,
|
||||||
|
"Spatny_kod".to_string(),
|
||||||
|
"127.0.0.1".to_string()
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.is_err());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,18 +3,27 @@ use sea_orm::entity::prelude::*;
|
||||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
|
||||||
#[sea_orm(table_name = "admin")]
|
#[sea_orm(table_name = "admin")]
|
||||||
pub struct Model {
|
pub struct Model {
|
||||||
#[sea_orm(primary_key)]
|
#[sea_orm(column_type = "Integer", primary_key)]
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub public_key: String,
|
pub public_key: String,
|
||||||
#[sea_orm(column_type = "Text")]
|
#[sea_orm(column_type = "Text")]
|
||||||
pub private_key_hash: String,
|
pub private_key: String,
|
||||||
pub password_hash: String,
|
pub password: String,
|
||||||
pub created_at: DateTime,
|
pub created_at: DateTime,
|
||||||
pub updated_at: DateTime,
|
pub updated_at: DateTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
pub enum Relation {}
|
pub enum Relation {
|
||||||
|
#[sea_orm(has_many = "super::session::Entity")]
|
||||||
|
Session,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::session::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::Session.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ActiveModelBehavior for ActiveModel {}
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use sea_orm::entity::prelude::*;
|
||||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
|
||||||
#[sea_orm(table_name = "candidate")]
|
#[sea_orm(table_name = "candidate")]
|
||||||
pub struct Model {
|
pub struct Model {
|
||||||
#[sea_orm(primary_key, auto_increment = false)]
|
#[sea_orm(column_type = "Integer", primary_key, auto_increment = false)]
|
||||||
pub application: i32,
|
pub application: i32,
|
||||||
pub code: String,
|
pub code: String,
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
|
|
@ -22,8 +22,6 @@ pub struct Model {
|
||||||
pub personal_identification_number_hash: String,
|
pub personal_identification_number_hash: String,
|
||||||
pub public_key: String,
|
pub public_key: String,
|
||||||
pub private_key: String,
|
pub private_key: String,
|
||||||
#[sea_orm(default_value = false)]
|
|
||||||
pub is_admin: bool,
|
|
||||||
pub created_at: DateTime,
|
pub created_at: DateTime,
|
||||||
pub updated_at: DateTime,
|
pub updated_at: DateTime,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,10 @@ use sea_orm::entity::prelude::*;
|
||||||
pub struct Model {
|
pub struct Model {
|
||||||
#[sea_orm(primary_key, auto_increment = false)]
|
#[sea_orm(primary_key, auto_increment = false)]
|
||||||
pub id: Uuid,
|
pub id: Uuid,
|
||||||
pub user_id: i32,
|
#[sea_orm(column_type = "Integer", nullable)]
|
||||||
|
pub admin_id: Option<i32>,
|
||||||
|
#[sea_orm(column_type = "Integer", nullable)]
|
||||||
|
pub user_id: Option<i32>,
|
||||||
pub ip_address: String,
|
pub ip_address: String,
|
||||||
pub created_at: DateTime,
|
pub created_at: DateTime,
|
||||||
pub expires_at: DateTime,
|
pub expires_at: DateTime,
|
||||||
|
|
@ -13,6 +16,14 @@ pub struct Model {
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
pub enum Relation {
|
pub enum Relation {
|
||||||
|
#[sea_orm(
|
||||||
|
belongs_to = "super::admin::Entity",
|
||||||
|
from = "Column::AdminId",
|
||||||
|
to = "super::admin::Column::Id",
|
||||||
|
on_update = "Cascade",
|
||||||
|
on_delete = "Cascade"
|
||||||
|
)]
|
||||||
|
Admin,
|
||||||
#[sea_orm(
|
#[sea_orm(
|
||||||
belongs_to = "super::candidate::Entity",
|
belongs_to = "super::candidate::Entity",
|
||||||
from = "Column::UserId",
|
from = "Column::UserId",
|
||||||
|
|
@ -29,4 +40,10 @@ impl Related<super::candidate::Entity> for Entity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Related<super::admin::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::Admin.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ActiveModelBehavior for ActiveModel {}
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
pub use sea_orm_migration::prelude::*;
|
pub use sea_orm_migration::prelude::*;
|
||||||
|
|
||||||
|
mod m20221024_111310_create_admin;
|
||||||
mod m20221024_121621_create_candidate;
|
mod m20221024_121621_create_candidate;
|
||||||
mod m20221024_124701_create_parent;
|
mod m20221024_124701_create_parent;
|
||||||
mod m20221024_134454_insert_sample_admin;
|
mod m20221024_134454_insert_sample_admin;
|
||||||
mod m20221025_154422_create_session;
|
mod m20221025_154422_create_session;
|
||||||
mod m20221027_194728_session_create_user_fk;
|
mod m20221027_194728_session_create_user_fk;
|
||||||
|
mod m20221028_194728_session_create_admin_fk;
|
||||||
mod m20221030_133428_parent_create_candidate_fk;
|
mod m20221030_133428_parent_create_candidate_fk;
|
||||||
pub struct Migrator;
|
pub struct Migrator;
|
||||||
|
|
||||||
|
|
@ -12,11 +14,13 @@ pub struct Migrator;
|
||||||
impl MigratorTrait for Migrator {
|
impl MigratorTrait for Migrator {
|
||||||
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
|
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
|
||||||
vec![
|
vec![
|
||||||
|
Box::new(m20221024_111310_create_admin::Migration),
|
||||||
Box::new(m20221024_121621_create_candidate::Migration),
|
Box::new(m20221024_121621_create_candidate::Migration),
|
||||||
Box::new(m20221024_124701_create_parent::Migration),
|
Box::new(m20221024_124701_create_parent::Migration),
|
||||||
Box::new(m20221024_134454_insert_sample_admin::Migration::default()),
|
Box::new(m20221024_134454_insert_sample_admin::Migration::default()),
|
||||||
Box::new(m20221025_154422_create_session::Migration),
|
Box::new(m20221025_154422_create_session::Migration),
|
||||||
Box::new(m20221027_194728_session_create_user_fk::Migration),
|
Box::new(m20221027_194728_session_create_user_fk::Migration),
|
||||||
|
Box::new(m20221028_194728_session_create_admin_fk::Migration),
|
||||||
Box::new(m20221030_133428_parent_create_candidate_fk::Migration),
|
Box::new(m20221030_133428_parent_create_candidate_fk::Migration),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
49
migration/src/m20221024_111310_create_admin.rs
Normal file
49
migration/src/m20221024_111310_create_admin.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
use sea_orm_migration::prelude::*;
|
||||||
|
|
||||||
|
#[derive(DeriveMigrationName)]
|
||||||
|
pub struct Migration;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl MigrationTrait for Migration {
|
||||||
|
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager
|
||||||
|
.create_table(
|
||||||
|
Table::create()
|
||||||
|
.table(Admin::Table)
|
||||||
|
.if_not_exists()
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(Admin::Id)
|
||||||
|
.integer()
|
||||||
|
.not_null()
|
||||||
|
.auto_increment()
|
||||||
|
.primary_key(),
|
||||||
|
)
|
||||||
|
.col(ColumnDef::new(Admin::Name).string().not_null())
|
||||||
|
.col(ColumnDef::new(Admin::PublicKey).string().not_null())
|
||||||
|
.col(ColumnDef::new(Admin::PrivateKey).text().not_null())
|
||||||
|
.col(ColumnDef::new(Admin::Password).string().not_null())
|
||||||
|
.col(ColumnDef::new(Admin::CreatedAt).date_time().not_null())
|
||||||
|
.col(ColumnDef::new(Admin::UpdatedAt).date_time().not_null())
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager
|
||||||
|
.drop_table(Table::drop().table(Admin::Table).to_owned())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Iden)]
|
||||||
|
pub enum Admin {
|
||||||
|
Table,
|
||||||
|
Id,
|
||||||
|
Name,
|
||||||
|
PublicKey,
|
||||||
|
PrivateKey,
|
||||||
|
Password,
|
||||||
|
CreatedAt,
|
||||||
|
UpdatedAt,
|
||||||
|
}
|
||||||
|
|
@ -34,7 +34,6 @@ impl MigrationTrait for Migration {
|
||||||
.col(ColumnDef::new(Candidate::PersonalIdentificationNumberHash).text().not_null())
|
.col(ColumnDef::new(Candidate::PersonalIdentificationNumberHash).text().not_null())
|
||||||
.col(ColumnDef::new(Candidate::PublicKey).string().not_null())
|
.col(ColumnDef::new(Candidate::PublicKey).string().not_null())
|
||||||
.col(ColumnDef::new(Candidate::PrivateKey).string().not_null())
|
.col(ColumnDef::new(Candidate::PrivateKey).string().not_null())
|
||||||
.col(ColumnDef::new(Candidate::IsAdmin).boolean().not_null().default(false))
|
|
||||||
.col(ColumnDef::new(Candidate::CreatedAt).date_time().not_null())
|
.col(ColumnDef::new(Candidate::CreatedAt).date_time().not_null())
|
||||||
.col(ColumnDef::new(Candidate::UpdatedAt).date_time().not_null())
|
.col(ColumnDef::new(Candidate::UpdatedAt).date_time().not_null())
|
||||||
.to_owned(),
|
.to_owned(),
|
||||||
|
|
@ -69,7 +68,6 @@ pub enum Candidate {
|
||||||
PersonalIdentificationNumberHash,
|
PersonalIdentificationNumberHash,
|
||||||
PublicKey,
|
PublicKey,
|
||||||
PrivateKey,
|
PrivateKey,
|
||||||
IsAdmin,
|
|
||||||
CreatedAt,
|
CreatedAt,
|
||||||
UpdatedAt,
|
UpdatedAt,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
use entity::{candidate};
|
use entity::admin;
|
||||||
use sea_orm_migration::{
|
use sea_orm_migration::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
sea_orm::{ActiveModelTrait, Set},
|
sea_orm::{ActiveModelTrait, Set},
|
||||||
|
|
@ -7,24 +7,22 @@ use sea_orm_migration::{
|
||||||
|
|
||||||
#[derive(DeriveMigrationName)]
|
#[derive(DeriveMigrationName)]
|
||||||
pub struct Migration {
|
pub struct Migration {
|
||||||
candidate: candidate::ActiveModel,
|
admin: admin::ActiveModel,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Migration {
|
impl Default for Migration {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
candidate: candidate::ActiveModel {
|
admin: admin::ActiveModel {
|
||||||
application: Set(1),
|
id: Set(1),
|
||||||
name: Set(Some("Admin".to_owned())),
|
name: Set("Admin".to_owned()),
|
||||||
public_key: Set("age1u889gp407hsz309wn09kxx9anl6uns30m27lfwnctfyq9tq4qpus8tzmq5".to_owned()),
|
public_key: Set("age1u889gp407hsz309wn09kxx9anl6uns30m27lfwnctfyq9tq4qpus8tzmq5".to_owned()),
|
||||||
// AGE-SECRET-KEY-14QG24502DMUUQDT2SPMX2YXPSES0X8UD6NT0PCTDAT6RH8V5Q3GQGSRXPS
|
// AGE-SECRET-KEY-14QG24502DMUUQDT2SPMX2YXPSES0X8UD6NT0PCTDAT6RH8V5Q3GQGSRXPS
|
||||||
private_key: Set("5KCEGk0ueWVGnu5Xo3rmpLoilcVZ2ZWmwIcdZEJ8rrBNW7jwzZU/XTcTXtk/xyy/zjF8s+YnuVpOklQvX3EC/Sn+ZwyPY3jokM2RNwnZZlnqdehOEV1SMm/Y".to_owned()),
|
private_key: Set("5KCEGk0ueWVGnu5Xo3rmpLoilcVZ2ZWmwIcdZEJ8rrBNW7jwzZU/XTcTXtk/xyy/zjF8s+YnuVpOklQvX3EC/Sn+ZwyPY3jokM2RNwnZZlnqdehOEV1SMm/Y".to_owned()),
|
||||||
// test
|
// test
|
||||||
code: Set("$argon2i$v=19$m=6000,t=3,p=10$WE9xCQmmWdBK82R4SEjoqA$TZSc6PuLd4aWK2x2WAb+Lm9sLySqjK3KLbNyqyQmzPQ".to_owned()),
|
password: Set("$argon2i$v=19$m=6000,t=3,p=10$WE9xCQmmWdBK82R4SEjoqA$TZSc6PuLd4aWK2x2WAb+Lm9sLySqjK3KLbNyqyQmzPQ".to_owned()),
|
||||||
personal_identification_number_hash: Set("ADMIN".to_owned()),
|
|
||||||
created_at: Set(Local::now().naive_local()),
|
created_at: Set(Local::now().naive_local()),
|
||||||
updated_at: Set(Local::now().naive_local()),
|
updated_at: Set(Local::now().naive_local()),
|
||||||
is_admin: Set(true),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -36,7 +34,7 @@ impl MigrationTrait for Migration {
|
||||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
let db = manager.get_connection();
|
let db = manager.get_connection();
|
||||||
|
|
||||||
self.candidate.to_owned().insert(db).await?;
|
self.admin.to_owned().insert(db).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -44,8 +42,8 @@ impl MigrationTrait for Migration {
|
||||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
let db = manager.get_connection();
|
let db = manager.get_connection();
|
||||||
|
|
||||||
self.candidate.to_owned().delete(db).await?;
|
self.admin.to_owned().delete(db).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -18,7 +18,8 @@ impl MigrationTrait for Migration {
|
||||||
.unique_key()
|
.unique_key()
|
||||||
.primary_key(),
|
.primary_key(),
|
||||||
)
|
)
|
||||||
.col(ColumnDef::new(Session::UserId).integer().not_null())
|
.col(ColumnDef::new(Session::UserId).integer())
|
||||||
|
.col(ColumnDef::new(Session::AdminId).integer())
|
||||||
.col(ColumnDef::new(Session::IpAddress).string().not_null())
|
.col(ColumnDef::new(Session::IpAddress).string().not_null())
|
||||||
.col(ColumnDef::new(Session::CreatedAt).date_time().not_null())
|
.col(ColumnDef::new(Session::CreatedAt).date_time().not_null())
|
||||||
.col(ColumnDef::new(Session::ExpiresAt).date_time().not_null())
|
.col(ColumnDef::new(Session::ExpiresAt).date_time().not_null())
|
||||||
|
|
@ -39,6 +40,7 @@ pub enum Session {
|
||||||
Table,
|
Table,
|
||||||
Id,
|
Id,
|
||||||
UserId,
|
UserId,
|
||||||
|
AdminId,
|
||||||
IpAddress,
|
IpAddress,
|
||||||
CreatedAt,
|
CreatedAt,
|
||||||
ExpiresAt,
|
ExpiresAt,
|
||||||
|
|
|
||||||
26
migration/src/m20221028_194728_session_create_admin_fk.rs
Normal file
26
migration/src/m20221028_194728_session_create_admin_fk.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
use sea_orm_migration::prelude::*;
|
||||||
|
|
||||||
|
use crate::{m20221025_154422_create_session::Session, m20221024_111310_create_admin::Admin};
|
||||||
|
|
||||||
|
#[derive(DeriveMigrationName)]
|
||||||
|
pub struct Migration;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl MigrationTrait for Migration {
|
||||||
|
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager.create_foreign_key(ForeignKey::create()
|
||||||
|
.name("admin_fk")
|
||||||
|
.from(Session::Table, Session::AdminId)
|
||||||
|
.to(Admin::Table, Admin::Id)
|
||||||
|
.on_delete(ForeignKeyAction::Cascade)
|
||||||
|
.on_update(ForeignKeyAction::Cascade)
|
||||||
|
.to_owned()).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager.drop_foreign_key(ForeignKey::drop()
|
||||||
|
.name("admin_fk")
|
||||||
|
.table(Session::Table)
|
||||||
|
.to_owned()).await
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue