Initial commit.
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
**/target
|
||||
.idea
|
||||
backend/launcher/lumiere.sqlite
|
||||
backend/launcher/.env
|
||||
backend/launcher/certs
|
||||
frontend/.angular
|
||||
frontend/node_modules
|
||||
Generated
+1376
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "lumiere-application"
|
||||
version = "1.0.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
name = "surveillant_systeme_application"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[test]]
|
||||
name = "user_service_test"
|
||||
path = "tests/user_service_test.rs"
|
||||
|
||||
[dependencies]
|
||||
lumiere-domain = { path = "../domain" }
|
||||
|
||||
bcrypt = "0.19.0"
|
||||
chrono = { version = "0.4.44", features = ["serde"] }
|
||||
log = "0.4.29"
|
||||
jsonwebtoken = { version = "10.3.0", features = ["rust_crypto"] }
|
||||
rand = "0.10.1"
|
||||
uuid = { version = "1.23.1", features = ["v4"] }
|
||||
@@ -0,0 +1 @@
|
||||
pub mod service;
|
||||
@@ -0,0 +1,46 @@
|
||||
use std::io::{Error, ErrorKind};
|
||||
use rand::distr::Alphanumeric;
|
||||
use rand::RngExt;
|
||||
use surveillant_systeme_domain::configuration::port::ConfigurationPort;
|
||||
|
||||
pub struct ConfigurationService {
|
||||
configuration_port: Box<dyn ConfigurationPort>
|
||||
}
|
||||
|
||||
impl ConfigurationService {
|
||||
pub fn new(configuration_repository: Box<dyn ConfigurationPort>) -> Self {
|
||||
Self { configuration_port: configuration_repository }
|
||||
}
|
||||
|
||||
pub fn create_initialisation_code(&self) -> Result<(), Error> {
|
||||
let initialisation_code = random_string();
|
||||
self.configuration_port.save(initialisation_code)
|
||||
}
|
||||
|
||||
pub fn get_initialisation_code(&self) -> Result<Option<String>, Error> {
|
||||
self.configuration_port.get_initialisation_code()
|
||||
.map_err(|database_error| Error::new(
|
||||
ErrorKind::Other,
|
||||
format!("Unable to retrieve the initialisation code… Cause: {}", database_error)
|
||||
))
|
||||
}
|
||||
|
||||
pub fn set_database_initialised(&self) -> Result<(), Error> {
|
||||
self.configuration_port.erase_initialisation_code()
|
||||
.map_err(|database_error| Error::new(
|
||||
ErrorKind::Other,
|
||||
format!("Unable to erase the initialisation code in database… Cause: {}", database_error)
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for ConfigurationService {
|
||||
fn clone(&self) -> Self {
|
||||
ConfigurationService::new(self.configuration_port.clone())
|
||||
}
|
||||
}
|
||||
|
||||
fn random_string() -> String {
|
||||
let mut rng = rand::rng();
|
||||
(0..10).map(|_| rng.sample(Alphanumeric) as char).collect()
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod configuration;
|
||||
pub mod user;
|
||||
@@ -0,0 +1,17 @@
|
||||
use jsonwebtoken::{encode, EncodingKey, Header};
|
||||
use std::env;
|
||||
use surveillant_systeme_domain::user::model::Claims;
|
||||
|
||||
fn get_jwt_secret() -> String {
|
||||
env::var("JWT_SECRET").expect("JWT_SECRET must be set.")
|
||||
}
|
||||
|
||||
pub fn generate_jwt_token(user_id: &str) -> Result<String, jsonwebtoken::errors::Error> {
|
||||
let jwt_secret = get_jwt_secret();
|
||||
let claims = Claims::new(user_id.to_string());
|
||||
encode(
|
||||
&Header::default(),
|
||||
&claims,
|
||||
&EncodingKey::from_secret(jwt_secret.as_bytes())
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod service;
|
||||
mod auth;
|
||||
@@ -0,0 +1,169 @@
|
||||
use std::env;
|
||||
use std::io::{Error, ErrorKind};
|
||||
use bcrypt::{hash_with_salt, verify};
|
||||
use chrono::Utc;
|
||||
use uuid::Uuid;
|
||||
use surveillant_systeme_domain::user::model::{LoginResponse, RefreshToken, User};
|
||||
use crate::configuration::service::ConfigurationService;
|
||||
use crate::user::auth::generate_jwt_token;
|
||||
use surveillant_systeme_domain::user::port::UserPort;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct UserService {
|
||||
configuration_service: ConfigurationService,
|
||||
user_port: Box<dyn UserPort>,
|
||||
}
|
||||
|
||||
impl UserService {
|
||||
pub fn new(configuration_service: ConfigurationService, user_port: Box<dyn UserPort>) -> Self {
|
||||
Self { configuration_service, user_port }
|
||||
}
|
||||
|
||||
pub fn initialise(
|
||||
&self,
|
||||
user_initialisation_code: &String,
|
||||
username: &String,
|
||||
password: &String,
|
||||
) -> Result<(), Error> {
|
||||
match self.configuration_service.get_initialisation_code()? {
|
||||
None => Err(Error::new(ErrorKind::InvalidInput, "Application has already been initialised.")),
|
||||
Some(system_initialisation_code) => {
|
||||
let is_initialisation_code_valid = system_initialisation_code
|
||||
.eq(&user_initialisation_code.to_string());
|
||||
if is_initialisation_code_valid {
|
||||
self.create_user(username, password)
|
||||
.expect("Unable to create the first user.");
|
||||
self.configuration_service.set_database_initialised()
|
||||
} else {
|
||||
Err(Error::new(ErrorKind::InvalidInput, "Invalid initialisation code!"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub fn create_user(&self, username: &String, password: &String) -> Result<(), Error> {
|
||||
let new_user = User::new(
|
||||
Uuid::new_v4(),
|
||||
username.to_string(),
|
||||
Self::encrypt_password(&password),
|
||||
);
|
||||
|
||||
self.user_port
|
||||
.insert(&new_user)
|
||||
.expect("Unable to insert new user.");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_user(&self, user_id: Uuid, new_username: String) -> Result<(), Error> {
|
||||
let existing_user = self.user_port.get_by_id(&user_id)
|
||||
.map_err(|_| Error::new(ErrorKind::Other, "An error occurred while retrieving the user to update it."))?
|
||||
.ok_or(Error::new(ErrorKind::InvalidInput, "User does not exist."))?;
|
||||
|
||||
if existing_user.username.eq(&new_username) {
|
||||
Err(Error::new(ErrorKind::InvalidInput, "New username is unchanged."))?;
|
||||
}
|
||||
|
||||
let username_already_exist_for_another_user = self.user_port.exists_by_name(&new_username)
|
||||
.map_err(|_| Error::new(ErrorKind::Other, "An error occurred while checking if the new username is available."))?;
|
||||
if username_already_exist_for_another_user {
|
||||
Err(Error::new(ErrorKind::InvalidInput, "New username is already used."))?;
|
||||
}
|
||||
|
||||
let updated_user = User::new(
|
||||
existing_user.id,
|
||||
new_username,
|
||||
existing_user.encrypted_password
|
||||
);
|
||||
self.user_port.update(&updated_user)
|
||||
.expect("An error occurred while saving user updates.");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_user_password(&self, user_id: Uuid, new_password: String) -> Result<(), Error> {
|
||||
let existing_user = self.user_port.get_by_id(&user_id)
|
||||
.map_err(|_| Error::new(ErrorKind::Other, "An error occurred while retrieving the user to update it."))?
|
||||
.expect("User does not exist.");
|
||||
|
||||
let new_encrypted_password = Self::encrypt_password(&new_password);
|
||||
let updated_user = User::new(
|
||||
existing_user.id,
|
||||
existing_user.username,
|
||||
new_encrypted_password
|
||||
);
|
||||
self.user_port.update(&updated_user)
|
||||
.expect("An error occurred while saving user updates.");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn delete_by_id(&self, authenticated_user_id: &Uuid, user_to_delete_id: &Uuid) -> Result<(), Error> {
|
||||
if authenticated_user_id == user_to_delete_id {
|
||||
Err(Error::new(ErrorKind::InvalidInput, "Authenticated user cannot delete himself."))?
|
||||
}
|
||||
|
||||
let is_last_user = self.user_port.count()
|
||||
.map_err(|_| Error::new(ErrorKind::Other, "An error occurred while counting existing users…"))? == 1;
|
||||
if is_last_user {
|
||||
Err(Error::new(ErrorKind::InvalidInput, "The last existing user cannot be deleted."))?
|
||||
}
|
||||
|
||||
self.user_port.delete_by_id(user_to_delete_id)
|
||||
.map_err(|_| Error::new(ErrorKind::Other, "An error occurred while deleting the user."))
|
||||
}
|
||||
|
||||
pub fn login(&self, username: &String, password: &String) -> Result<LoginResponse, Error> {
|
||||
let existing_user = self
|
||||
.user_port
|
||||
.get_by_username(username)
|
||||
.map_err(|_| Error::new(ErrorKind::InvalidInput, "Username or password incorrect."))?;
|
||||
|
||||
let user = existing_user.ok_or_else(|| Error::new(ErrorKind::InvalidInput, "Username or password incorrect."))?;
|
||||
|
||||
let passwords_match = verify(password, user.encrypted_password.as_str())
|
||||
.map_err(|_| Error::new(ErrorKind::InvalidInput, "Username or password incorrect."))?;
|
||||
|
||||
if passwords_match {
|
||||
Self::generate_login_response(&user)
|
||||
} else {
|
||||
Err(Error::new(ErrorKind::InvalidInput, "Username or password incorrect."))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_all_users(&self) -> Result<Vec<User>, Error> {
|
||||
let users = self
|
||||
.user_port
|
||||
.get_all()
|
||||
.expect("Unable to retrieve all users.");
|
||||
Ok(users)
|
||||
}
|
||||
|
||||
fn encrypt_password(password: &String) -> String {
|
||||
let salt = env::var("BCRYPT_SALT").expect("BCRYPT_SALT must be set!");
|
||||
hash_with_salt(password, 12, string_to_salt(salt))
|
||||
.map(|hash_parts| hash_parts.to_string())
|
||||
.expect("Unable to encrypt password.")
|
||||
}
|
||||
|
||||
fn generate_login_response(user: &User) -> Result<LoginResponse, Error> {
|
||||
let jwt = generate_jwt_token(&user.id.to_string()).expect("Unable to generate a JWT…");
|
||||
|
||||
let response = LoginResponse {
|
||||
token_type: "Bearer".to_string(),
|
||||
access_token: jwt,
|
||||
refresh_token: RefreshToken {
|
||||
user_id: user.id,
|
||||
value: "<empty>".to_string(),
|
||||
expiration_date: Utc::now(),
|
||||
}
|
||||
};
|
||||
Ok(response)
|
||||
}
|
||||
}
|
||||
|
||||
fn string_to_salt(salt_str: String) -> [u8; 16] {
|
||||
let mut salt = [0u8; 16];
|
||||
let bytes = salt_str.as_bytes();
|
||||
let len = std::cmp::min(bytes.len(), 16);
|
||||
salt[..len].copy_from_slice(&bytes[..len]);
|
||||
salt
|
||||
}
|
||||
@@ -0,0 +1,317 @@
|
||||
# System Resources ORM Setup - Diesel Implementation
|
||||
|
||||
This document provides all the code needed to convert the `system_resource` entity from raw SQL queries to Diesel ORM, following the same pattern used for the user entity.
|
||||
|
||||
## Current State Analysis
|
||||
|
||||
The system_resources entity currently has:
|
||||
- ✅ Diesel model defined in `infrastructure/src/system_resources/model.rs`
|
||||
- ❌ No Diesel repository implementation (only SQL-based)
|
||||
- ❌ No Diesel adapter implementation (only SQL-based)
|
||||
- ❌ Missing table definition in `schema.rs`
|
||||
|
||||
## Step 1: Add system_resources Table Definition to schema.rs
|
||||
|
||||
**File**: `infrastructure/src/schema.rs`
|
||||
|
||||
Add the following table definition after the user table:
|
||||
|
||||
```rust
|
||||
// Define the system_resources table structure matching the current SQL schema
|
||||
table! {
|
||||
system_resources (id) {
|
||||
id -> Text,
|
||||
datetime -> Integer,
|
||||
cpu_usage -> Float,
|
||||
total_memory -> Integer,
|
||||
used_memory -> Integer,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Step 2: Create Diesel Repository Implementation
|
||||
|
||||
**File**: `infrastructure/src/system_resources/diesel_repository.rs`
|
||||
|
||||
```rust
|
||||
use diesel::prelude::*;
|
||||
use diesel::sqlite::SqliteConnection;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use chrono::{DateTime, Utc};
|
||||
use super::model::SystemResourcesEntity;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SystemResourcesDieselRepository {
|
||||
pub database_connection: Arc<Mutex<SqliteConnection>>,
|
||||
}
|
||||
|
||||
impl SystemResourcesDieselRepository {
|
||||
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 system_resources (
|
||||
id TEXT PRIMARY KEY,
|
||||
datetime INTEGER NOT NULL,
|
||||
cpu_usage REAL NOT NULL,
|
||||
total_memory INTEGER NOT NULL,
|
||||
used_memory INTEGER NOT NULL
|
||||
);"
|
||||
).execute(&mut *connection)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn insert(&self, resource: &SystemResourcesEntity) -> Result<(), diesel::result::Error> {
|
||||
let mut connection = self.get_connection();
|
||||
|
||||
diesel::insert_into(crate::schema::system_resources::table)
|
||||
.values(resource)
|
||||
.execute(&mut *connection)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_by_id(&self, id: &str) -> Result<Option<SystemResourcesEntity>, diesel::result::Error> {
|
||||
let mut connection = self.get_connection();
|
||||
|
||||
let result: Result<SystemResourcesEntity, _> = crate::schema::system_resources::table
|
||||
.find(id)
|
||||
.first(&mut *connection);
|
||||
|
||||
match result {
|
||||
Ok(resource_diesel) => Ok(Some(resource_diesel)),
|
||||
Err(diesel::result::Error::NotFound) => Ok(None),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_between(&self, start: i64, end: i64) -> Result<Vec<SystemResourcesEntity>, diesel::result::Error> {
|
||||
let mut connection = self.get_connection();
|
||||
|
||||
let resources: Vec<SystemResourcesEntity> = crate::schema::system_resources::table
|
||||
.filter(crate::schema::system_resources::datetime.between(start, end))
|
||||
.order(crate::schema::system_resources::datetime.asc())
|
||||
.load(&mut *connection)?;
|
||||
|
||||
Ok(resources)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Step 3: Create Diesel Adapter Implementation
|
||||
|
||||
**File**: `infrastructure/src/system_resources/diesel_adapter.rs`
|
||||
|
||||
```rust
|
||||
use std::io::{Error, ErrorKind};
|
||||
use chrono::{DateTime, Utc};
|
||||
use surveillant_systeme_domain::system_resources::model::SystemResources;
|
||||
use surveillant_systeme_domain::system_resources::port::SystemResourcesPort;
|
||||
use crate::system_resources::diesel_repository::SystemResourcesDieselRepository;
|
||||
use sysinfo::{CpuRefreshKind, MemoryRefreshKind, RefreshKind, System};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SystemResourcesDieselAdapter {
|
||||
pub repository: SystemResourcesDieselRepository
|
||||
}
|
||||
|
||||
impl SystemResourcesDieselAdapter {
|
||||
pub fn new(repository: SystemResourcesDieselRepository) -> Self {
|
||||
Self {
|
||||
repository
|
||||
}
|
||||
}
|
||||
|
||||
fn get_cpu_usage_from_shell(&self, _: &System) -> f32 {
|
||||
use std::process::Command;
|
||||
use log::error;
|
||||
|
||||
if cfg!(target_os = "linux") {
|
||||
self.get_cpu_usage_from_command("echo $[100-$(vmstat 1 2|tail -1|awk '{print $15}')]")
|
||||
} else if cfg!(target_os = "macos") {
|
||||
self.get_cpu_usage_from_command("top -l 1 | grep -E \"^CPU\" | cut -d ' ' -f3 | cut -d '%' -f1")
|
||||
} else {
|
||||
error!("Unhandled OS…");
|
||||
0f32
|
||||
}
|
||||
}
|
||||
|
||||
fn get_cpu_usage_from_command(&self, command: &str) -> f32 {
|
||||
use std::process::Command;
|
||||
|
||||
let command_output = Command::new("sh")
|
||||
.arg("-c")
|
||||
.arg(command)
|
||||
.output()
|
||||
.expect("Error while retrieving CPU usage from command…");
|
||||
|
||||
if command_output.status.success() {
|
||||
let string = String::from_utf8(command_output.stdout).unwrap();
|
||||
string.trim().parse().unwrap()
|
||||
} else {
|
||||
let stderr = String::from_utf8(command_output.stderr).unwrap();
|
||||
error!("Command failed with error:\n{}", stderr);
|
||||
0f32
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SystemResourcesPort for SystemResourcesDieselAdapter {
|
||||
fn insert_system_resources(&self, system_resources: &SystemResources) -> Result<(), Error> {
|
||||
let resource_entity = crate::system_resources::model::SystemResourcesEntity::from(system_resources);
|
||||
self.repository.insert(&resource_entity)
|
||||
.map_err(|error| Error::new(
|
||||
ErrorKind::Other,
|
||||
format!("Unable to insert the system resources… Cause: {}", error)
|
||||
))
|
||||
}
|
||||
|
||||
fn get_system_resources_lines_between(&self, start: &DateTime<Utc>, end: &DateTime<Utc>) -> Result<Vec<SystemResources>, Error> {
|
||||
self.repository.get_between(start.timestamp(), end.timestamp())
|
||||
.map_err(|error| Error::new(
|
||||
ErrorKind::Other,
|
||||
format!("Unable to retrieve the system resources in the given period… Cause: {}", error)
|
||||
))
|
||||
.map(|resource_entities| resource_entities.into_iter().map(|resource_entity| resource_entity.to_domain()).collect())
|
||||
}
|
||||
|
||||
fn get_system_resources(&self) -> Result<SystemResources, Error> {
|
||||
let system = System::new_with_specifics(
|
||||
RefreshKind::nothing().with_cpu(CpuRefreshKind::everything()).with_memory(MemoryRefreshKind::everything()),
|
||||
);
|
||||
let cpu_usage = self.get_cpu_usage_from_shell(&system);
|
||||
let total_memory = system.total_memory();
|
||||
let used_memory = system.used_memory();
|
||||
|
||||
let result = SystemResources::new(
|
||||
cpu_usage,
|
||||
total_memory,
|
||||
used_memory,
|
||||
);
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn SystemResourcesPort> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Step 4: Update Module Files
|
||||
|
||||
**File**: `infrastructure/src/system_resources/mod.rs`
|
||||
|
||||
Add the new Diesel implementations to the module exports:
|
||||
|
||||
```rust
|
||||
// Add these lines to expose the new Diesel implementations
|
||||
pub mod diesel_repository;
|
||||
pub mod diesel_adapter;
|
||||
|
||||
// Re-export the types for convenience
|
||||
pub use diesel_repository::SystemResourcesDieselRepository;
|
||||
pub use diesel_adapter::SystemResourcesDieselAdapter;
|
||||
```
|
||||
|
||||
## Step 5: Update Cargo.toml Dependencies
|
||||
|
||||
**File**: `infrastructure/Cargo.toml`
|
||||
|
||||
Ensure Diesel dependencies are properly configured. The file should include:
|
||||
|
||||
```toml
|
||||
diesel = { version = "2.1", features = ["sqlite"] }
|
||||
diesel_migrations = "2.1"
|
||||
```
|
||||
|
||||
## Step 6: Update Database Initialization
|
||||
|
||||
**File**: `infrastructure/src/common/database_initialisation.rs`
|
||||
|
||||
Update the initialization code to use Diesel for creating tables:
|
||||
|
||||
```rust
|
||||
// Replace the existing system_resources table creation with Diesel version
|
||||
impl DatabaseInitialisation {
|
||||
pub fn create_tables(&self) -> Result<(), Error> {
|
||||
// User table (already using Diesel)
|
||||
let user_repository = crate::user::diesel_repository::UserDieselRepository::new(self.database_connection.clone());
|
||||
user_repository.create_table()?;
|
||||
|
||||
// System resources table (now using Diesel)
|
||||
let system_resources_repository = crate::system_resources::diesel_repository::SystemResourcesDieselRepository::new(self.database_connection.clone());
|
||||
system_resources_repository.create_table()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Step 7: Update Application Context
|
||||
|
||||
**File**: `launcher/src/application_context.rs`
|
||||
|
||||
Update the application context to use Diesel adapters:
|
||||
|
||||
```rust
|
||||
// Replace system resources adapter initialization
|
||||
let system_resources_repository = SystemResourcesDieselRepository::new(database_connection.clone());
|
||||
let system_resources_adapter = SystemResourcesDieselAdapter::new(system_resources_repository);
|
||||
```
|
||||
|
||||
## Migration Strategy
|
||||
|
||||
### Phase 1: Parallel Implementation (Current Step)
|
||||
1. ✅ Create Diesel model (already done)
|
||||
2. ✅ Add table definition to schema.rs
|
||||
3. ✅ Implement SystemResourcesDieselRepository
|
||||
4. ✅ Implement SystemResourcesDieselAdapter
|
||||
5. ✅ Update module exports
|
||||
|
||||
### Phase 2: Integration
|
||||
1. Update database initialization to use Diesel
|
||||
2. Update application context to use Diesel adapters
|
||||
3. Test Diesel implementation alongside existing SQL version
|
||||
|
||||
### Phase 3: Validation
|
||||
1. Verify identical behavior through manual testing
|
||||
2. Check performance characteristics
|
||||
3. Ensure all system resources API endpoints work correctly
|
||||
|
||||
### Phase 4: Cleanup (Future)
|
||||
1. Remove old SQL-based adapter implementation
|
||||
2. Remove old SQL-based repository implementation
|
||||
3. Update all imports to use Diesel versions
|
||||
|
||||
## Key Differences from User Implementation
|
||||
|
||||
1. **Model Differences**: SystemResourcesEntity uses different field types:
|
||||
- `datetime` as i64 (Unix timestamp) instead of DateTime
|
||||
- `cpu_usage` as f32 instead of String
|
||||
- Memory fields as i64 instead of other types
|
||||
|
||||
2. **Repository Method Differences**:
|
||||
- No equivalent to `get_by_username` (system resources use datetime ranges)
|
||||
- `get_between` method for time-based queries instead of ID/username lookups
|
||||
- Different query patterns for temporal data
|
||||
|
||||
3. **Adapter Method Differences**:
|
||||
- Additional `get_system_resources()` method that collects live system data
|
||||
- Time-based range queries instead of single entity lookups
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. ✅ SystemResourcesDieselRepository implements all necessary methods
|
||||
2. ✅ SystemResourcesDieselAdapter provides same functionality as SQL version
|
||||
3. ✅ All system resources API endpoints work identically with Diesel backend
|
||||
4. ✅ No data migration required - existing system resources continue to work
|
||||
5. ✅ Performance characteristics meet or exceed current implementation
|
||||
6. ✅ Error handling produces same error types and messages
|
||||
Generated
+610
@@ -0,0 +1,610 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.102"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
|
||||
dependencies = [
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasip2",
|
||||
"wasip3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||
dependencies = [
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "id-arena"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.17.0",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "leb128fmt"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.185"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.2.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.149"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "lumiere-domain"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"serde",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.23.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"js-sys",
|
||||
"serde_core",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.2+wasi-0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasip3"
|
||||
version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.118"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.118"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.118"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.118"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
|
||||
dependencies = [
|
||||
"leb128fmt",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-metadata"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"indexmap",
|
||||
"wasm-encoder",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"hashbrown 0.15.5",
|
||||
"indexmap",
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.62.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
||||
dependencies = [
|
||||
"wit-bindgen-rust-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-core"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck",
|
||||
"wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck",
|
||||
"indexmap",
|
||||
"prettyplease",
|
||||
"syn",
|
||||
"wasm-metadata",
|
||||
"wit-bindgen-core",
|
||||
"wit-component",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust-macro"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wit-bindgen-core",
|
||||
"wit-bindgen-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-component"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags",
|
||||
"indexmap",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"wasm-encoder",
|
||||
"wasm-metadata",
|
||||
"wasmparser",
|
||||
"wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-parser"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"id-arena",
|
||||
"indexmap",
|
||||
"log",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"unicode-xid",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "lumiere-domain"
|
||||
version = "1.0.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
name = "surveillant_systeme_domain"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
chrono = { version = "0.4.44", features = ["serde"] }
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
uuid = { version = "1.23.1", features = ["v4", "serde"] }
|
||||
@@ -0,0 +1 @@
|
||||
pub mod port;
|
||||
@@ -0,0 +1,14 @@
|
||||
use std::io::Error;
|
||||
|
||||
pub trait ConfigurationPort: Send + Sync {
|
||||
fn get_initialisation_code(&self) -> Result<Option<String>, Error>;
|
||||
fn save(&self, initialisation_code: String) -> Result<(), Error>;
|
||||
fn erase_initialisation_code(&self) -> Result<(), Error>;
|
||||
fn clone_box(&self) -> Box<dyn ConfigurationPort>;
|
||||
}
|
||||
|
||||
impl Clone for Box<dyn ConfigurationPort> {
|
||||
fn clone(&self) -> Self {
|
||||
self.clone_box()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod configuration;
|
||||
pub mod user;
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod model;
|
||||
pub mod port;
|
||||
@@ -0,0 +1,47 @@
|
||||
use chrono::{DateTime, Duration, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct User {
|
||||
pub id: Uuid,
|
||||
pub username: String,
|
||||
pub encrypted_password: String,
|
||||
}
|
||||
|
||||
impl User {
|
||||
pub fn new(id: Uuid, username: String, encrypted_password: String) -> Self {
|
||||
Self { id, username, encrypted_password }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LoginResponse {
|
||||
pub token_type: String,
|
||||
pub access_token: String,
|
||||
pub refresh_token: RefreshToken,
|
||||
}
|
||||
|
||||
pub struct RefreshToken {
|
||||
pub user_id: Uuid,
|
||||
pub value: String,
|
||||
pub expiration_date: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Claims {
|
||||
pub sub: String,
|
||||
pub exp: usize,
|
||||
}
|
||||
|
||||
impl Claims {
|
||||
pub fn new(user_id: String) -> Self {
|
||||
let expiration = Utc::now()
|
||||
.checked_add_signed(Duration::minutes(10))
|
||||
.expect("Invalid timestamp")
|
||||
.timestamp();
|
||||
Self {
|
||||
sub: user_id,
|
||||
exp: expiration as usize
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
use std::io::Error;
|
||||
use uuid::Uuid;
|
||||
use crate::user::model::User;
|
||||
|
||||
pub trait UserPort: Send + Sync {
|
||||
fn get_by_id(&self, id: &Uuid) -> Result<Option<User>, Error>;
|
||||
fn get_by_username(&self, username: &String) -> Result<Option<User>, Error>;
|
||||
fn get_all(&self) -> Result<Vec<User>, Error>;
|
||||
fn count(&self) -> Result<u8, Error>;
|
||||
fn exists_by_name(&self, username: &String) -> Result<bool, Error>;
|
||||
fn insert(&self, user: &User) -> Result<(), Error>;
|
||||
fn update(&self, user: &User) -> Result<(), Error>;
|
||||
fn delete_by_id(&self, id: &Uuid) -> Result<(), Error>;
|
||||
fn clone_box(&self) -> Box<dyn UserPort>;
|
||||
}
|
||||
|
||||
impl Clone for Box<dyn UserPort> {
|
||||
fn clone(&self) -> Self {
|
||||
self.clone_box()
|
||||
}
|
||||
}
|
||||
Generated
+2627
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "lumiere-exposition"
|
||||
version = "1.0.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
name = "surveillant_systeme_exposition"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
lumiere-domain = { path = "../domain" }
|
||||
lumiere-application = { path = "../application" }
|
||||
|
||||
actix-web = { version = "4.13.0", features = ["rustls-0_23"] }
|
||||
actix-web-httpauth = "0.8.2"
|
||||
chrono = "0.4.44"
|
||||
jsonwebtoken = { version = "10.3.0", features = ["rust_crypto"] }
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
uuid = { version = "1.23.0", features = ["v4", "serde"] }
|
||||
@@ -0,0 +1,36 @@
|
||||
extern crate core;
|
||||
|
||||
use crate::user::handler::{create_new_user, delete_by_id, get_all_users, initialise, login, update_user, update_user_password};
|
||||
use crate::user::validator::validator;
|
||||
use actix_web::web::ServiceConfig;
|
||||
use actix_web::{get, web, HttpResponse, Responder};
|
||||
use actix_web_httpauth::middleware::HttpAuthentication;
|
||||
|
||||
pub mod user;
|
||||
|
||||
#[get("/health")]
|
||||
async fn health() -> impl Responder {
|
||||
"Ok!"
|
||||
}
|
||||
|
||||
pub fn init(cfg: &mut ServiceConfig) {
|
||||
cfg.service(health)
|
||||
.service(health)
|
||||
.service(initialise)
|
||||
.service(login)
|
||||
.service(
|
||||
web::scope("")
|
||||
.wrap(HttpAuthentication::bearer(validator))
|
||||
.service(create_new_user)
|
||||
.service(update_user)
|
||||
.service(update_user_password)
|
||||
.service(get_all_users)
|
||||
.service(delete_by_id)
|
||||
);
|
||||
|
||||
cfg.default_service(web::to(not_found));
|
||||
}
|
||||
|
||||
async fn not_found() -> impl Responder {
|
||||
HttpResponse::NotFound().finish()
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
use actix_web::{delete, get, post, put, web, HttpMessage, HttpRequest, HttpResponse, Responder};
|
||||
use std::io::ErrorKind;
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
use surveillant_systeme_application::user::service::UserService;
|
||||
use surveillant_systeme_domain::user::model::Claims;
|
||||
use crate::user::model::request::{CreateFirstUserRequestDto, CreateUserRequestDto, LoginRequestDto, UpdateUserPasswordRequestDto, UpdateUserRequestDto};
|
||||
use crate::user::model::response::{LoginResponseDto, UserDto};
|
||||
|
||||
#[post("/api/login")]
|
||||
pub async fn login(
|
||||
user_service: web::Data<Arc<UserService>>,
|
||||
login_request: web::Json<LoginRequestDto>,
|
||||
) -> impl Responder {
|
||||
let login_result = user_service.login(
|
||||
&login_request.username.to_string(),
|
||||
&login_request.password.to_string(),
|
||||
);
|
||||
|
||||
match login_result {
|
||||
Ok(login_response) => HttpResponse::Ok().json(LoginResponseDto::new(&login_response)),
|
||||
Err(error) => match error.kind() {
|
||||
ErrorKind::InvalidInput => HttpResponse::BadRequest().body(error.to_string()),
|
||||
_ => HttpResponse::InternalServerError().finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/api/users")]
|
||||
pub async fn create_new_user(
|
||||
user_service: web::Data<Arc<UserService>>,
|
||||
create_user_request: web::Json<CreateUserRequestDto>,
|
||||
) -> impl Responder {
|
||||
let user_creation_result = user_service.create_user(
|
||||
&create_user_request.username.to_string(),
|
||||
&create_user_request.password.to_string(),
|
||||
);
|
||||
|
||||
match user_creation_result {
|
||||
Ok(_) => HttpResponse::NoContent().finish(),
|
||||
Err(error) => match error.kind() {
|
||||
ErrorKind::InvalidInput => HttpResponse::BadRequest().body(error.to_string()),
|
||||
_ => HttpResponse::InternalServerError().finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[put("/api/users/{user_id}")]
|
||||
pub async fn update_user(
|
||||
user_service: web::Data<Arc<UserService>>,
|
||||
user_id: web::Path<Uuid>,
|
||||
update_user_request: web::Json<UpdateUserRequestDto>
|
||||
) -> impl Responder {
|
||||
let user_uuid = user_id.into_inner();
|
||||
let user_update_result = user_service.update_user(
|
||||
user_uuid,
|
||||
update_user_request.username.clone()
|
||||
);
|
||||
|
||||
match user_update_result {
|
||||
Ok(_) => HttpResponse::NoContent().finish(),
|
||||
Err(error) => match error.kind() {
|
||||
ErrorKind::InvalidInput => HttpResponse::BadRequest().body(error.to_string()),
|
||||
_ => HttpResponse::InternalServerError().body(error.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[put("/api/users/{user_id}/password")]
|
||||
pub async fn update_user_password(
|
||||
user_service: web::Data<Arc<UserService>>,
|
||||
user_id: web::Path<Uuid>,
|
||||
update_user_password_request: web::Json<UpdateUserPasswordRequestDto>
|
||||
) -> impl Responder {
|
||||
let user_uuid = user_id.into_inner();
|
||||
let user_password_update_result = user_service.update_user_password(
|
||||
user_uuid,
|
||||
update_user_password_request.password.clone()
|
||||
);
|
||||
|
||||
match user_password_update_result {
|
||||
Ok(_) => HttpResponse::NoContent().finish(),
|
||||
Err(error) => match error.kind() {
|
||||
ErrorKind::InvalidInput => HttpResponse::BadRequest().body(error.to_string()),
|
||||
_ => HttpResponse::InternalServerError().body(error.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/api/initialise")]
|
||||
pub async fn initialise(
|
||||
user_service: web::Data<Arc<UserService>>,
|
||||
request: web::Json<CreateFirstUserRequestDto>,
|
||||
) -> impl Responder {
|
||||
let initialisation_result = user_service.initialise(
|
||||
&request.initialisation_code,
|
||||
&request.username,
|
||||
&request.password,
|
||||
);
|
||||
match initialisation_result {
|
||||
Ok(_) => HttpResponse::NoContent().finish(),
|
||||
Err(error) => HttpResponse::BadRequest().body(error.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/api/users")]
|
||||
pub async fn get_all_users(user_service: web::Data<Arc<UserService>>) -> impl Responder {
|
||||
match user_service.get_all_users() {
|
||||
Ok(users) => {
|
||||
let user_dto_list = users.iter().map(UserDto::new).collect::<Vec<UserDto>>();
|
||||
HttpResponse::Ok().json(user_dto_list)
|
||||
}
|
||||
Err(_) => HttpResponse::InternalServerError().finish(),
|
||||
}
|
||||
}
|
||||
|
||||
#[delete("/api/users/{user_id}")]
|
||||
pub async fn delete_by_id(
|
||||
user_service: web::Data<Arc<UserService>>,
|
||||
user_id: web::Path<Uuid>,
|
||||
req: HttpRequest,
|
||||
) -> impl Responder {
|
||||
let authenticated_user_id = get_authenticated_user_id(req);
|
||||
match user_service.delete_by_id(&authenticated_user_id, &user_id) {
|
||||
Ok(()) => HttpResponse::NoContent().finish(),
|
||||
Err(error) => match error.kind() {
|
||||
ErrorKind::InvalidInput => HttpResponse::BadRequest().body(error.to_string()),
|
||||
_ => HttpResponse::InternalServerError().body(error.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_authenticated_user_id(req: HttpRequest) -> Uuid {
|
||||
req.extensions()
|
||||
.get::<Claims>()
|
||||
.map(|claims| &claims.sub)
|
||||
.map(|authenticated_user_id_as_str| authenticated_user_id_as_str.as_str())
|
||||
.map(Uuid::parse_str)
|
||||
.unwrap()
|
||||
.expect("Unable to retrieve the authenticated user id…")
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod handler;
|
||||
mod model;
|
||||
pub mod validator;
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
@@ -0,0 +1,31 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CreateFirstUserRequestDto {
|
||||
pub initialisation_code: String,
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CreateUserRequestDto {
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct UpdateUserRequestDto {
|
||||
pub username: String
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct UpdateUserPasswordRequestDto {
|
||||
pub password: String
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct LoginRequestDto {
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::Serialize;
|
||||
use uuid::Uuid;
|
||||
use surveillant_systeme_domain::user::model::{LoginResponse, RefreshToken, User};
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct UserDto {
|
||||
pub id: Uuid,
|
||||
pub username: String,
|
||||
}
|
||||
|
||||
impl UserDto {
|
||||
pub fn new(user: &User) -> UserDto {
|
||||
Self {
|
||||
id: user.id,
|
||||
username: user.username.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct LoginResponseDto {
|
||||
pub token_type: String,
|
||||
pub access_token: String,
|
||||
pub refresh_token: RefreshTokenDto,
|
||||
}
|
||||
|
||||
impl LoginResponseDto {
|
||||
pub fn new(login_response: &LoginResponse) -> Self {
|
||||
Self {
|
||||
token_type: login_response.token_type.clone(),
|
||||
access_token: login_response.access_token.clone(),
|
||||
refresh_token: RefreshTokenDto::new(&login_response.refresh_token),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RefreshTokenDto {
|
||||
pub user_id: Uuid,
|
||||
pub value: String,
|
||||
pub expiration_date: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl RefreshTokenDto {
|
||||
pub fn new(refresh_token: &RefreshToken) -> Self {
|
||||
Self {
|
||||
user_id: refresh_token.user_id.clone(),
|
||||
value: refresh_token.value.clone(),
|
||||
expiration_date: refresh_token.expiration_date.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
use std::env;
|
||||
use actix_web::{dev, HttpMessage};
|
||||
use actix_web_httpauth::extractors::bearer::BearerAuth;
|
||||
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
|
||||
use surveillant_systeme_domain::user::model::Claims;
|
||||
|
||||
fn get_jwt_secret() -> String {
|
||||
env::var("JWT_SECRET").expect("JWT_SECRET must be set.")
|
||||
}
|
||||
|
||||
pub async fn validator(
|
||||
request: dev::ServiceRequest,
|
||||
credentials: BearerAuth
|
||||
) -> Result<dev::ServiceRequest, (actix_web::Error, dev::ServiceRequest)> {
|
||||
let jwt_secret = get_jwt_secret();
|
||||
let token = credentials.token();
|
||||
|
||||
match decode::<Claims>(
|
||||
token,
|
||||
&DecodingKey::from_secret(jwt_secret.as_bytes()),
|
||||
&Validation::new(Algorithm::HS256)
|
||||
) {
|
||||
Ok(decoded) => {
|
||||
request.extensions_mut().insert(decoded.claims);
|
||||
Ok(request)
|
||||
}
|
||||
Err(_) => {
|
||||
Err((
|
||||
actix_web::error::ErrorUnauthorized("The provided JWT is invalid or expired."),
|
||||
request
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+1195
File diff suppressed because it is too large
Load Diff
@@ -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(())
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
pub mod common;
|
||||
pub mod configuration;
|
||||
pub mod schema;
|
||||
pub mod user;
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
pub mod adapter;
|
||||
pub mod diesel_adapter;
|
||||
pub mod diesel_repository;
|
||||
pub mod model;
|
||||
pub mod repository;
|
||||
@@ -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>>()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
HTTPS_ENABLED=false
|
||||
JWT_SECRET=jwt-secret
|
||||
SQLITE_DATABASE_NAME=lumiere.sqlite
|
||||
BCRYPT_SALT=salt
|
||||
SNAPSHOT_RESOURCES_CRON_FREQUENCY="*/5 * * * * *"
|
||||
SERVER_MODE=real # real | mocked
|
||||
#MOCKED_DAEMON_START_SUCCESS=true
|
||||
#MOCKED_DAEMON_STOP_SUCCESS=true
|
||||
#MOCKED_DAEMON_RESTART_SUCCESS=true
|
||||
Generated
+3486
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,24 @@
|
||||
[package]
|
||||
name = "lumiere"
|
||||
version = "1.0.0"
|
||||
edition = "2024"
|
||||
|
||||
[[bin]]
|
||||
name = "lumiere"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
lumiere-domain = { path = "../domain" }
|
||||
lumiere-application = { path = "../application" }
|
||||
lumiere-infrastructure = { path = "../infrastructure" }
|
||||
lumiere-exposition = { path = "../exposition" }
|
||||
|
||||
actix-web = { version = "4.13.0", features = ["rustls-0_23"] }
|
||||
diesel = { version = "2.0", features = ["sqlite"] }
|
||||
dotenv = "0.15.0"
|
||||
env_logger = "0.11.10"
|
||||
log = "0.4.29"
|
||||
rusqlite = { version = "0.39.0", features = ["bundled"] }
|
||||
rustls = "0.23.37"
|
||||
rustls-pemfile = "2.2.0"
|
||||
tokio-cron-scheduler = "0.15.1"
|
||||
@@ -0,0 +1,80 @@
|
||||
use crate::server_mode::ServerMode;
|
||||
use crate::server_mode::ServerMode::Mocked;
|
||||
use diesel::SqliteConnection;
|
||||
use log::{debug, info};
|
||||
use rusqlite::Connection;
|
||||
use std::env;
|
||||
use std::io::{Error, ErrorKind};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use surveillant_systeme_application::configuration::service::ConfigurationService;
|
||||
use surveillant_systeme_application::user::service::UserService;
|
||||
use surveillant_systeme_infrastructure::common::database_connection::DatabaseConnection;
|
||||
use surveillant_systeme_infrastructure::common::database_initialisation::initialise_database_tables;
|
||||
use surveillant_systeme_infrastructure::configuration::adapter::ConfigurationSqliteAdapter;
|
||||
use surveillant_systeme_infrastructure::configuration::repository::ConfigurationSqliteRepository;
|
||||
use surveillant_systeme_infrastructure::user::adapter::UserSqliteAdapter;
|
||||
use surveillant_systeme_infrastructure::user::diesel_adapter::UserDieselAdapter;
|
||||
use surveillant_systeme_infrastructure::user::diesel_repository::UserDieselRepository;
|
||||
use surveillant_systeme_infrastructure::user::repository::UserSqliteRepository;
|
||||
|
||||
pub struct ApplicationContext {
|
||||
pub database_connection: Arc<Mutex<Connection>>,
|
||||
pub configuration_service: ConfigurationService,
|
||||
pub user_service: UserService,
|
||||
}
|
||||
|
||||
impl ApplicationContext {
|
||||
pub fn init() -> Result<Self, Error> {
|
||||
let database_connection = DatabaseConnection::init();
|
||||
|
||||
let application_context = Self::new(database_connection.connection, database_connection.diesel_connection);
|
||||
|
||||
if database_connection.database_has_been_created {
|
||||
initialise_database_tables(&application_context.database_connection)
|
||||
.map_err(|error| Error::new(
|
||||
ErrorKind::Other,
|
||||
format!("Unable to initialise database tables… {}", error)
|
||||
))?;
|
||||
application_context.configuration_service.create_initialisation_code().expect("Unable to create initialisation code…");
|
||||
}
|
||||
|
||||
application_context.display_initialisation_code_if_required()?;
|
||||
|
||||
Ok(application_context)
|
||||
}
|
||||
|
||||
fn new(connection: Connection, diesel_connection: SqliteConnection) -> Self {
|
||||
let arc_mutex_connection = Arc::new(Mutex::new(connection));
|
||||
let arc_mutex_diesel_connection = Arc::new(Mutex::new(diesel_connection));
|
||||
|
||||
let configuration_repository = ConfigurationSqliteRepository::new(arc_mutex_connection.clone());
|
||||
let configuration_adapter = Box::new(ConfigurationSqliteAdapter::new(configuration_repository.clone()));
|
||||
let configuration_service = ConfigurationService::new(configuration_adapter.clone());
|
||||
|
||||
let user_repository = UserDieselRepository::new(arc_mutex_diesel_connection.clone());
|
||||
let user_adapter = Box::new(UserDieselAdapter::new(user_repository.clone()));
|
||||
// let user_repository = UserSqliteRepository::new(arc_mutex_connection.clone());
|
||||
// let user_adapter = Box::new(UserSqliteAdapter::new(user_repository.clone()));
|
||||
let user_service = UserService::new(configuration_service.clone(), user_adapter.clone());
|
||||
|
||||
Self {
|
||||
database_connection: arc_mutex_connection.clone(),
|
||||
configuration_service,
|
||||
user_service,
|
||||
}
|
||||
}
|
||||
|
||||
fn display_initialisation_code_if_required(&self) -> Result<(), Error> {
|
||||
let initialisation_code_opt = self.configuration_service.get_initialisation_code()?;
|
||||
let is_initialised = initialisation_code_opt.is_none();
|
||||
if is_initialised {
|
||||
debug!("Database is already initialised.");
|
||||
} else {
|
||||
info!("Database is not initialised yet.");
|
||||
let initialisation_code = initialisation_code_opt.unwrap();
|
||||
info!("Here is the initialisation code: {}", initialisation_code);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
mod application_context;
|
||||
mod server_mode;
|
||||
|
||||
use crate::application_context::ApplicationContext;
|
||||
use actix_web::{web, App, HttpServer};
|
||||
use dotenv::dotenv;
|
||||
use log::info;
|
||||
use rustls::ServerConfig;
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::{BufReader, Error};
|
||||
use std::sync::Arc;
|
||||
use surveillant_systeme_exposition::init;
|
||||
use tokio_cron_scheduler::{Job, JobScheduler};
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()>{
|
||||
dotenv().ok();
|
||||
unsafe { env::set_var("RUST_LOG", "debug"); }
|
||||
check_all_variables_are_set();
|
||||
env_logger::init();
|
||||
|
||||
let application_context = ApplicationContext::init()?;
|
||||
|
||||
let tls_config = init_tls_configuration()?;
|
||||
let server = HttpServer::new(
|
||||
move || App::new().configure(init)
|
||||
.app_data(web::Data::new(Arc::new(application_context.user_service.clone())))
|
||||
)
|
||||
.bind_rustls_0_23(("0.0.0.0", 19000), tls_config)
|
||||
.expect("Unable to start server");
|
||||
|
||||
server.run().await
|
||||
}
|
||||
|
||||
fn init_tls_configuration() -> Result<ServerConfig, Error> {
|
||||
let mut certs_file = BufReader::new(File::open("./certs/lumiere.crt")?);
|
||||
let mut key_file = BufReader::new(File::open("./certs/lumiere.key")?);
|
||||
|
||||
let tls_certs = rustls_pemfile::certs(&mut certs_file)
|
||||
.collect::<Result<Vec<_>, _>>().expect("Unable to read the TLS certificate…");
|
||||
let tls_key = rustls_pemfile::pkcs8_private_keys(&mut key_file)
|
||||
.next()
|
||||
.unwrap()
|
||||
.expect("Unable to read the private key for TLS configuration…");
|
||||
|
||||
let tls_config = rustls::ServerConfig::builder()
|
||||
.with_no_client_auth()
|
||||
.with_single_cert(tls_certs, rustls::pki_types::PrivateKeyDer::Pkcs8(tls_key))
|
||||
.unwrap();
|
||||
|
||||
Ok(tls_config)
|
||||
}
|
||||
|
||||
|
||||
fn check_all_variables_are_set() {
|
||||
info!("Checking all variables exist…");
|
||||
let variables = vec!["JWT_SECRET", "SQLITE_DATABASE_NAME", "BCRYPT_SALT"];
|
||||
variables.iter().for_each(|variable| {
|
||||
env::var(variable).expect(format!("{} should be defined!", variable).as_str());
|
||||
});
|
||||
info!("All variables are set correctly.");
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
use crate::server_mode::ServerMode::{Mocked, Real};
|
||||
|
||||
pub enum ServerMode {
|
||||
Mocked,
|
||||
Real
|
||||
}
|
||||
|
||||
impl ServerMode {
|
||||
pub fn from_string(value: String) -> Option<ServerMode> {
|
||||
match value.as_str() {
|
||||
"mocked" => Some(Mocked),
|
||||
"real" => Some(Real),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user