feat!: admin session table

This commit is contained in:
Sebastian Pravda 2022-12-21 18:27:58 +01:00
parent 0b1c93c336
commit 996b45d62e
No known key found for this signature in database
GPG key ID: F3BC84F08EFA3F57
24 changed files with 399 additions and 229 deletions

View file

@ -58,8 +58,9 @@ pub async fn logout(conn: Connection<'_, Db>, _session: AdminAuth, cookies: &Coo
.ok_or(Custom(Status::Unauthorized, "No session cookie".to_string()))?;
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()))?;
let session = Query::find_admin_session_by_uuid(db, session_id).await.unwrap().unwrap();
let _res = AdminService::logout(db, session_id)
let _res = AdminService::logout(db, session)
.await
.map_err(to_custom_error)?;

View file

@ -1,5 +1,6 @@
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use portfolio_core::Query;
use portfolio_core::models::auth::AuthenticableTrait;
use portfolio_core::models::candidate::ApplicationDetails;
use portfolio_core::sea_orm::prelude::Uuid;
@ -59,8 +60,8 @@ pub async fn logout(
))?;
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()))?;
CandidateService::logout(db, session_id)
let session = Query::find_session_by_uuid(db, session_id).await.unwrap().unwrap(); // TODO
CandidateService::logout(db, session)
.await
.map_err(to_custom_error)?;

View file

@ -0,0 +1,49 @@
use chrono::{Utc, Duration};
use entity::{admin_session};
use sea_orm::{DbConn, prelude::Uuid, DbErr, Set, ActiveModelTrait};
use crate::Mutation;
impl Mutation {
pub async fn insert_admin_session(
db: &DbConn,
admin_id: i32,
random_uuid: Uuid,
ip_addr: String,
) -> Result<admin_session::Model, DbErr> {
admin_session::ActiveModel {
id: Set(random_uuid),
admin_id: Set(Some(admin_id)),
ip_address: Set(ip_addr),
created_at: Set(Utc::now().naive_local()),
expires_at: Set(Utc::now()
.naive_local()
.checked_add_signed(Duration::days(1))
.unwrap()),
updated_at: Set(Utc::now().naive_local())
}
.insert(db)
.await
}
/* pub async fn update_session_expiration(db: &DbConn,
session: session::Model,
expires_at: NaiveDateTime,
) -> Result<session::Model, DbErr> {
let mut session = session.into_active_model();
session.expires_at = Set(expires_at);
session.updated_at = Set(Utc::now().naive_local());
session.update(db).await
}
pub async fn delete_admin_session(db: &DbConn, session: ad) -> Result<DeleteResult, DbErr> {
session::ActiveModel {
id: Set(session_id),
..Default::default()
}
.delete(db)
.await
} */
}

View file

@ -2,4 +2,5 @@ pub(crate) struct Mutation;
pub mod session;
pub mod candidate;
pub mod parent;
pub mod parent;
pub mod admin_session;

View file

