first commit

This commit is contained in:
Ultradesu
2025-08-25 21:48:59 +03:00
commit 98ba8846c3
25 changed files with 6400 additions and 0 deletions

View File

@@ -0,0 +1,64 @@
use sea_orm::{Database, DatabaseConnection, DbErr, ConnectionTrait};
use sea_orm::{Schema, DbBackend, Statement};
use migration::prelude::{SqliteQueryBuilder, PostgresQueryBuilder, MysqlQueryBuilder};
use std::time::Duration;
use std::path::Path;
use crate::config::DatabaseConfig;
pub async fn create_connection(config: &DatabaseConfig) -> Result<DatabaseConnection, DbErr> {
// Create SQLite database file if it doesn't exist
if config.url.starts_with("sqlite://") {
let db_path = config.url.strip_prefix("sqlite://").unwrap_or(&config.url);
// Create parent directories if they don't exist
if let Some(parent) = Path::new(db_path).parent() {
if !parent.exists() {
std::fs::create_dir_all(parent)
.map_err(|e| DbErr::Custom(format!("Failed to create database directory: {}", e)))?;
}
}
// Create empty database file if it doesn't exist
if !Path::new(db_path).exists() {
std::fs::File::create(db_path)
.map_err(|e| DbErr::Custom(format!("Failed to create database file: {}", e)))?;
tracing::info!("Created SQLite database file: {}", db_path);
}
}
let mut options = sea_orm::ConnectOptions::new(&config.url);
options
.max_connections(config.max_connections)
.min_connections(1)
.connect_timeout(Duration::from_secs(config.connect_timeout))
.acquire_timeout(Duration::from_secs(config.acquire_timeout))
.idle_timeout(Duration::from_secs(config.idle_timeout))
.max_lifetime(Duration::from_secs(config.max_lifetime))
.sqlx_logging(true)
.sqlx_logging_level(tracing::log::LevelFilter::Debug);
Database::connect(options).await
}
pub async fn migrate_database(db: &DatabaseConnection) -> Result<(), DbErr> {
// Get the database backend
let backend = db.get_database_backend();
let schema = Schema::new(backend);
// Create nodes table if it doesn't exist
let mut create_table_stmt = schema.create_table_from_entity(crate::database::entities::node::Entity);
// Convert to SQL
let sql = match backend {
DbBackend::Sqlite => create_table_stmt.if_not_exists().to_string(SqliteQueryBuilder),
DbBackend::Postgres => create_table_stmt.if_not_exists().to_string(PostgresQueryBuilder),
DbBackend::MySql => create_table_stmt.if_not_exists().to_string(MysqlQueryBuilder),
};
// Execute the statement
db.execute(Statement::from_string(backend, sql)).await?;
tracing::info!("Database migration completed");
Ok(())
}

View File

@@ -0,0 +1 @@
pub mod node;

View File

@@ -0,0 +1,82 @@
use sea_orm::entity::prelude::*;
use sea_orm::Set;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "nodes")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id: String,
pub name: String,
pub public_key: String,
pub private_key: String,
pub listen: String, // JSON array stored as string
pub addresses: String, // JSON array stored as string
pub created_at: DateTimeUtc,
pub updated_at: DateTimeUtc,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
impl ActiveModelBehavior for ActiveModel {
fn new() -> Self {
Self {
id: Set(uuid::Uuid::new_v4().to_string()),
created_at: Set(chrono::Utc::now()),
updated_at: Set(chrono::Utc::now()),
..ActiveModelTrait::default()
}
}
fn before_save<'life0, 'async_trait, C>(
mut self,
_db: &'life0 C,
_insert: bool,
) -> core::pin::Pin<Box<dyn core::future::Future<Output = Result<Self, DbErr>> + core::marker::Send + 'async_trait>>
where
'life0: 'async_trait,
C: 'async_trait + ConnectionTrait,
Self: 'async_trait,
{
Box::pin(async move {
self.updated_at = Set(chrono::Utc::now());
Ok(self)
})
}
}
// Conversion functions between database model and domain model
impl From<Model> for crate::yggdrasil::Node {
fn from(model: Model) -> Self {
let listen: Vec<String> = serde_json::from_str(&model.listen).unwrap_or_default();
let addresses: Vec<String> = serde_json::from_str(&model.addresses).unwrap_or_default();
crate::yggdrasil::Node {
id: model.id,
name: model.name,
public_key: model.public_key,
private_key: model.private_key,
listen,
addresses,
}
}
}
impl From<&crate::yggdrasil::Node> for ActiveModel {
fn from(node: &crate::yggdrasil::Node) -> Self {
let listen = serde_json::to_string(&node.listen).unwrap_or_default();
let addresses = serde_json::to_string(&node.addresses).unwrap_or_default();
ActiveModel {
id: Set(node.id.clone()),
name: Set(node.name.clone()),
public_key: Set(node.public_key.clone()),
private_key: Set(node.private_key.clone()),
listen: Set(listen),
addresses: Set(addresses),
created_at: Set(chrono::Utc::now()),
updated_at: Set(chrono::Utc::now()),
}
}
}

4
src/database/mod.rs Normal file
View File

@@ -0,0 +1,4 @@
pub mod entities;
pub mod connection;
pub use connection::*;