Initial commit.

This commit is contained in:
Florian THIERRY
2026-07-13 21:57:58 +02:00
commit b5a95b0fa3
49 changed files with 11243 additions and 0 deletions
+1195
View File
File diff suppressed because it is too large Load Diff
+20
View File
@@ -0,0 +1,20 @@
[package]
name = "lumiere-infrastructure"
version = "1.0.0"
edition = "2024"
[lib]
name = "surveillant_systeme_infrastructure"
path = "src/lib.rs"
[dependencies]
lumiere-domain = { path = "../domain" }
chrono = "0.4.44"
diesel = { version = "2.1.0", features = ["sqlite", "returning_clauses_for_sqlite_3_35"] }
diesel_migrations = "2.1.0"
log = "0.4.29"
rand = "0.10.1"
rusqlite = { version = "0.39.0", features = ["bundled"] }
sysinfo = "0.39.1"
uuid = { version = "1.23.1", features = ["v4"] }
@@ -0,0 +1,34 @@
use rusqlite::Connection as RusqliteConnection;
use std::env;
use std::path::Path;
use diesel::{Connection, SqliteConnection};
pub struct DatabaseConnection {
pub connection: RusqliteConnection,
pub diesel_connection: SqliteConnection,
pub database_has_been_created: bool,
}
impl DatabaseConnection {
pub fn init() -> Self {
let sqlite_database_name = env::var("SQLITE_DATABASE_NAME").expect("SQLITE_DATABASE_NAME must be set.");
let database_existed_before_connection_initialisation = does_database_exist(&sqlite_database_name);
let connection = RusqliteConnection::open(sqlite_database_name.clone())
.expect("Unable to connect to SQLite database…");
let diesel_connection = SqliteConnection::establish(&sqlite_database_name.clone())
.unwrap_or_else(|_| panic!("Error connecting to {}", sqlite_database_name.clone()));
Self {
connection,
diesel_connection,
database_has_been_created: !database_existed_before_connection_initialisation
}
}
}
fn does_database_exist(database_name: &String) -> bool {
Path::new(database_name).exists()
}
@@ -0,0 +1,30 @@
use std::sync::{Arc, Mutex};
use rusqlite::{Connection, Error};
pub fn initialise_database_tables(connection: &Arc<Mutex<Connection>>) -> Result<(), Error> {
connection.lock()
.unwrap()
.execute_batch(
"
BEGIN;
CREATE TABLE IF NOT EXISTS configuration (
id TEXT PRIMARY KEY,
initialisation_code VARCHAR
);
CREATE TABLE IF NOT EXISTS system_resources (
id TEXT PRIMARY KEY,
datetime INTEGER NOT NULL,
cpu_usage INTEGER NOT NULL,
total_memory INTEGER NOT NULL,
used_memory INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS \"user\" (
id TEXT PRIMARY KEY,
username VARCHAR NOT NULL,
encrypted_password VARCHAR NOT NULL
);
COMMIT;
"
)?;
Ok(())
}
+2
View File
@@ -0,0 +1,2 @@
pub mod database_connection;
pub mod database_initialisation;
@@ -0,0 +1,46 @@
use std::io::{Error, ErrorKind};
use surveillant_systeme_domain::configuration::port::ConfigurationPort;
use crate::configuration::repository::ConfigurationSqliteRepository;
#[derive(Clone)]
pub struct ConfigurationSqliteAdapter {
repository: ConfigurationSqliteRepository
}
impl ConfigurationSqliteAdapter {
pub fn new(repository: ConfigurationSqliteRepository) -> Self {
Self {
repository
}
}
}
impl ConfigurationPort for ConfigurationSqliteAdapter {
fn get_initialisation_code(&self) -> Result<Option<String>, Error> {
self.repository.get_initialisation_code()
.map_err(|error| Error::new(
ErrorKind::Other,
format!("Unable to retrieve the initialisation code… Cause: {}", error)
))
}
fn save(&self, initialisation_code: String) -> Result<(), Error> {
self.repository.save(initialisation_code)
.map_err(|error| Error::new(
ErrorKind::Other,
format!("Unable to save the initialisation code… Cause: {}", error)
))
}
fn erase_initialisation_code(&self) -> Result<(), Error> {
self.repository.erase_initialisation_code()
.map_err(|error| Error::new(
ErrorKind::Other,
format!("Unable to erase the initialisation code… Cause: {}", error)
))
}
fn clone_box(&self) -> Box<dyn ConfigurationPort> {
Box::new(self.clone())
}
}
@@ -0,0 +1,2 @@
pub mod adapter;
pub mod repository;
@@ -0,0 +1,75 @@
use std::sync::{Arc, Mutex, MutexGuard};
use rusqlite::{Connection, Error};
const CONFIGURATION_ID: &'static str = "1";
#[derive(Clone)]
pub struct ConfigurationSqliteRepository {
pub database_connection: Arc<Mutex<Connection>>
}
impl ConfigurationSqliteRepository {
pub fn new(database_connection: Arc<Mutex<Connection>>) -> Self {
Self { database_connection }
}
fn get_connection(&self) -> MutexGuard<'_, Connection> {
self.database_connection.lock().unwrap()
}
pub fn create_table(&self) -> Result<(), Error> {
let connection = self.get_connection();
connection.execute(
"CREATE TABLE IF NOT EXISTS configuration (
id TEXT PRIMARY KEY,
initialisation_code VARCHAR
);",
[]
)?;
Ok(())
}
pub fn get_initialisation_code(&self) -> Result<Option<String>, Error> {
let connection = self.get_connection();
let mut prepared_statement = connection.prepare(
"SELECT initialisation_code
FROM configuration
WHERE id = ?1;"
).expect("Unable to prepare statement to get initialisation code…");
let mut rows = prepared_statement.query([CONFIGURATION_ID])
.expect("Unable to retrieve initialisation code…");
if let Some(row) = rows.next()? {
let initialisation_code = row.get(0)?;
Ok(initialisation_code)
} else {
Ok(None)
}
}
pub fn save(&self, initialisation_code: String) -> Result<(), Error> {
let connection = self.get_connection();
let mut prepared_statement = connection.prepare(
"INSERT INTO configuration (
id,
initialisation_code
) VALUES (?1, ?2);"
).expect("Unable to prepare statement for configuration saving…");
prepared_statement.execute(rusqlite::params![
CONFIGURATION_ID,
initialisation_code
]).expect("Unable to insert configuration…");
Ok(())
}
pub fn erase_initialisation_code(&self) -> Result<(), Error> {
let connection = self.get_connection();
let mut prepared_statement = connection.prepare(
"UPDATE configuration SET initialisation_code = NULL WHERE id = ?1;"
).expect("Unable to prepare statement to erase initialisation code…");
prepared_statement.execute(rusqlite::params![CONFIGURATION_ID]).expect("Unable to erase initialisation code…");
Ok(())
}
}
+4
View File
@@ -0,0 +1,4 @@
pub mod common;
pub mod configuration;
pub mod schema;
pub mod user;
+21
View File
@@ -0,0 +1,21 @@
use diesel::prelude::*;
// Define the user table structure matching the current SQL schema
table! {
user (id) {
id -> Text,
username -> Varchar,
encrypted_password -> Varchar,
}
}
// Add system_resources table definition
table! {
system_resources (id) {
id -> Text,
datetime -> Integer,
cpu_usage -> Float,
total_memory -> BigInt,
used_memory -> BigInt,
}
}
@@ -0,0 +1,88 @@
use std::io::{Error, ErrorKind};
use uuid::Uuid;
use surveillant_systeme_domain::user::model::User;
use surveillant_systeme_domain::user::port::UserPort;
use crate::user::repository::UserSqliteRepository;
#[derive(Clone)]
pub struct UserSqliteAdapter {
pub repository: UserSqliteRepository
}
impl UserSqliteAdapter {
pub fn new(repository: UserSqliteRepository) -> Self {
Self {
repository
}
}
}
impl UserPort for UserSqliteAdapter {
fn get_by_id(&self, id: &Uuid) -> Result<Option<User>, Error> {
self.repository.get_by_id(id)
.map_err(|error| Error::new(
ErrorKind::Other,
format!("Unable to get the user by its id… Cause: {}", error)
))
}
fn get_by_username(&self, username: &String) -> Result<Option<User>, Error> {
self.repository.get_by_username(username)
.map_err(|error| Error::new(
ErrorKind::Other,
format!("Unable to get the user by its username… Cause: {}", error)
))
}
fn get_all(&self) -> Result<Vec<User>, Error> {
self.repository.get_all()
.map_err(|error| Error::new(
ErrorKind::Other,
format!("Unable to get all users… Cause: {}", error)
))
}
fn count(&self) -> Result<u8, Error> {
self.repository.count()
.map_err(|error| Error::new(
ErrorKind::Other,
format!("Unable to count users… Cause: {}", error)
))
}
fn exists_by_name(&self, username: &String) -> Result<bool, Error> {
self.repository.exists_by_name(username)
.map_err(|error| Error::new(
ErrorKind::Other,
format!("Unable to check if a user exists by its name… Cause: {}", error)
))
}
fn insert(&self, user: &User) -> Result<(), Error> {
self.repository.insert(user)
.map_err(|error| Error::new(
ErrorKind::Other,
format!("Unable to insert the user… Cause: {}", error)
))
}
fn update(&self, user: &User) -> Result<(), Error> {
self.repository.update(user)
.map_err(|error| Error::new(
ErrorKind::Other,
format!("Unable to update the user… Cause: {}", error)
))
}
fn delete_by_id(&self, id: &Uuid) -> Result<(), Error> {
self.repository.delete_by_id(id)
.map_err(|error| Error::new(
ErrorKind::Other,
format!("Unable to delete the user by its id… Cause: {}", error)
))
}
fn clone_box(&self) -> Box<dyn UserPort> {
Box::new(self.clone())
}
}
@@ -0,0 +1,95 @@
use std::io::{Error, ErrorKind};
use uuid::Uuid;
use surveillant_systeme_domain::user::model::User;
use surveillant_systeme_domain::user::port::UserPort;
use crate::user::diesel_repository::UserDieselRepository;
#[derive(Clone)]
pub struct UserDieselAdapter {
pub repository: UserDieselRepository
}
impl UserDieselAdapter {
pub fn new(repository: UserDieselRepository) -> Self {
Self {
repository
}
}
}
impl UserPort for UserDieselAdapter {
fn get_by_id(&self, id: &Uuid) -> Result<Option<User>, Error> {
match self.repository.get_by_id(id) {
Ok(option_user_entity) => Ok(option_user_entity.map(|user_entity| user_entity.to_domain())),
Err(diesel::result::Error::NotFound) => Ok(None),
Err(e) => Err(Error::new(
ErrorKind::Other,
format!("Unable to get the user by its id… Cause: {}", e)
)),
}
}
fn get_by_username(&self, username: &String) -> Result<Option<User>, Error> {
self.repository.get_by_username(username)
.map_err(|error| Error::new(
ErrorKind::Other,
format!("Unable to get the user by its username… Cause: {}", error)
))
.map(|option_user_entity| option_user_entity.map(|user_entity| user_entity.to_domain()))
}
fn get_all(&self) -> Result<Vec<User>, Error> {
self.repository.get_all()
.map_err(|error| Error::new(
ErrorKind::Other,
format!("Unable to get all users… Cause: {}", error)
))
.map(|user_entities| user_entities.into_iter().map(|user_entity| user_entity.to_domain()).collect())
}
fn count(&self) -> Result<u8, Error> {
self.repository.count()
.map_err(|error| Error::new(
ErrorKind::Other,
format!("Unable to count users… Cause: {}", error)
))
}
fn exists_by_name(&self, username: &String) -> Result<bool, Error> {
self.repository.exists_by_name(username)
.map_err(|error| Error::new(
ErrorKind::Other,
format!("Unable to check if a user exists by its name… Cause: {}", error)
))
}
fn insert(&self, user: &User) -> Result<(), Error> {
let user_entity = crate::user::model::UserEntity::from(user);
self.repository.insert(&user_entity)
.map_err(|error| Error::new(
ErrorKind::Other,
format!("Unable to insert the user… Cause: {}", error)
))
}
fn update(&self, user: &User) -> Result<(), Error> {
let user_entity = crate::user::model::UserEntity::from(user);
self.repository.update(&user_entity)
.map_err(|error| Error::new(
ErrorKind::Other,
format!("Unable to update the user… Cause: {}", error)
))
}
fn delete_by_id(&self, id: &Uuid) -> Result<(), Error> {
self.repository.delete_by_id(id)
.map_err(|error| Error::new(
ErrorKind::Other,
format!("Unable to delete the user by its id… Cause: {}", error)
))
}
fn clone_box(&self) -> Box<dyn UserPort> {
Box::new(self.clone())
}
}
@@ -0,0 +1,115 @@
use diesel::prelude::*;
use diesel::sqlite::SqliteConnection;
use std::sync::{Arc, Mutex};
use uuid::Uuid;
use super::model::UserEntity;
#[derive(Clone)]
pub struct UserDieselRepository {
pub database_connection: Arc<Mutex<SqliteConnection>>,
}
impl UserDieselRepository {
pub fn new(database_connection: Arc<Mutex<SqliteConnection>>) -> Self {
Self {
database_connection,
}
}
fn get_connection(&self) -> std::sync::MutexGuard<'_, SqliteConnection> {
self.database_connection.lock().unwrap()
}
pub fn create_table(&self) -> Result<(), diesel::result::Error> {
let mut connection = self.get_connection();
diesel::sql_query(
"CREATE TABLE IF NOT EXISTS \"user\" (
id TEXT PRIMARY KEY,
username VARCHAR NOT NULL,
encrypted_password VARCHAR NOT NULL
);"
).execute(&mut *connection)?;
Ok(())
}
pub fn insert(&self, user: &UserEntity) -> Result<(), diesel::result::Error> {
let mut connection = self.get_connection();
diesel::insert_into(crate::schema::user::table)
.values(user)
.execute(&mut *connection)?;
Ok(())
}
pub fn update(&self, user: &UserEntity) -> Result<(), diesel::result::Error> {
let mut connection = self.get_connection();
diesel::update(crate::schema::user::table.find(&user.id.to_string()))
.set((crate::schema::user::username.eq(user.username.clone()),
crate::schema::user::encrypted_password.eq(user.encrypted_password.clone())))
.execute(&mut *connection)?;
Ok(())
}
pub fn delete_by_id(&self, id: &Uuid) -> Result<(), diesel::result::Error> {
let mut connection = self.get_connection();
diesel::delete(crate::schema::user::table.find(id.to_string()))
.execute(&mut *connection)?;
Ok(())
}
pub fn count(&self) -> Result<u8, diesel::result::Error> {
let mut connection = self.get_connection();
let count: i64 = crate::schema::user::table.count().get_result(&mut *connection)?;
Ok(count as u8)
}
pub fn get_by_id(&self, id: &Uuid) -> Result<Option<UserEntity>, diesel::result::Error> {
let mut connection = self.get_connection();
let result: Result<UserEntity, _> = crate::schema::user::table
.find(id.to_string())
.first(&mut *connection);
match result {
Ok(user_diesel) => Ok(Some(user_diesel)),
Err(e) => Err(e),
}
}
pub fn exists_by_name(&self, username: &String) -> Result<bool, diesel::result::Error> {
let mut connection = self.get_connection();
let count: i64 = crate::schema::user::table
.filter(crate::schema::user::username.eq(username))
.count()
.get_result(&mut *connection)?;
Ok(count > 0)
}
pub fn get_by_username(&self, username: &String) -> Result<Option<UserEntity>, diesel::result::Error> {
let mut connection = self.get_connection();
let result: Result<UserEntity, _> = crate::schema::user::table
.filter(crate::schema::user::username.eq(username))
.first(&mut *connection);
match result {
Ok(user_diesel) => Ok(Some(user_diesel)),
Err(diesel::result::Error::NotFound) => Ok(None),
Err(e) => Err(e),
}
}
pub fn get_all(&self) -> Result<Vec<UserEntity>, diesel::result::Error> {
let mut connection = self.get_connection();
let users: Vec<UserEntity> = crate::schema::user::table
.order(crate::schema::user::username.asc())
.load(&mut *connection)?;
Ok(users)
}
}
+5
View File
@@ -0,0 +1,5 @@
pub mod adapter;
pub mod diesel_adapter;
pub mod diesel_repository;
pub mod model;
pub mod repository;
+31
View File
@@ -0,0 +1,31 @@
use surveillant_systeme_domain::user::model::User;
use diesel::prelude::*;
use uuid::Uuid;
#[derive(Queryable, Identifiable, Insertable, AsChangeset)]
#[diesel(table_name = crate::schema::user)]
pub struct UserEntity {
pub id: String,
pub username: String,
pub encrypted_password: String,
}
impl UserEntity {
pub fn to_domain(&self) -> User {
User {
id: Uuid::parse_str(&self.id).unwrap(),
username: self.username.clone(),
encrypted_password: self.encrypted_password.clone(),
}
}
}
impl From<&User> for UserEntity {
fn from(user: &User) -> Self {
UserEntity {
id: user.id.to_string(),
username: user.username.clone(),
encrypted_password: user.encrypted_password.clone(),
}
}
}
@@ -0,0 +1,184 @@
use rusqlite::fallible_iterator::FallibleIterator;
use rusqlite::{Connection, Error, Row};
use std::sync::{Arc, Mutex, MutexGuard};
use log::warn;
use uuid::Uuid;
use surveillant_systeme_domain::user::model::User;
#[derive(Clone)]
pub struct UserSqliteRepository {
pub database_connection: Arc<Mutex<Connection>>,
}
impl UserSqliteRepository {
pub fn new(database_connection: Arc<Mutex<Connection>>) -> Self {
Self {
database_connection,
}
}
fn get_connection(&self) -> MutexGuard<'_, Connection> {
self.database_connection.lock().unwrap()
}
pub fn create_table(&self) -> Result<(), Error> {
let connection = self.get_connection();
connection.execute(
"CREATE TABLE IF NOT EXISTS \"user\" (
id TEXT PRIMARY KEY,
username VARCHAR NOT NULL,
encrypted_password VARCHAR NOT NULL
);",
[]
)?;
Ok(())
}
pub fn insert(&self, user: &User) -> rusqlite::Result<()> {
let database_connection = self.get_connection();
let mut prepared_statement = database_connection
.prepare(
"INSERT INTO \"user\" (
id,
username,
encrypted_password
) VALUES (?1, ?2, ?3);",
)
.expect("Unable to prepare statement for user saving…");
prepared_statement.execute(rusqlite::params![
user.id.to_string(),
user.username,
user.encrypted_password
]).expect("Unable to insert user.");
Ok(())
}
pub fn update(&self, user: &User) -> rusqlite::Result<()> {
let database_connection = self.get_connection();
let mut prepared_statement = database_connection
.prepare(
"UPDATE \"user\" SET
username = ?1,
encrypted_password = ?2
WHERE id = ?3;",
)
.expect("Unable to prepare statement for user saving…");
prepared_statement.execute(rusqlite::params![
user.username,
user.encrypted_password,
user.id.to_string()
]).expect("Unable to update user.");
Ok(())
}
pub fn delete_by_id(&self, id: &Uuid) -> rusqlite::Result<()> {
let database_connection = self.get_connection();
let mut prepared_statement = database_connection
.prepare("DELETE FROM \"user\" WHERE id = ?1;")
.expect("Unable to prepare statement for user deletion…");
prepared_statement.execute(rusqlite::params![id.to_string()])
.expect("Unable to delete the user…");
Ok(())
}
pub fn count(&self) -> rusqlite::Result<u8> {
let database_connection = self.get_connection();
let mut prepared_statement = database_connection
.prepare("SELECT COUNT(1) FROM \"user\";")
.expect("Unable to prepare statement for user counting…");
let mut rows = prepared_statement.query([])?;
if let Some(row) = rows.next()? {
Ok(row.get(0)?)
} else {
Ok(0)
}
}
pub fn get_by_id(&self, id: &Uuid) -> rusqlite::Result<Option<User>> {
let database_connection = self.get_connection();
let mut prepared_statement = database_connection.prepare(
"SELECT
id,
username,
encrypted_password
FROM \"user\"\
WHERE id = ?1;"
)?;
let mut rows = prepared_statement.query([id.to_string()])?;
if let Some(row) = rows.next()? {
Ok(Some(Self::map_to_user(&row)?))
} else {
Ok(None)
}
}
pub fn exists_by_name(&self, username: &String) -> rusqlite::Result<bool> {
let database_connection = self.get_connection();
let mut prepared_statement = database_connection.prepare(
"SELECT EXISTS(
SELECT 1
FROM \"user\"
WHERE username = ?1\
);"
)?;
let mut rows = prepared_statement.query([username])?;
if let Some(row) = rows.next()? {
Ok(row.get(0)?)
} else {
warn!("No result for exists_by_name repository function.");
Ok(false)
}
}
pub fn get_by_username(&self, username: &String) -> rusqlite::Result<Option<User>> {
let database_connection = self.get_connection();
let mut prepared_statement = database_connection.prepare(
"SELECT
id,
username,
encrypted_password
FROM \"user\"\
WHERE username = ?1;"
)?;
let mut rows = prepared_statement.query([username])?;
if let Some(row) = rows.next()? {
Ok(Some(Self::map_to_user(&row)?))
} else {
Ok(None)
}
}
fn map_to_user(row: &Row) -> Result<User, Error> {
let id_text: String = row.get(0)?;
Ok(User {
id: Uuid::parse_str(&id_text).unwrap(),
username: row.get(1)?,
encrypted_password: row.get(2)?,
})
}
pub fn get_all(&self) -> Result<Vec<User>, Error> {
let database_connection = self.get_connection();
let mut prepared_statement = database_connection.prepare(
"SELECT
id,
username,
encrypted_password
FROM \"user\"",
)?;
let rows = prepared_statement.query([])?;
rows.map(UserSqliteRepository::map_to_user).collect::<Vec<User>>()
}
}