@ -1,22 +1,20 @@
use chrono::{Utc, Duration, NaiveDateTime};
use ::entity::session;
use ::entity::{session, candidate};
use sea_orm::{*, prelude::Uuid};
use crate::Mutation;
impl Mutation {
pub async fn insert_session(
pub async fn insert_candidate_session(
db: &DbConn,
user_id: Option<i32>,
admin_id: Option<i32>,
random_uuid: Uuid,
candidate_id: i32,
ip_addr: String,
) -> Result<session::Model, DbErr> {
session::ActiveModel {
id: Set(random_uuid),
user_id: Set(user_id),
admin_id: Set(admin_id),
candidate_id: Set(Some(candidate_id)),
ip_address: Set(ip_addr),
created_at: Set(Utc::now().naive_local()),
expires_at: Set(Utc::now()
@ -41,19 +39,18 @@ impl Mutation {
session.update(db).await
}
pub async fn delete_session(db: &DbConn, session_id: Uuid) -> Result<DeleteResult, DbErr> {
session::ActiveModel {
id: Set(session_id),
..Default::default()
}
.delete(db)
.await
pub async fn delete_session<T>(db: &DbConn, session: T) -> Result<DeleteResult, DbErr>
where T: ActiveModelTrait + std::marker::Send + ActiveModelBehavior
{
session
.delete(db)
.await
}
}
#[cfg(test)]
mod tests {
use sea_orm::prelude::Uuid;
/* use sea_orm::prelude::Uuid;
use crate::{utils::db::get_memory_sqlite_connection, Mutation, services::candidate_service::tests::put_user_data};
@ -62,14 +59,14 @@ mod tests {
let db = get_memory_sqlite_connection().await;
let session_id = Uuid::new_v4();
let (user, _) = put_user_data(&db).await;
let (candidate, _) = put_user_data(&db).await;
let session = Mutation::insert_session(&db, Some(user.application), None, session_id, "127.0.0.1".to_string()).await.unwrap();
let session = Mutation::insert_candidate_session(&db, session_id, candidate.application, "127.0.0.1".to_string()).await.unwrap();
assert_eq!(session.id, session_id);
let delete_result = Mutation::delete_session(&db, session_id).await.unwrap();
assert_eq!(delete_result.rows_affected, 1);
}
} */
}

View file

@ -1,5 +1,7 @@
use crate::Query;
use ::entity::prelude::AdminSession;
use ::entity::{candidate, admin, admin_session};
use ::entity::{session, session::Entity as Session};
use sea_orm::prelude::Uuid;
use sea_orm::*;
@ -12,8 +14,23 @@ impl Query {
Session::find_by_id(uuid).one(db).await
}
pub async fn find_admin_session_by_uuid(
db: &DbConn,
uuid: Uuid,
) -> Result<Option<admin_session::Model>, DbErr> {
AdminSession::find_by_id(uuid).one(db).await
}
pub async fn find_related_candidate_sessions(db: &DbConn, candidate: candidate::Model) -> Result<Vec<session::Model>, DbErr> {
candidate.find_related(Session).all(db).await
}
pub async fn find_related_admin_sessions(db: &DbConn, admin: admin::Model) -> Result<Vec<admin_session::Model>, DbErr> {
admin.find_related(admin_session::Entity).all(db).await
}
// find session by user id
pub async fn find_sessions_by_user_id(
/* pub async fn find_sessions_by_user_id(
db: &DbConn,
user_id: Option<i32>,
admin_id: Option<i32>,
@ -27,12 +44,12 @@ impl Query {
}
.all(db)
.await
}
} */
}
#[cfg(test)]
mod tests {
use entity::{session, admin, candidate};
use entity::{session, admin, candidate, admin_session};
use sea_orm::ActiveValue::NotSet;
use sea_orm::{prelude::Uuid, ActiveModelTrait, Set};
@ -65,7 +82,7 @@ mod tests {
const APPLICATION_ID: i32 = 103158;
candidate::ActiveModel {
let candidate = candidate::ActiveModel {
application: Set(APPLICATION_ID),
code: Set("test".to_string()),
public_key: Set("test".to_string()),
@ -81,8 +98,7 @@ mod tests {
session::ActiveModel {
id: Set(Uuid::new_v4()),
user_id: Set(Some(APPLICATION_ID)),
admin_id: NotSet,
candidate_id: Set(Some(APPLICATION_ID)),
ip_address: Set("10.10.10.10".to_string()),
created_at: Set(chrono::offset::Local::now().naive_local()),
expires_at: Set(chrono::offset::Local::now().naive_local()),
@ -95,7 +111,7 @@ mod tests {
const ADMIN_ID: i32 = 1;
admin::ActiveModel {
let admin = admin::ActiveModel {
id: Set(ADMIN_ID),
name: Set("admin".to_string()),
public_key: Set("test".to_string()),
@ -109,9 +125,8 @@ mod tests {
.await
.unwrap();
session::ActiveModel {
admin_session::ActiveModel {
id: Set(Uuid::new_v4()),
user_id: NotSet,
admin_id: Set(Some(ADMIN_ID)),
ip_address: Set("10.10.10.10".to_string()),
created_at: Set(chrono::offset::Local::now().naive_local()),
@ -123,10 +138,10 @@ mod tests {
.await
.unwrap();
let sessions = Query::find_sessions_by_user_id(&db, Some(APPLICATION_ID), None).await.unwrap();
let sessions = Query::find_related_candidate_sessions(&db, candidate).await.unwrap();
assert_eq!(sessions.len(), 1);
let sessions = Query::find_sessions_by_user_id(&db, None, Some(ADMIN_ID)).await.unwrap();
let sessions = Query::find_related_admin_sessions(&db, admin).await.unwrap();
assert_eq!(sessions.len(), 1);
}
}

View file

@ -1,4 +1,5 @@
use async_trait::async_trait;
use entity::session;
use sea_orm::{prelude::Uuid, DbConn};
use crate::error::ServiceError;
@ -7,8 +8,10 @@ use crate::error::ServiceError;
#[async_trait]
pub trait AuthenticableTrait {
type User;
// fn password_valid(user: T);
type Session;
async fn login(db: &DbConn, user: i32, password: String, ip_addr: String) -> Result<(String, String), ServiceError>;
async fn auth(db: &DbConn, session_id: Uuid) -> Result<Self::User, ServiceError>;
async fn logout(db: &DbConn, session_id: Uuid) -> Result<(), ServiceError>;
async fn logout(db: &DbConn, session: Self::Session) -> Result<(), ServiceError>;
async fn new_session(db: &DbConn, user: Self::User, ip_addr: String, password: String) -> Result<String, ServiceError>;
async fn delete_old_sessions(db: &DbConn, user: Self::User, keep_n_recent: usize) -> Result<(), ServiceError>;
}

View file

@ -1,10 +1,10 @@
use async_trait::async_trait;
use entity::admin;
use sea_orm::{prelude::Uuid, DbConn};
use entity::{admin, admin_session};
use sea_orm::{prelude::Uuid, DbConn, IntoActiveModel};
use crate::{crypto, error::ServiceError, Query, Mutation, models::auth::AuthenticableTrait};
use super::session_service::{AdminUser, SessionService};
use super::session_service::SessionService;
pub struct AdminService;
@ -25,6 +25,7 @@ impl AdminService {
#[async_trait]
impl AuthenticableTrait for AdminService {
type User = admin::Model;
type Session = admin_session::Model;
async fn login(
db: &DbConn,
@ -34,9 +35,8 @@ impl AuthenticableTrait for AdminService {
) -> Result<(String, String), ServiceError> {
let admin = Query::find_admin_by_id(db, admin_id).await?.ok_or(ServiceError::InvalidCredentials)?;
let session_id = SessionService::new_session(db,
None,
Some(admin_id),
let session_id = Self::new_session(db,
admin.clone(),
password.clone(),
ip_addr
)
@ -47,16 +47,64 @@ impl AuthenticableTrait for AdminService {
}
async fn auth(db: &DbConn, session_uuid: Uuid) -> Result<admin::Model, ServiceError> {
match SessionService::auth_user_session(db, session_uuid).await? {
AdminUser::Admin(admin) => Ok(admin),
AdminUser::Candidate(_) => Err(ServiceError::Unauthorized),
let session = Query::find_admin_session_by_uuid(db, session_uuid)
.await?
.ok_or(ServiceError::Unauthorized)?;
if !SessionService::is_valid(&session).await? {
Mutation::delete_session(db, session.into_active_model()).await?;
return Err(ServiceError::ExpiredSession);
}
let admin = Query::find_admin_by_id(db, session.admin_id.unwrap())
.await?
.ok_or(ServiceError::CandidateNotFound)?;
Ok(admin)
}
async fn logout(db: &DbConn, session_id: Uuid) -> Result<(), ServiceError> {
Mutation::delete_session(db, session_id).await?;
async fn logout(db: &DbConn, session: admin_session::Model) -> Result<(), ServiceError> {
Mutation::delete_session(db, session.into_active_model()).await?;
Ok(())
}
async fn new_session(
db: &DbConn,
admin: admin::Model,
password: String,
ip_addr: String,
) -> Result<String, ServiceError> {
if !crypto::verify_password(password.clone(), admin.password.clone()).await? {
return Err(ServiceError::InvalidCredentials);
}
// user is authenticated, generate a new session
let random_uuid: Uuid = Uuid::new_v4();
let session = Mutation::insert_admin_session(db, admin.id, random_uuid, ip_addr).await?;
Self::delete_old_sessions(db, admin, 1)
.await?;
Ok(session.id.to_string())
}
async fn delete_old_sessions(
db: &DbConn,
admin: admin::Model,
keep_n_recent: usize,
) -> Result<(), ServiceError> {
let mut sessions = Query::find_related_admin_sessions(db, admin)
.await?;
sessions.sort_by_key(|s| s.created_at);
let sessions = sessions.iter()
.map(|s| s.clone().into_active_model())
.collect::<Vec<admin_session::ActiveModel>>();
SessionService::delete_sessions(db, sessions, keep_n_recent).await?;
Ok(())
}
}
#[cfg(test)]

View file

@ -1,6 +1,7 @@
use async_trait::async_trait;
use entity::candidate;
use sea_orm::{prelude::Uuid, DbConn};
use chrono::Duration;
use entity::{candidate, session};
use sea_orm::{prelude::Uuid, DbConn, IntoActiveModel};
use crate::{
models::{candidate_details::{EncryptedApplicationDetails, EncryptedString, EncryptedCandidateDetails}, candidate::CandidateDetails},
@ -9,7 +10,7 @@ use crate::{
Mutation, Query, models::{candidate::{BaseCandidateResponse, CreateCandidateResponse}, auth::AuthenticableTrait}, utils::db::get_recipients,
};
use super::{session_service::{AdminUser, SessionService}, application_service::ApplicationService, portfolio_service::PortfolioService};
use super::{session_service::SessionService, application_service::ApplicationService, portfolio_service::PortfolioService};
// TODO validation
@ -115,7 +116,7 @@ impl CandidateService {
).await?;
SessionService::revoke_all_sessions(db, Some(id), None).await?;
Self::delete_old_sessions(db, candidate.clone(), 0).await?;
Mutation::update_candidate_password_and_keys(db, candidate.clone(), new_password_hash, pubkey, encrypted_priv_key).await?;
// user might no have filled his details yet, but personal id number is filled from beginning
@ -223,11 +224,21 @@ impl CandidateService {
let field_of_study_prefix = &s[0..3];
FIELD_OF_STUDY_PREFIXES.contains(&field_of_study_prefix)
}
pub async fn extend_session_duration_to_14_days(db: &DbConn, session: session::Model) -> Result<(), ServiceError> {
let now = chrono::Utc::now().naive_utc();
if now >= session.updated_at.checked_add_signed(Duration::days(1)).ok_or(ServiceError::Unauthorized)? {
let new_expires_at = now.checked_add_signed(Duration::days(14)).ok_or(ServiceError::Unauthorized)?;
Mutation::update_session_expiration(db, session, new_expires_at).await?;
}
Ok(())
}
}
#[async_trait]
impl AuthenticableTrait for CandidateService {
type User = candidate::Model;
type Session = session::Model;
async fn login(
db: &DbConn,
@ -239,7 +250,7 @@ impl AuthenticableTrait for CandidateService {
.await?
.ok_or(ServiceError::CandidateNotFound)?;
let session_id = SessionService::new_session(db, Some(application_id), None, password.clone(), ip_addr)
let session_id = Self::new_session(db, candidate.clone(), password.clone(), ip_addr)
.await?;
let private_key = Self::decrypt_private_key(candidate, password).await?;
@ -247,18 +258,68 @@ impl AuthenticableTrait for CandidateService {
}
async fn auth(db: &DbConn, session_uuid: Uuid) -> Result<candidate::Model, ServiceError> {
match SessionService::auth_user_session(db, session_uuid).await {
Ok(user) => match user {
AdminUser::Candidate(candidate) => Ok(candidate),
AdminUser::Admin(_) => Err(ServiceError::Unauthorized),
},
Err(e) => Err(e),
let session = Query::find_session_by_uuid(db, session_uuid)
.await?
.ok_or(ServiceError::Unauthorized)?;
if !SessionService::is_valid(&session).await? {
Mutation::delete_session(db, session.into_active_model()).await?;
return Err(ServiceError::ExpiredSession);
}
// Candidate authenticated
Self::extend_session_duration_to_14_days(db, session.clone()).await?;
let candidate = Query::find_candidate_by_id(db, session.candidate_id.unwrap())
.await?
.ok_or(ServiceError::CandidateNotFound)?;
Ok(candidate)
}
async fn logout(db: &DbConn, session_id: Uuid) -> Result<(), ServiceError> {
Mutation::delete_session(db, session_id).await?;
async fn logout(db: &DbConn, session: session::Model) -> Result<(), ServiceError> {
Mutation::delete_session(db, session.into_active_model()).await?;
Ok(())
}
async fn new_session(
db: &DbConn,
candidate: candidate::Model,
password: String,
ip_addr: String,
) -> Result<String, ServiceError> {
if !crypto::verify_password(password.clone(), candidate.code.clone()).await? {
return Err(ServiceError::InvalidCredentials);
}
// user is authenticated, generate a new session
let random_uuid: Uuid = Uuid::new_v4();
let session = Mutation::insert_candidate_session(db, random_uuid, candidate.application, ip_addr).await?;
Self::delete_old_sessions(db, candidate, 3)
.await
.ok();
Ok(session.id.to_string())
}
async fn delete_old_sessions(
db: &DbConn,
candidate: candidate::Model,
keep_n_recent: usize,
) -> Result<(), ServiceError> {
let mut sessions = Query::find_related_candidate_sessions(db, candidate)
.await?;
sessions.sort_by_key(|s| s.created_at);
let sessions = sessions.iter()
.map(|s| s.clone().into_active_model())
.collect::<Vec<session::ActiveModel>>();
SessionService::delete_sessions(db, sessions, keep_n_recent).await?;
Ok(())
}
}
#[cfg(test)]

View file

@ -1,13 +1,10 @@
use std::cmp::min;
use chrono::Duration;
use entity::{admin, candidate, session};
use sea_orm::{prelude::Uuid, ModelTrait, DbConn};
use entity::{session_trait::UserSession};
use sea_orm::{DbConn, ActiveModelTrait, ActiveModelBehavior};
use crate::{
crypto::{self},
error::ServiceError,
Mutation, Query,
Mutation,
};
pub enum AdminUser {
@ -18,134 +15,27 @@ pub enum AdminUser {
pub(in crate::services) struct SessionService;
impl SessionService {
/// Delete n old sessions for user
async fn delete_old_sessions(
db: &DbConn,
user_id: Option<i32>,
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))
{
Mutation::delete_session(db, session.id).await.unwrap();
}
Ok(())
}
/// Authenticate user by application id and password and generate a new session
pub async fn new_session(
db: &DbConn,
candidate_id: Option<i32>,
admin_id: Option<i32>,
password: String,
ip_addr: String,
) -> Result<String, ServiceError> {
if candidate_id.is_none() && admin_id.is_none() {
return Err(ServiceError::UserNotFoundBySessionId);
}
if let Some(candidate_id) = candidate_id {
let candidate = Query::find_candidate_by_id(db, candidate_id).await?
.ok_or(ServiceError::CandidateNotFound)?;
// compare passwords
if !crypto::verify_password(password.clone(), candidate.code.clone()).await? {
return Err(ServiceError::InvalidCredentials);
}
}
if let Some(admin_id) = admin_id {
let admin = Query::find_admin_by_id(db, admin_id).await?
.ok_or(ServiceError::InvalidCredentials)?;
// compare passwords
if !crypto::verify_password(password.clone(), admin.password.clone()).await? {
return Err(ServiceError::InvalidCredentials);
}
}
// user is authenticated, generate a new session
let random_uuid: Uuid = Uuid::new_v4();
let session = Mutation::insert_session(db, candidate_id, admin_id, random_uuid, ip_addr).await?;
SessionService::delete_old_sessions(db, candidate_id, admin_id, 3)
.await
.ok();
Ok(session.id.to_string())
}
pub async fn revoke_all_sessions(db: &DbConn, user_id: Option<i32>, admin_id: Option<i32>) -> Result<(), ServiceError> {
Self::delete_old_sessions(db, user_id, admin_id, 0).await
}
/// Check if session is valid
async fn is_valid(db: &DbConn, session: &session::Model) -> Result<bool, ServiceError> {
pub async fn is_valid<T>(session: &T) -> Result<bool, ServiceError> where T: UserSession {
let now = chrono::Utc::now().naive_utc();
if now >= session.expires_at {
Mutation::delete_session(db, session.id).await?;
if now >= session.expires_at().await {
Ok(false)
} else {
Ok(true)
}
}
/// If 1 day or more since last update, extend session duration to 14 days
async fn extend_session_duration_to_14_days(db: &DbConn, session: session::Model) -> Result<(), ServiceError> {
let now = chrono::Utc::now().naive_utc();
if now >= session.updated_at.checked_add_signed(Duration::days(1)).ok_or(ServiceError::Unauthorized)? {
let new_expires_at = now.checked_add_signed(Duration::days(14)).ok_or(ServiceError::Unauthorized)?;
Mutation::update_session_expiration(db, session, new_expires_at).await?;
/// Delete list of sessions
pub async fn delete_sessions<T>(db: &DbConn, sessions: Vec<T>, keep_n_recent: usize) -> Result<(), ServiceError> where T: ActiveModelTrait + std::marker::Send + ActiveModelBehavior {
for session in sessions
.iter()
.take(sessions.len() - min(sessions.len(), keep_n_recent))
{
Mutation::delete_session(db, session.clone()).await?;
}
Ok(())
}
/// Authenticate user by session id
/// Return user model if session is valid
pub async fn auth_user_session(
db: &DbConn,
uuid: Uuid,
) -> Result<AdminUser, ServiceError> {
let session = Query::find_session_by_uuid(db, uuid).await?
.ok_or(ServiceError::UserNotFoundBySessionId)?;
if !Self::is_valid(db, &session).await? {
return Err(ServiceError::ExpiredSession);
}
Self::extend_session_duration_to_14_days(db, session.clone()).await?;
let candidate = session.find_related(candidate::Entity).one(db).await;
let admin = session.find_related(admin::Entity).one(db).await;
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::Candidate(candidate));
}
}
if admin.is_ok() {
if let Some(admin) = admin.unwrap() {
return Ok(AdminUser::Admin(admin));
}
}
return Err(ServiceError::UserNotFoundBySessionId);
}
}
@ -157,8 +47,8 @@ mod tests {
use crate::{
crypto,
services::{application_service::ApplicationService, session_service::SessionService},
utils::db::get_memory_sqlite_connection,
services::{application_service::ApplicationService, candidate_service::CandidateService},
utils::db::get_memory_sqlite_connection, models::auth::AuthenticableTrait,
};
#[tokio::test]
@ -190,7 +80,7 @@ mod tests {
async fn test_candidate_session_correct_password() {
let db = &get_memory_sqlite_connection().await;
ApplicationService::create_candidate_with_parent(
let candidate = ApplicationService::create_candidate_with_parent(
db,
103151,
&"Tajny_kod".to_string(),
@ -201,10 +91,9 @@ mod tests {
.0;
// correct password
let session = SessionService::new_session(
let session = CandidateService::new_session(
db,
Some(103151),
None,
candidate,
"Tajny_kod".to_string(),
"127.0.0.1".to_string(),
)
@ -212,7 +101,7 @@ mod tests {
.unwrap();
// println!("{}", session.err().unwrap().1);
assert!(
SessionService::auth_user_session(db, Uuid::parse_str(&session).unwrap())
CandidateService::auth(db, Uuid::parse_str(&session).unwrap())
.await
.is_ok()
);
@ -233,10 +122,9 @@ mod tests {
.0;
// incorrect password
assert!(SessionService::new_session(
assert!(CandidateService::new_session(
db,
Some(candidate_form.application),
None,
candidate_form,
"Spatny_kod".to_string(),
"127.0.0.1".to_string()
)

View file

@ -10,6 +10,7 @@ path = "src/lib.rs"
[dependencies]
chrono = "^0.4"
async-trait = "0.1.60"
[dependencies.sea-orm]
version = "^0.10"

View file

@ -18,13 +18,13 @@ pub struct Model {
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(has_many = "super::session::Entity")]
Session,
#[sea_orm(has_many = "super::admin_session::Entity")]
AdminSession,
}
impl Related<super::session::Entity> for Entity {
impl Related<super::admin_session::Entity> for Entity {
fn to() -> RelationDef {
Relation::Session.def()
Relation::AdminSession.def()
}
}

View file

@ -0,0 +1,47 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.9.3
use sea_orm::entity::prelude::*;
use crate::session_trait::UserSession;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "admin_session")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id: Uuid,
pub admin_id: Option<i32>,
pub ip_address: String,
pub created_at: DateTime,
pub expires_at: DateTime,
pub updated_at: DateTime,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
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,
}
impl Related<super::admin::Entity> for Entity {
fn to() -> RelationDef {
Relation::Admin.def()
}
}
impl ActiveModelBehavior for ActiveModel {}
#[async_trait::async_trait]
impl UserSession for Model {
async fn id(&self) -> Uuid {
self.id
}
async fn expires_at(&self) -> chrono::NaiveDateTime {
self.expires_at
}
}

View file

@ -30,16 +30,10 @@ pub struct Model {
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(has_many = "super::parent::Entity")]
Parent,
#[sea_orm(has_many = "super::session::Entity")]
Session,
}
impl Related<super::parent::Entity> for Entity {
fn to() -> RelationDef {
Relation::Parent.def()
}
#[sea_orm(has_many = "super::parent::Entity")]
Parent,
}
impl Related<super::session::Entity> for Entity {
@ -48,4 +42,10 @@ impl Related<super::session::Entity> for Entity {
}
}
impl Related<super::parent::Entity> for Entity {
fn to() -> RelationDef {
Relation::Parent.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View file

@ -4,3 +4,5 @@ pub mod admin;
pub mod candidate;
pub mod parent;
pub mod session;
pub mod admin_session;
pub mod session_trait;

View file

@ -3,6 +3,7 @@
pub mod prelude;
pub mod admin;
pub mod admin_session;
pub mod candidate;
pub mod parent;
pub mod session;

View file

@ -1,6 +1,7 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.9.3
pub use super::admin::Entity as Admin;
pub use super::admin_session::Entity as AdminSession;
pub use super::candidate::Entity as Candidate;
pub use super::parent::Entity as Parent;
pub use super::session::Entity as Session;

View file

@ -2,13 +2,14 @@
use sea_orm::entity::prelude::*;
use crate::session_trait::UserSession;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "session")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id: Uuid,
pub user_id: Option<i32>,
pub admin_id: Option<i32>,
pub candidate_id: Option<i32>,
pub ip_address: String,
pub created_at: DateTime,
pub expires_at: DateTime,
@ -17,17 +18,9 @@ pub struct Model {
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
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(
belongs_to = "super::candidate::Entity",
from = "Column::UserId",
from = "Column::CandidateId",
to = "super::candidate::Column::Application",
on_update = "Cascade",
on_delete = "Cascade"
@ -35,12 +28,6 @@ pub enum Relation {
Candidate,
}
impl Related<super::admin::Entity> for Entity {
fn to() -> RelationDef {
Relation::Admin.def()
}
}
impl Related<super::candidate::Entity> for Entity {
fn to() -> RelationDef {
Relation::Candidate.def()
@ -48,3 +35,13 @@ impl Related<super::candidate::Entity> for Entity {
}
impl ActiveModelBehavior for ActiveModel {}
#[async_trait::async_trait]
impl UserSession for Model {
async fn id(&self) -> Uuid {
self.id
}
async fn expires_at(&self) -> chrono::NaiveDateTime {
self.expires_at
}
}

View file

@ -0,0 +1,8 @@
use async_trait::async_trait;
use sea_orm::prelude::Uuid;
#[async_trait]
pub trait UserSession {
async fn expires_at(&self) -> chrono::NaiveDateTime;
async fn id(&self) -> Uuid;
}

View file

@ -8,6 +8,7 @@ mod m20221025_154422_create_session;
mod m20221027_194728_session_create_user_fk;
mod m20221028_194728_session_create_admin_fk;
mod m20221112_112212_create_parent_candidate_fk;
mod m20221221_162232_create_admin_session;
pub struct Migrator;
#[async_trait::async_trait]
@ -20,8 +21,10 @@ impl MigratorTrait for Migrator {
Box::new(m20221024_134454_insert_sample_admin::Migration::default()),
Box::new(m20221025_154422_create_session::Migration),
Box::new(m20221027_194728_session_create_user_fk::Migration),
Box::new(m20221028_194728_session_create_admin_fk::Migration),
Box::new(m20221112_112212_create_parent_candidate_fk::Migration),
Box::new(m20221221_162232_create_admin_session::Migration),
Box::new(m20221028_194728_session_create_admin_fk::Migration),
]
}
}

View file

@ -18,8 +18,7 @@ impl MigrationTrait for Migration {
.unique_key()
.primary_key(),
)
.col(ColumnDef::new(Session::UserId).integer())
.col(ColumnDef::new(Session::AdminId).integer())
.col(ColumnDef::new(Session::CandidateId).integer())
.col(ColumnDef::new(Session::IpAddress).string().not_null())
.col(ColumnDef::new(Session::CreatedAt).date_time().not_null())
.col(ColumnDef::new(Session::ExpiresAt).date_time().not_null())
@ -40,8 +39,7 @@ impl MigrationTrait for Migration {
pub enum Session {
Table,
Id,
UserId,
AdminId,
CandidateId,
IpAddress,
CreatedAt,
ExpiresAt,

View file

@ -10,7 +10,7 @@ impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager.create_foreign_key(ForeignKey::create()
.name("user_fk")
.from(Session::Table, Session::UserId)
.from(Session::Table, Session::CandidateId)
.to(Candidate::Table, Candidate::Application)
.on_delete(ForeignKeyAction::Cascade)
.on_update(ForeignKeyAction::Cascade)

View file

@ -1,6 +1,6 @@
use sea_orm_migration::prelude::*;
use crate::{m20221025_154422_create_session::Session, m20221024_111310_create_admin::Admin};
use crate::{m20221221_162232_create_admin_session::AdminSession, m20221024_111310_create_admin::Admin};
#[derive(DeriveMigrationName)]
pub struct Migration;
@ -10,7 +10,7 @@ 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)
.from(AdminSession::Table, AdminSession::AdminId)
.to(Admin::Table, Admin::Id)
.on_delete(ForeignKeyAction::Cascade)
.on_update(ForeignKeyAction::Cascade)
@ -20,7 +20,7 @@ impl MigrationTrait for Migration {
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager.drop_foreign_key(ForeignKey::drop()
.name("admin_fk")
.table(Session::Table)
.table(AdminSession::Table)
.to_owned()).await
}
}

View file

@ -0,0 +1,48 @@
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(AdminSession::Table)
.if_not_exists()
.col(
ColumnDef::new(AdminSession::Id)
.uuid()
.unique_key()
.not_null()
.primary_key(),
)
.col(ColumnDef::new(AdminSession::AdminId).integer())
.col(ColumnDef::new(AdminSession::IpAddress).string().not_null())
.col(ColumnDef::new(AdminSession::CreatedAt).date_time().not_null())
.col(ColumnDef::new(AdminSession::ExpiresAt).date_time().not_null())
.col(ColumnDef::new(AdminSession::UpdatedAt).date_time().not_null())
.to_owned(),
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_table(Table::drop().table(AdminSession::Table).to_owned())
.await
}
}
/// Learn more at https://docs.rs/sea-query#iden
#[derive(Iden)]
pub enum AdminSession {
Table,
Id,
AdminId,
IpAddress,
CreatedAt,
ExpiresAt,
UpdatedAt,
}