/v1/auth/magic-link
This commit is contained in:
parent
fca4fb4cd5
commit
3a702ed3a5
|
@ -74,6 +74,17 @@ dependencies = [
|
||||||
"syn 1.0.107",
|
"syn 1.0.107",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "actix-request-identifier"
|
||||||
|
version = "4.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f620de7c806297b88cf39da5ef4293a59f7dc219a9838433e83238e53a9c7057"
|
||||||
|
dependencies = [
|
||||||
|
"actix-web",
|
||||||
|
"futures",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "actix-router"
|
name = "actix-router"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
|
@ -1210,6 +1221,7 @@ checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-executor",
|
||||||
"futures-io",
|
"futures-io",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
|
@ -1232,6 +1244,17 @@ version = "0.3.26"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608"
|
checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-executor"
|
||||||
|
version = "0.3.26"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-intrusive"
|
name = "futures-intrusive"
|
||||||
version = "0.4.2"
|
version = "0.4.2"
|
||||||
|
@ -1264,6 +1287,17 @@ dependencies = [
|
||||||
"waker-fn",
|
"waker-fn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-macro"
|
||||||
|
version = "0.3.26"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 1.0.107",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-sink"
|
name = "futures-sink"
|
||||||
version = "0.3.26"
|
version = "0.3.26"
|
||||||
|
@ -1285,6 +1319,7 @@ dependencies = [
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-io",
|
"futures-io",
|
||||||
|
"futures-macro",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
@ -3336,6 +3371,7 @@ dependencies = [
|
||||||
name = "trifid-api"
|
name = "trifid-api"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"actix-request-identifier",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
"hex",
|
"hex",
|
||||||
"log",
|
"log",
|
||||||
|
@ -3467,6 +3503,7 @@ version = "1.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79"
|
checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"getrandom 0.2.8",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,8 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-web = "4" # Web framework
|
actix-web = "4" # Web framework
|
||||||
|
actix-request-identifier = "4" # Web framework
|
||||||
|
|
||||||
serde = { version = "1", features = ["derive"] } # Serialization and deserialization
|
serde = { version = "1", features = ["derive"] } # Serialization and deserialization
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,8 @@ pub static CONFIG: Lazy<TrifidConfig> = Lazy::new(|| {
|
||||||
#[derive(Serialize, Debug, Deserialize)]
|
#[derive(Serialize, Debug, Deserialize)]
|
||||||
pub struct TrifidConfig {
|
pub struct TrifidConfig {
|
||||||
pub database: TrifidConfigDatabase,
|
pub database: TrifidConfigDatabase,
|
||||||
pub server: TrifidConfigServer
|
pub server: TrifidConfigServer,
|
||||||
|
pub tokens: TrifidConfigTokens
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
@ -53,8 +54,15 @@ pub struct TrifidConfigServer {
|
||||||
pub bind: SocketAddr
|
pub bind: SocketAddr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct TrifidConfigTokens {
|
||||||
|
#[serde(default = "magic_link_expiry_time")]
|
||||||
|
pub magic_link_expiry_time_seconds: u64
|
||||||
|
}
|
||||||
|
|
||||||
fn max_connections_default() -> u32 { 100 }
|
fn max_connections_default() -> u32 { 100 }
|
||||||
fn min_connections_default() -> u32 { 5 }
|
fn min_connections_default() -> u32 { 5 }
|
||||||
fn time_defaults() -> u64 { 8 }
|
fn time_defaults() -> u64 { 8 }
|
||||||
fn sqlx_logging_default() -> bool { true }
|
fn sqlx_logging_default() -> bool { true }
|
||||||
fn socketaddr_8080() -> SocketAddr { SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::from([0, 0, 0, 0]), 8080)) }
|
fn socketaddr_8080() -> SocketAddr { SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::from([0, 0, 0, 0]), 8080)) }
|
||||||
|
fn magic_link_expiry_time() -> u64 { 3600 }
|
|
@ -1,5 +1,6 @@
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
use actix_request_identifier::RequestIdentifier;
|
||||||
use actix_web::{App, HttpResponse, HttpServer, post, web::{Data, Json, JsonConfig}};
|
use actix_web::{App, HttpResponse, HttpServer, post, web::{Data, Json, JsonConfig}};
|
||||||
use log::{error, info, Level};
|
use log::{error, info, Level};
|
||||||
use sea_orm::{ConnectOptions, Database, DatabaseConnection};
|
use sea_orm::{ConnectOptions, Database, DatabaseConnection};
|
||||||
|
@ -7,10 +8,17 @@ use serde::{Serialize, Deserialize};
|
||||||
use trifid_api_migration::{Migrator, MigratorTrait};
|
use trifid_api_migration::{Migrator, MigratorTrait};
|
||||||
use crate::config::CONFIG;
|
use crate::config::CONFIG;
|
||||||
use crate::error::{APIError, APIErrorsResponse};
|
use crate::error::{APIError, APIErrorsResponse};
|
||||||
|
use crate::tokens::random_id_no_id;
|
||||||
|
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod routes;
|
pub mod routes;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
pub mod tokens;
|
||||||
|
pub mod timers;
|
||||||
|
|
||||||
|
pub struct AppState {
|
||||||
|
pub conn: DatabaseConnection
|
||||||
|
}
|
||||||
|
|
||||||
#[actix_web::main]
|
#[actix_web::main]
|
||||||
async fn main() -> Result<(), Box<dyn Error>> {
|
async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
@ -33,7 +41,9 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
info!("Performing database migration...");
|
info!("Performing database migration...");
|
||||||
Migrator::up(&db, None).await?;
|
Migrator::up(&db, None).await?;
|
||||||
|
|
||||||
let data = Data::new(db);
|
let data = Data::new(AppState {
|
||||||
|
conn: db
|
||||||
|
});
|
||||||
|
|
||||||
HttpServer::new(move || {
|
HttpServer::new(move || {
|
||||||
App::new()
|
App::new()
|
||||||
|
@ -49,6 +59,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
})
|
})
|
||||||
).into()
|
).into()
|
||||||
}))
|
}))
|
||||||
|
.wrap(RequestIdentifier::with_generator(random_id_no_id))
|
||||||
}).bind(CONFIG.server.bind)?.run().await?;
|
}).bind(CONFIG.server.bind)?.run().await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
use actix_web::{HttpResponse, post};
|
||||||
|
use actix_web::web::{Data, Json};
|
||||||
|
use log::error;
|
||||||
|
use sea_orm::{ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, IntoActiveModel, QueryFilter};
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use trifid_api_entities::user::Entity as UserEntity;
|
||||||
|
use trifid_api_entities::user;
|
||||||
|
use crate::AppState;
|
||||||
|
use crate::config::CONFIG;
|
||||||
|
use crate::error::{APIError, APIErrorsResponse};
|
||||||
|
use crate::timers::expires_in_seconds;
|
||||||
|
use crate::tokens::random_token;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct MagicLinkRequest {
|
||||||
|
pub email: String
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct MagicLinkResponse {
|
||||||
|
pub data: MagicLinkResponseData,
|
||||||
|
pub metadata: MagicLinkResponseMetadata
|
||||||
|
}
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct MagicLinkResponseData {}
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct MagicLinkResponseMetadata {}
|
||||||
|
|
||||||
|
#[post("/v1/auth/magic-link")]
|
||||||
|
pub async fn magic_link_request(data: Data<AppState>, req: Json<MagicLinkRequest>) -> HttpResponse {
|
||||||
|
let user: Option<user::Model> = match UserEntity::find().filter(user::Column::Email.eq(&req.email)).one(&data.conn).await {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(e) => {
|
||||||
|
error!("database error: {}", e);
|
||||||
|
return HttpResponse::InternalServerError().json(APIErrorsResponse {
|
||||||
|
errors: vec![
|
||||||
|
APIError {
|
||||||
|
code: "ERR_DB_ERROR".to_string(),
|
||||||
|
message: "There was an error with the database request, please try again later.".to_string(),
|
||||||
|
path: None,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let user = match user {
|
||||||
|
Some(u) => u,
|
||||||
|
None => {
|
||||||
|
return HttpResponse::Unauthorized().json(APIErrorsResponse {
|
||||||
|
errors: vec![
|
||||||
|
APIError {
|
||||||
|
code: "ERR_USER_DOES_NOT_EXIST".to_string(),
|
||||||
|
message: "That user does not exist.".to_string(),
|
||||||
|
path: None,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let model = trifid_api_entities::magic_link::Model {
|
||||||
|
id: random_token("ml"),
|
||||||
|
user: user.id,
|
||||||
|
expires_on: expires_in_seconds(CONFIG.tokens.magic_link_expiry_time_seconds) as i64,
|
||||||
|
};
|
||||||
|
|
||||||
|
let active_model = model.into_active_model();
|
||||||
|
|
||||||
|
match active_model.insert(&data.conn).await {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => {
|
||||||
|
error!("database error: {}", e);
|
||||||
|
return HttpResponse::InternalServerError().json(APIErrorsResponse {
|
||||||
|
errors: vec![
|
||||||
|
APIError {
|
||||||
|
code: "ERR_DB_ERROR".to_string(),
|
||||||
|
message: "There was an error with the database request, please try again later.".to_string(),
|
||||||
|
path: None,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpResponse::Ok().json(MagicLinkResponse {
|
||||||
|
data: MagicLinkResponseData {},
|
||||||
|
metadata: MagicLinkResponseMetadata {}
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
pub mod magic_link;
|
|
@ -0,0 +1 @@
|
||||||
|
pub mod auth;
|
|
@ -0,0 +1,9 @@
|
||||||
|
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
pub fn expires_in_seconds(seconds: u64) -> u64 {
|
||||||
|
(SystemTime::now() + Duration::from_secs(seconds)).duration_since(UNIX_EPOCH).expect("Time went backwards").as_secs()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expired(time: u64) -> bool {
|
||||||
|
UNIX_EPOCH + Duration::from_secs(time) > SystemTime::now()
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
use actix_web::http::header::HeaderValue;
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
|
pub const ID_CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||||
|
pub const ID_LEN: u32 = 26;
|
||||||
|
|
||||||
|
pub const TOKEN_CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
|
||||||
|
pub const TOKEN_LEN: u32 = 43;
|
||||||
|
|
||||||
|
// 26
|
||||||
|
// format: [ID]-[26 chars]
|
||||||
|
pub fn random_id(identifier: &str) -> String {
|
||||||
|
format!("{}-{}", identifier, random_with_charset(ID_LEN, ID_CHARSET))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 26
|
||||||
|
// format: [ID]-[26 chars]
|
||||||
|
pub fn random_id_no_id() -> HeaderValue {
|
||||||
|
HeaderValue::from_str(&random_with_charset(ID_LEN, ID_CHARSET)).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 43
|
||||||
|
// format: [TYPE]-[43 chars]
|
||||||
|
pub fn random_token(identifier: &str) -> String {
|
||||||
|
format!("{}-{}", identifier, random_with_charset(TOKEN_LEN, TOKEN_CHARSET))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn random_with_charset(len: u32, charset: &[u8]) -> String {
|
||||||
|
(0..len).map(|_| {
|
||||||
|
let idx = rand::thread_rng().gen_range(0..charset.len());
|
||||||
|
charset[idx] as char
|
||||||
|
}).collect()
|
||||||
|
}
|
|
@ -3,3 +3,4 @@
|
||||||
pub mod prelude;
|
pub mod prelude;
|
||||||
|
|
||||||
pub mod user;
|
pub mod user;
|
||||||
|
pub mod magic_link;
|
|
@ -0,0 +1,32 @@
|
||||||
|
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2
|
||||||
|
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||||
|
#[sea_orm(table_name = "magic_link")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key, auto_increment = false)]
|
||||||
|
pub id: String,
|
||||||
|
pub user: String,
|
||||||
|
pub expires_on: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {
|
||||||
|
#[sea_orm(
|
||||||
|
belongs_to = "super::user::Entity",
|
||||||
|
from = "Column::User",
|
||||||
|
to = "super::user::Column::Id",
|
||||||
|
on_update = "Cascade",
|
||||||
|
on_delete = "Cascade"
|
||||||
|
)]
|
||||||
|
User,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::user::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::User.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
|
@ -1,3 +1,4 @@
|
||||||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2
|
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2
|
||||||
|
|
||||||
|
pub use super::magic_link::Entity as MagicLink;
|
||||||
pub use super::user::Entity as User;
|
pub use super::user::Entity as User;
|
||||||
|
|
|
@ -7,11 +7,21 @@ 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: String,
|
pub id: String,
|
||||||
|
#[sea_orm(unique)]
|
||||||
pub email: String,
|
pub email: String,
|
||||||
pub password_hash: String,
|
pub password_hash: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
pub enum Relation {}
|
pub enum Relation {
|
||||||
|
#[sea_orm(has_many = "super::magic_link::Entity")]
|
||||||
|
MagicLink,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::magic_link::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::MagicLink.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ActiveModelBehavior for ActiveModel {}
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
|
|
|
@ -3,12 +3,14 @@ pub use sea_orm_migration::prelude::*;
|
||||||
pub struct Migrator;
|
pub struct Migrator;
|
||||||
|
|
||||||
pub mod m20230402_162601_create_table_users;
|
pub mod m20230402_162601_create_table_users;
|
||||||
|
pub mod m20230402_183515_create_table_magic_links;
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl MigratorTrait for Migrator {
|
impl MigratorTrait for Migrator {
|
||||||
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
|
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
|
||||||
vec![
|
vec![
|
||||||
Box::new(m20230402_162601_create_table_users::Migration)
|
Box::new(m20230402_162601_create_table_users::Migration),
|
||||||
|
Box::new(m20230402_183515_create_table_magic_links::Migration),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ impl MigrationTrait for Migration {
|
||||||
.table(User::Table)
|
.table(User::Table)
|
||||||
.if_not_exists()
|
.if_not_exists()
|
||||||
.col(ColumnDef::new(User::Id).string().not_null().primary_key())
|
.col(ColumnDef::new(User::Id).string().not_null().primary_key())
|
||||||
.col(ColumnDef::new(User::Email).string().not_null())
|
.col(ColumnDef::new(User::Email).string().not_null().unique_key())
|
||||||
.col(ColumnDef::new(User::PasswordHash).string().not_null())
|
.col(ColumnDef::new(User::PasswordHash).string().not_null())
|
||||||
.to_owned()
|
.to_owned()
|
||||||
).await
|
).await
|
||||||
|
@ -25,7 +25,7 @@ impl MigrationTrait for Migration {
|
||||||
|
|
||||||
/// Learn more at https://docs.rs/sea-query#iden
|
/// Learn more at https://docs.rs/sea-query#iden
|
||||||
#[derive(Iden)]
|
#[derive(Iden)]
|
||||||
enum User {
|
pub enum User {
|
||||||
Table,
|
Table,
|
||||||
Id,
|
Id,
|
||||||
Email,
|
Email,
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
use sea_orm_migration::prelude::*;
|
||||||
|
use crate::m20230402_162601_create_table_users::User;
|
||||||
|
|
||||||
|
|
||||||
|
#[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(MagicLink::Table)
|
||||||
|
.if_not_exists()
|
||||||
|
.col(ColumnDef::new(MagicLink::Id).string().not_null().primary_key())
|
||||||
|
.col(ColumnDef::new(MagicLink::User).string().not_null())
|
||||||
|
.col(ColumnDef::new(MagicLink::ExpiresOn).big_integer().not_null())
|
||||||
|
.foreign_key(
|
||||||
|
ForeignKey::create()
|
||||||
|
.name("fk_magiclink_user_users_id")
|
||||||
|
.from(MagicLink::Table, MagicLink::User)
|
||||||
|
.to(User::Table, User::Id)
|
||||||
|
.on_delete(ForeignKeyAction::Cascade)
|
||||||
|
.on_update(ForeignKeyAction::Cascade)
|
||||||
|
).to_owned()
|
||||||
|
).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager.drop_table(Table::drop().table(MagicLink::Table).to_owned()).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Learn more at https://docs.rs/sea-query#iden
|
||||||
|
#[derive(Iden)]
|
||||||
|
pub enum MagicLink {
|
||||||
|
Table,
|
||||||
|
Id,
|
||||||
|
User,
|
||||||
|
ExpiresOn
|
||||||
|
}
|
Loading…
Reference in New Issue