mirror of
https://codeberg.org/tyy/aspm
synced 2024-12-22 15:59:29 -07:00
Migrate to sqlite+sea_orm
This commit is contained in:
parent
5390a9389a
commit
19c42f6138
14 changed files with 1423 additions and 246 deletions
1252
Cargo.lock
generated
1252
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -18,7 +18,10 @@ thiserror = "1.0.40"
|
|||
asp = { path = "crates/asp" }
|
||||
indoc = "2.0.1"
|
||||
anstyle = "1.0.1"
|
||||
redb = "1.0.3"
|
||||
dialoguer = { version = "0.10.4", features = ["password"] }
|
||||
argon2 = { version = "0.5.0", features = ["std"] }
|
||||
data-encoding = "2.4.0"
|
||||
sea-orm = { version = "0.11.3", features = ["sqlx-sqlite", "runtime-tokio-native-tls"] }
|
||||
tokio = { version = "1.29.1", features = ["macros", "rt-multi-thread"] }
|
||||
sea-orm-migration = "0.11.3"
|
||||
async-trait = "0.1.68"
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use anyhow::bail;
|
||||
use josekit::{
|
||||
jwk::{
|
||||
alg::{ec::EcCurve, ed::EdCurve},
|
||||
|
@ -19,6 +20,26 @@ pub enum AspKeyType {
|
|||
ES256,
|
||||
}
|
||||
|
||||
impl Into<i32> for AspKeyType {
|
||||
fn into(self) -> i32 {
|
||||
match self {
|
||||
Self::Ed25519 => 0,
|
||||
Self::ES256 => 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<i32> for AspKeyType {
|
||||
type Error = anyhow::Error;
|
||||
fn try_from(value: i32) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
0 => Ok(Self::Ed25519),
|
||||
1 => Ok(Self::ES256),
|
||||
_ => bail!("Invalid index for AspKeyType"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A struct representing a key that can be used to create profiles or ASPE requests
|
||||
#[derive(Debug)]
|
||||
pub struct AspKey {
|
||||
|
|
|
@ -4,9 +4,9 @@ use asp::keys::AspKey;
|
|||
use clap::{Parser, ValueEnum};
|
||||
use data_encoding::BASE64_NOPAD;
|
||||
use dialoguer::{theme::ColorfulTheme, Password};
|
||||
use redb::ReadableTable;
|
||||
use sea_orm::EntityTrait;
|
||||
|
||||
use crate::{commands::AspmSubcommand, db::KEYS_TABLE};
|
||||
use crate::{commands::AspmSubcommand, entities::keys};
|
||||
|
||||
#[derive(ValueEnum, Debug, Clone)]
|
||||
pub enum KeyExportFormat {
|
||||
|
@ -29,20 +29,16 @@ pub struct KeysExportCommand {
|
|||
fingerprint: String,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl AspmSubcommand for KeysExportCommand {
|
||||
fn execute(&self, state: crate::AspmState) -> Result<(), anyhow::Error> {
|
||||
let txn = state
|
||||
.db
|
||||
.begin_read()
|
||||
.context("Unable to start db read transaction")?;
|
||||
let table = txn.open_table(KEYS_TABLE)?;
|
||||
let entry = table
|
||||
.get(&*self.fingerprint)
|
||||
async fn execute(&self, state: crate::AspmState) -> Result<(), anyhow::Error> {
|
||||
// Fetch key from db
|
||||
let entry = keys::Entity::find_by_id(&self.fingerprint)
|
||||
.one(&state.db)
|
||||
.await
|
||||
.context("Unable to fetch key from database")?;
|
||||
|
||||
if let Some(entry) = entry {
|
||||
let value = entry.value();
|
||||
|
||||
let key_password = std::env::var("KEY_PASSWORD").or_else(|_| {
|
||||
Password::with_theme(&ColorfulTheme::default())
|
||||
.with_prompt("Please enter a password to decrypt the key with")
|
||||
|
@ -61,7 +57,7 @@ impl AspmSubcommand for KeysExportCommand {
|
|||
let aes_key = hash.hash.context("Unable to derive encryption key")?;
|
||||
let aes_key = &aes_key.as_bytes()[0..32];
|
||||
|
||||
if let Ok(decrypted) = AspKey::from_encrypted(aes_key, &value.key) {
|
||||
if let Ok(decrypted) = AspKey::from_encrypted(aes_key, &entry.encrypted) {
|
||||
let export = match self.format {
|
||||
KeyExportFormat::Encrypted => decrypted
|
||||
.export_encrypted(aes_key)
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
use anyhow::{anyhow, Context};
|
||||
use anyhow::{anyhow, bail, Context};
|
||||
use argon2::{password_hash::SaltString, Argon2, PasswordHasher};
|
||||
use asp::keys::{AspKey, AspKeyType};
|
||||
use clap::{Parser, ValueEnum};
|
||||
use data_encoding::BASE64_NOPAD;
|
||||
use dialoguer::{theme::ColorfulTheme, Input, Password};
|
||||
use indoc::printdoc;
|
||||
use sea_orm::{ActiveValue, EntityTrait};
|
||||
|
||||
use crate::{
|
||||
commands::AspmSubcommand,
|
||||
db::{KeysTableValue, KEYS_TABLE},
|
||||
};
|
||||
use crate::{commands::AspmSubcommand, entities::keys};
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)]
|
||||
pub enum KeyGenerationType {
|
||||
|
@ -25,19 +23,23 @@ pub struct KeysGenerateCommand {
|
|||
/// It doesn't really matter that much which one is used, as they both work fine, but Ed25519 is used as a safe default.
|
||||
#[clap(value_enum, default_value_t = KeyGenerationType::Ed25519, long_about, ignore_case = true)]
|
||||
key_type: KeyGenerationType,
|
||||
/// Tha alias of the key to generate. This can be anything, and it can also be omitted to prompt interactively. This has no purpose other than providing a way to nicely name keys, rather than having to remember a fingerprint.
|
||||
#[arg(short = 'n', long)]
|
||||
key_alias: Option<String>,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl AspmSubcommand for KeysGenerateCommand {
|
||||
fn execute(&self, config: crate::AspmState) -> Result<(), anyhow::Error> {
|
||||
let alias = Input::with_theme(&ColorfulTheme::default())
|
||||
.with_prompt("Please enter an alias to give to this key")
|
||||
.allow_empty(false)
|
||||
.validate_with(|input: &String| match input.as_bytes().len() <= 255 {
|
||||
true => Ok(()),
|
||||
false => Err("Alias must not be longer than 255 characters!"),
|
||||
})
|
||||
.interact()
|
||||
.context("Unable to prompt on stderr")?;
|
||||
async fn execute(&self, state: crate::AspmState) -> Result<(), anyhow::Error> {
|
||||
let alias = if let Some(alias) = &self.key_alias {
|
||||
alias.clone()
|
||||
} else {
|
||||
Input::with_theme(&ColorfulTheme::default())
|
||||
.with_prompt("Please enter an alias to give to this key")
|
||||
.allow_empty(false)
|
||||
.interact()
|
||||
.context("Unable to prompt on stderr")?
|
||||
};
|
||||
|
||||
let key_password = std::env::var("KEY_PASSWORD").or_else(|_| {
|
||||
Password::with_theme(&ColorfulTheme::default())
|
||||
|
@ -70,26 +72,20 @@ impl AspmSubcommand for KeysGenerateCommand {
|
|||
.export_encrypted(aes_key)
|
||||
.context("Unable to derive the encryption key")?;
|
||||
|
||||
let txn = config
|
||||
.db
|
||||
.begin_write()
|
||||
.context("Unable to open the database for writing")?;
|
||||
{
|
||||
let mut table = txn.open_table(KEYS_TABLE)?;
|
||||
table
|
||||
.insert(
|
||||
key.fingerprint.as_str(),
|
||||
KeysTableValue {
|
||||
key: encrypted,
|
||||
alias: alias
|
||||
.try_into()
|
||||
.context("alias must be less than or equal to 255 characters")?,
|
||||
key_type: key.key_type,
|
||||
},
|
||||
)
|
||||
.context("Unable to write to database")?;
|
||||
// Write to db
|
||||
let entry = keys::ActiveModel {
|
||||
fingerprint: ActiveValue::Set(key.fingerprint.clone()),
|
||||
key_type: ActiveValue::Set(key.key_type.into()),
|
||||
alias: ActiveValue::Set(alias),
|
||||
encrypted: ActiveValue::Set(encrypted),
|
||||
};
|
||||
let res = keys::Entity::insert(entry)
|
||||
.exec(&state.db)
|
||||
.await
|
||||
.context("Unable to add key to database")?;
|
||||
if res.last_insert_id != key.fingerprint {
|
||||
bail!("The key was unable to be saved to the database")
|
||||
}
|
||||
txn.commit().context("Unable to write to database")?;
|
||||
|
||||
printdoc! {
|
||||
"
|
||||
|
|
|
@ -1,24 +1,23 @@
|
|||
use anstyle::{AnsiColor, Reset, Style as Anstyle};
|
||||
use anyhow::Context;
|
||||
use asp::keys::AspKeyType;
|
||||
use clap::Parser;
|
||||
use indoc::printdoc;
|
||||
use redb::ReadableTable;
|
||||
use sea_orm::EntityTrait;
|
||||
|
||||
use crate::{commands::AspmSubcommand, db::KEYS_TABLE};
|
||||
use crate::{commands::AspmSubcommand, entities::keys};
|
||||
|
||||
/// A command to list all saved keys, along with their fingerprints and types
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct KeysListCommand;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl AspmSubcommand for KeysListCommand {
|
||||
fn execute(&self, state: crate::AspmState) -> Result<(), anyhow::Error> {
|
||||
let txn = state
|
||||
.db
|
||||
.begin_read()
|
||||
.context("Unable to start db read transaction")?;
|
||||
let table = txn.open_table(KEYS_TABLE)?;
|
||||
let iter = table.iter().context("Unable to read table entries")?;
|
||||
let entries: Vec<_> = iter.collect();
|
||||
async fn execute(&self, state: crate::AspmState) -> Result<(), anyhow::Error> {
|
||||
let entries = keys::Entity::find()
|
||||
.all(&state.db)
|
||||
.await
|
||||
.context("Unable to read keys from database")?;
|
||||
|
||||
// Construct styles
|
||||
let reset = Reset::default().render();
|
||||
|
@ -44,18 +43,15 @@ impl AspmSubcommand for KeysListCommand {
|
|||
n = entries.len(),
|
||||
);
|
||||
for entry in entries.iter() {
|
||||
if let Ok((fingerprint, value)) = entry {
|
||||
let value = value.value();
|
||||
printdoc! {
|
||||
"
|
||||
{alias_style}{alias}:{reset}
|
||||
{key_style}Fingerprint{reset} {value_style}{fingerprint}{reset}
|
||||
{key_style}Key Type{reset} {value_style}{key_type:?}{reset}
|
||||
",
|
||||
fingerprint = fingerprint.value(),
|
||||
key_type = value.key_type,
|
||||
alias = value.alias
|
||||
}
|
||||
printdoc! {
|
||||
"
|
||||
{alias_style}{alias}:{reset}
|
||||
{key_style}Fingerprint{reset} {value_style}{fingerprint}{reset}
|
||||
{key_style}Key Type{reset} {value_style}{key_type:?}{reset}
|
||||
",
|
||||
fingerprint = entry.fingerprint,
|
||||
key_type = TryInto::<AspKeyType>::try_into(entry.key_type).context("Unable to get key type from database")?,
|
||||
alias = entry.alias
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ use clap::Parser;
|
|||
|
||||
use crate::AspmState;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait AspmSubcommand: Parser {
|
||||
fn execute(&self, state: AspmState) -> Result<(), anyhow::Error>;
|
||||
async fn execute(&self, state: AspmState) -> Result<(), anyhow::Error>;
|
||||
}
|
||||
|
|
128
src/db.rs
128
src/db.rs
|
@ -1,128 +0,0 @@
|
|||
use std::fmt::Display;
|
||||
|
||||
use anyhow::bail;
|
||||
use asp::keys::AspKeyType;
|
||||
use redb::{RedbValue, TableDefinition};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct KeysTableValue {
|
||||
pub alias: BoundedString,
|
||||
pub key: String,
|
||||
pub key_type: AspKeyType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BoundedString {
|
||||
inner: String,
|
||||
}
|
||||
|
||||
impl Display for BoundedString {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for BoundedString {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
if value.as_bytes().len() > 255 {
|
||||
bail!("Value was too long");
|
||||
}
|
||||
Ok(Self { inner: value })
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Vec<u8>> for BoundedString {
|
||||
fn into(self) -> Vec<u8> {
|
||||
self.inner.as_bytes().to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
impl RedbValue for KeysTableValue {
|
||||
type SelfType<'a> = KeysTableValue;
|
||||
|
||||
/// The first u8 is a length, specifying how long the alias is (therefore the alias can only be a max of 255 bytes)
|
||||
/// The next bytes (bounded by the length of the first u8) is the alias
|
||||
/// The next byte is a u8 representing the type of key
|
||||
/// The rest of the bytes are the key
|
||||
type AsBytes<'a> = Vec<u8>;
|
||||
|
||||
fn fixed_width() -> Option<usize> {
|
||||
None
|
||||
}
|
||||
|
||||
// This does panic, but I don't know if there is a better way to do it. If you manage to insert bad data into the table, good job, that is your problem
|
||||
fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'a>
|
||||
where
|
||||
Self: 'a,
|
||||
{
|
||||
let mut iter = data.iter().map(|b| *b);
|
||||
|
||||
let alias_length: usize = iter
|
||||
.next()
|
||||
.expect("parsing key table value failed: unable to get first byte")
|
||||
.into();
|
||||
// Get alias
|
||||
let alias_bytes = {
|
||||
let mut vec = Vec::new();
|
||||
for _ in 0..alias_length {
|
||||
vec.push(
|
||||
iter.next()
|
||||
.expect("parsing key table value failed: ran out of bytes on alias"),
|
||||
);
|
||||
}
|
||||
vec
|
||||
};
|
||||
if alias_bytes.len() != alias_length {
|
||||
panic!("parsing key table value failed: unable to get full alias");
|
||||
};
|
||||
// Get the type of key
|
||||
let key_type_byte = iter
|
||||
.next()
|
||||
.expect("parsing key table value failed: unable to get the key type");
|
||||
// Get key
|
||||
let key_bytes = iter.collect::<Vec<u8>>();
|
||||
// Assemble bytes into strings and struct
|
||||
Self {
|
||||
key: String::from_utf8(key_bytes)
|
||||
.expect("parsing key table value failed: unable to decode key into string"),
|
||||
alias: String::from_utf8(alias_bytes)
|
||||
.expect("parsing key table value failed: unable to decode alias into string")
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
key_type: match key_type_byte {
|
||||
0 => AspKeyType::Ed25519,
|
||||
1 => AspKeyType::ES256,
|
||||
_ => panic!("parsing key table value failed: unknown key type byte found"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'b>) -> Self::AsBytes<'a>
|
||||
where
|
||||
Self: 'a,
|
||||
Self: 'b,
|
||||
{
|
||||
let key_bytes = value.key.as_bytes();
|
||||
let alias_bytes: Vec<u8> = value.alias.clone().into();
|
||||
|
||||
let mut serialized: Vec<u8> = vec![];
|
||||
serialized.push(alias_bytes.len().try_into().unwrap()); // Add the first byte (alias length)
|
||||
serialized.extend_from_slice(alias_bytes.as_slice()); // Add the alias bytes
|
||||
serialized.push(match value.key_type {
|
||||
AspKeyType::Ed25519 => 0,
|
||||
AspKeyType::ES256 => 1,
|
||||
}); // Add the key type byte
|
||||
serialized.extend_from_slice(key_bytes); // Add the rest of the bytes, all of which are the key
|
||||
|
||||
serialized
|
||||
}
|
||||
|
||||
fn type_name() -> redb::TypeName {
|
||||
redb::TypeName::new("aspm::KeysTableValue")
|
||||
}
|
||||
}
|
||||
|
||||
/// A table to contain saved keys
|
||||
pub const KEYS_TABLE: TableDefinition<&str, KeysTableValue> = TableDefinition::new("claims");
|
18
src/entities/keys.rs
Normal file
18
src/entities/keys.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||
#[sea_orm(table_name = "keys")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub fingerprint: String,
|
||||
pub key_type: i32,
|
||||
pub alias: String,
|
||||
pub encrypted: String,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
5
src/entities/mod.rs
Normal file
5
src/entities/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
|
||||
|
||||
pub mod prelude;
|
||||
|
||||
pub mod keys;
|
3
src/entities/prelude.rs
Normal file
3
src/entities/prelude.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
|
||||
|
||||
pub use super::keys::Entity as Keys;
|
49
src/main.rs
49
src/main.rs
|
@ -1,12 +1,15 @@
|
|||
mod commands;
|
||||
mod db;
|
||||
mod entities;
|
||||
mod migrations;
|
||||
|
||||
use anstyle::{AnsiColor, Color as AnstyleColor, Style as Anstyle};
|
||||
use anyhow::Context;
|
||||
use app_dirs2::{AppDataType, AppInfo};
|
||||
use clap::{Parser, Subcommand};
|
||||
use commands::{keys::KeysSubcommands, AspmSubcommand};
|
||||
use redb::Database;
|
||||
use migrations::Migrator;
|
||||
use sea_orm::{Database, DatabaseConnection};
|
||||
use sea_orm_migration::{MigratorTrait, SchemaManager};
|
||||
use thiserror::Error;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
@ -15,11 +18,12 @@ const APP_INFO: AppInfo = AppInfo {
|
|||
name: env!("CARGO_CRATE_NAME"),
|
||||
author: "Ty",
|
||||
};
|
||||
const DATABASE_URL: &str = "sqlite://DB_PATH?mode=rwc";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AspmState {
|
||||
pub data_dir: PathBuf,
|
||||
pub db: Database,
|
||||
pub db: DatabaseConnection,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
|
@ -73,8 +77,9 @@ pub enum AspmSubcommands {
|
|||
Keys(commands::keys::KeysSubcommand),
|
||||
}
|
||||
|
||||
fn main() {
|
||||
match cli() {
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
match cli().await {
|
||||
Err(e) => {
|
||||
eprintln!("An error occurred while running that command:\n{e}");
|
||||
}
|
||||
|
@ -82,7 +87,7 @@ fn main() {
|
|||
}
|
||||
}
|
||||
|
||||
fn cli() -> Result<(), anyhow::Error> {
|
||||
async fn cli() -> Result<(), anyhow::Error> {
|
||||
let parsed = AspmCommand::parse();
|
||||
|
||||
// Check the data dir (read and write)
|
||||
|
@ -96,14 +101,24 @@ fn cli() -> Result<(), anyhow::Error> {
|
|||
std::fs::read_dir(&data_dir).or(Err(AspmError::DataDirReadError))?;
|
||||
|
||||
// Construct the database in the dir
|
||||
let mut db = Database::create({
|
||||
let db = Database::connect(DATABASE_URL.replace("DB_PATH", &{
|
||||
let mut new = data_dir.clone();
|
||||
new.push("db.redb");
|
||||
new
|
||||
})
|
||||
.or(Err(AspmError::DatabaseCreateError))?;
|
||||
db.check_integrity()
|
||||
.context("Unable to check database integrity")?;
|
||||
new.push("db.sqlite");
|
||||
new.to_str()
|
||||
.context("Unable to parse database path into string")
|
||||
.map(|s| s.to_string())
|
||||
}?))
|
||||
.await
|
||||
.context("Unable to open database")?;
|
||||
|
||||
let schema_manager = SchemaManager::new(&db);
|
||||
Migrator::up(&db, None)
|
||||
.await
|
||||
.context("Unable to migrate database")?;
|
||||
assert!(schema_manager
|
||||
.has_table("keys")
|
||||
.await
|
||||
.context("Unable to check database for keys table")?);
|
||||
|
||||
// Make the state
|
||||
let state = AspmState { data_dir, db };
|
||||
|
@ -111,9 +126,9 @@ fn cli() -> Result<(), anyhow::Error> {
|
|||
// Call the subcommand
|
||||
match &parsed.subcommand {
|
||||
AspmSubcommands::Keys(subcommand) => match &subcommand.subcommand {
|
||||
KeysSubcommands::Generate(subcommand) => subcommand.execute(state),
|
||||
KeysSubcommands::List(subcommand) => subcommand.execute(state),
|
||||
KeysSubcommands::Export(subcommand) => subcommand.execute(state),
|
||||
KeysSubcommands::Generate(subcommand) => subcommand.execute(state).await,
|
||||
KeysSubcommands::List(subcommand) => subcommand.execute(state).await,
|
||||
KeysSubcommands::Export(subcommand) => subcommand.execute(state).await,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -122,8 +137,6 @@ fn cli() -> Result<(), anyhow::Error> {
|
|||
enum AspmError {
|
||||
#[error("The data directory was unable to be created, is it correct, and does the current user have permission to create it?")]
|
||||
DataDirCreateError,
|
||||
#[error("The database was unable to be created, is the data dir correct, and does the current user have permission to modify it?")]
|
||||
DatabaseCreateError,
|
||||
#[error("The data directory was unable to be read, is it correct, and does the current user have permission to read it?")]
|
||||
DataDirReadError,
|
||||
#[error("An unknown internal error occurred, please report this to the developer")]
|
||||
|
|
48
src/migrations/m_20230701_000001_create_keys_table.rs
Normal file
48
src/migrations/m_20230701_000001_create_keys_table.rs
Normal file
|
@ -0,0 +1,48 @@
|
|||
use sea_orm_migration::prelude::*;
|
||||
|
||||
pub struct Migration;
|
||||
|
||||
impl MigrationName for Migration {
|
||||
fn name(&self) -> &str {
|
||||
"m_20230701_000001_create_keys_table"
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
// Define how to apply this migration: Create the Bakery table.
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Keys::Table)
|
||||
.col(
|
||||
ColumnDef::new(Keys::Fingerprint)
|
||||
.string_len(26)
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Keys::KeyType).integer().not_null())
|
||||
.col(ColumnDef::new(Keys::Alias).string().not_null())
|
||||
.col(ColumnDef::new(Keys::Encrypted).string().not_null())
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
// Define how to rollback this migration: Drop the Bakery table.
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(Keys::Table).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Iden)]
|
||||
enum Keys {
|
||||
Table,
|
||||
Fingerprint,
|
||||
KeyType,
|
||||
Alias,
|
||||
Encrypted,
|
||||
}
|
11
src/migrations/mod.rs
Normal file
11
src/migrations/mod.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
mod m_20230701_000001_create_keys_table;
|
||||
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
pub struct Migrator;
|
||||
|
||||
impl MigratorTrait for Migrator {
|
||||
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
|
||||
vec![Box::new(m_20230701_000001_create_keys_table::Migration)]
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue