Initial commit.
This commit is contained in:
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
|
||||
Reference in New Issue
Block a user