Initial commit: CPU management API
This commit is contained in:
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
target/
|
||||||
|
Cargo.lock
|
||||||
|
*.db
|
||||||
|
*.db-shm
|
||||||
|
*.db-wal
|
||||||
|
.env
|
||||||
|
.DS_Store
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
14
Cargo.toml
Normal file
14
Cargo.toml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
[package]
|
||||||
|
name = "cpu-server"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
axum = "0.7"
|
||||||
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
|
sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "sqlite", "macros"] }
|
||||||
|
config = "0.14"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
utoipa = "5.3"
|
||||||
|
env_logger = "0.10"
|
||||||
|
log = "0.4"
|
||||||
3
settings.toml
Normal file
3
settings.toml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
host = "127.0.0.1"
|
||||||
|
port = 3000
|
||||||
|
database_url = "sqlite:/home/popov_av/cpu-server/cpu.db"
|
||||||
11
src/dtos.rs
Normal file
11
src/dtos.rs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
|
||||||
|
pub struct CpuDto {
|
||||||
|
pub id: i64,
|
||||||
|
pub brand: String,
|
||||||
|
pub model: String,
|
||||||
|
pub frequency_mhz: i32,
|
||||||
|
pub cores: i32,
|
||||||
|
pub threads: i32,
|
||||||
|
}
|
||||||
23
src/lib.rs
Normal file
23
src/lib.rs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
use axum::Router;
|
||||||
|
use sqlx::SqlitePool;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
mod dtos;
|
||||||
|
mod models;
|
||||||
|
mod queries;
|
||||||
|
mod routes;
|
||||||
|
mod schemas;
|
||||||
|
mod services;
|
||||||
|
mod settings;
|
||||||
|
mod openapi;
|
||||||
|
|
||||||
|
pub use settings::Settings;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct AppState {
|
||||||
|
pub pool: SqlitePool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn router(state: AppState) -> Router {
|
||||||
|
routes::create_router(Arc::new(state))
|
||||||
|
}
|
||||||
49
src/main.rs
Normal file
49
src/main.rs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
use cpu_server::{router, Settings};
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
// Инициализация логгера
|
||||||
|
env_logger::init();
|
||||||
|
|
||||||
|
// Загрузка настроек
|
||||||
|
let settings = Settings::new().expect("Failed to load settings");
|
||||||
|
|
||||||
|
// Создание пула подключений к базе данных
|
||||||
|
let pool = sqlx::sqlite::SqlitePoolOptions::new()
|
||||||
|
.connect(&settings.database_url)
|
||||||
|
.await
|
||||||
|
.expect("Failed to connect to database");
|
||||||
|
|
||||||
|
// Создание таблицы если не существует
|
||||||
|
sqlx::query(
|
||||||
|
r#"
|
||||||
|
CREATE TABLE IF NOT EXISTS cpus (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
brand TEXT NOT NULL,
|
||||||
|
model TEXT NOT NULL,
|
||||||
|
frequency_mhz INTEGER NOT NULL,
|
||||||
|
cores INTEGER NOT NULL,
|
||||||
|
threads INTEGER NOT NULL
|
||||||
|
)
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.execute(&pool)
|
||||||
|
.await
|
||||||
|
.expect("Failed to create table");
|
||||||
|
|
||||||
|
let app_state = cpu_server::AppState { pool };
|
||||||
|
|
||||||
|
let app = router(app_state);
|
||||||
|
|
||||||
|
let addr = SocketAddr::from(([127, 0, 0, 1], settings.port));
|
||||||
|
println!("Server running on http://{}", addr);
|
||||||
|
|
||||||
|
let listener = tokio::net::TcpListener::bind(addr)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
axum::serve(listener, app)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
11
src/models.rs
Normal file
11
src/models.rs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Cpu {
|
||||||
|
pub id: i64,
|
||||||
|
pub brand: String,
|
||||||
|
pub model: String,
|
||||||
|
pub frequency_mhz: i32,
|
||||||
|
pub cores: i32,
|
||||||
|
pub threads: i32,
|
||||||
|
}
|
||||||
20
src/openapi.rs
Normal file
20
src/openapi.rs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
use utoipa::OpenApi;
|
||||||
|
use crate::schemas::{CreateCpuRequest, CpuResponse, UpdateCpuRequest};
|
||||||
|
|
||||||
|
#[derive(OpenApi)]
|
||||||
|
#[openapi(
|
||||||
|
paths(
|
||||||
|
crate::routes::get_all_cpus,
|
||||||
|
crate::routes::create_cpu,
|
||||||
|
crate::routes::update_cpu,
|
||||||
|
crate::routes::delete_cpu,
|
||||||
|
crate::routes::openapi_json
|
||||||
|
),
|
||||||
|
components(
|
||||||
|
schemas(CreateCpuRequest, UpdateCpuRequest, CpuResponse)
|
||||||
|
),
|
||||||
|
tags(
|
||||||
|
(name = "CPU API", description = "CPU management API")
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
pub struct ApiDoc;
|
||||||
87
src/queries.rs
Normal file
87
src/queries.rs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
use sqlx::SqlitePool;
|
||||||
|
use crate::dtos::CpuDto;
|
||||||
|
|
||||||
|
pub async fn get_all_cpus(pool: &SqlitePool) -> Result<Vec<CpuDto>, sqlx::Error> {
|
||||||
|
sqlx::query_as::<_, CpuDto>("SELECT * FROM cpus")
|
||||||
|
.fetch_all(pool)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_cpu_by_id(pool: &SqlitePool, id: i64) -> Result<Option<CpuDto>, sqlx::Error> {
|
||||||
|
sqlx::query_as::<_, CpuDto>("SELECT * FROM cpus WHERE id = ?")
|
||||||
|
.bind(id)
|
||||||
|
.fetch_optional(pool)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn create_cpu(
|
||||||
|
pool: &SqlitePool,
|
||||||
|
brand: &str,
|
||||||
|
model: &str,
|
||||||
|
frequency_mhz: i32,
|
||||||
|
cores: i32,
|
||||||
|
threads: i32,
|
||||||
|
) -> Result<CpuDto, sqlx::Error> {
|
||||||
|
let id = sqlx::query(
|
||||||
|
"INSERT INTO cpus (brand, model, frequency_mhz, cores, threads) VALUES (?, ?, ?, ?, ?)"
|
||||||
|
)
|
||||||
|
.bind(brand)
|
||||||
|
.bind(model)
|
||||||
|
.bind(frequency_mhz)
|
||||||
|
.bind(cores)
|
||||||
|
.bind(threads)
|
||||||
|
.execute(pool)
|
||||||
|
.await?
|
||||||
|
.last_insert_rowid();
|
||||||
|
|
||||||
|
get_cpu_by_id(pool, id).await.map(|cpu| cpu.unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn update_cpu(
|
||||||
|
pool: &SqlitePool,
|
||||||
|
id: i64,
|
||||||
|
brand: Option<&str>,
|
||||||
|
model: Option<&str>,
|
||||||
|
frequency_mhz: Option<i32>,
|
||||||
|
cores: Option<i32>,
|
||||||
|
threads: Option<i32>,
|
||||||
|
) -> Result<Option<CpuDto>, sqlx::Error> {
|
||||||
|
// Получаем текущие данные процессора
|
||||||
|
let current_cpu = get_cpu_by_id(pool, id).await?;
|
||||||
|
if current_cpu.is_none() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let current_cpu = current_cpu.unwrap();
|
||||||
|
|
||||||
|
// Используем переданные значения или текущие
|
||||||
|
let brand = brand.unwrap_or(¤t_cpu.brand);
|
||||||
|
let model = model.unwrap_or(¤t_cpu.model);
|
||||||
|
let frequency_mhz = frequency_mhz.unwrap_or(current_cpu.frequency_mhz);
|
||||||
|
let cores = cores.unwrap_or(current_cpu.cores);
|
||||||
|
let threads = threads.unwrap_or(current_cpu.threads);
|
||||||
|
|
||||||
|
// Выполняем обновление
|
||||||
|
sqlx::query(
|
||||||
|
"UPDATE cpus SET brand = ?, model = ?, frequency_mhz = ?, cores = ?, threads = ? WHERE id = ?"
|
||||||
|
)
|
||||||
|
.bind(brand)
|
||||||
|
.bind(model)
|
||||||
|
.bind(frequency_mhz)
|
||||||
|
.bind(cores)
|
||||||
|
.bind(threads)
|
||||||
|
.bind(id)
|
||||||
|
.execute(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Возвращаем обновленные данные
|
||||||
|
get_cpu_by_id(pool, id).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete_cpu(pool: &SqlitePool, id: i64) -> Result<bool, sqlx::Error> {
|
||||||
|
let result = sqlx::query("DELETE FROM cpus WHERE id = ?")
|
||||||
|
.bind(id)
|
||||||
|
.execute(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(result.rows_affected() > 0)
|
||||||
|
}
|
||||||
172
src/routes.rs
Normal file
172
src/routes.rs
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
use axum::{
|
||||||
|
extract::{Path, State},
|
||||||
|
http::StatusCode,
|
||||||
|
response::Json,
|
||||||
|
routing::{get, post},
|
||||||
|
Router,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use utoipa::OpenApi;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
schemas::{CreateCpuRequest, CpuResponse, UpdateCpuRequest},
|
||||||
|
services::CpuService,
|
||||||
|
AppState, openapi::ApiDoc,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub type AppStateType = Arc<AppState>;
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/api/cpu",
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "List all CPUs", body = [CpuResponse])
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
pub async fn get_all_cpus(
|
||||||
|
State(state): State<AppStateType>,
|
||||||
|
) -> Result<Json<Vec<CpuResponse>>, StatusCode> {
|
||||||
|
let cpus = CpuService::get_all_cpus(&state.pool)
|
||||||
|
.await
|
||||||
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
|
let responses: Vec<CpuResponse> = cpus
|
||||||
|
.into_iter()
|
||||||
|
.map(|cpu| CpuResponse {
|
||||||
|
id: cpu.id,
|
||||||
|
brand: cpu.brand,
|
||||||
|
model: cpu.model,
|
||||||
|
frequency_mhz: cpu.frequency_mhz,
|
||||||
|
cores: cpu.cores,
|
||||||
|
threads: cpu.threads,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(Json(responses))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
post,
|
||||||
|
path = "/api/cpu",
|
||||||
|
request_body = CreateCpuRequest,
|
||||||
|
responses(
|
||||||
|
(status = 201, description = "CPU created successfully", body = CpuResponse),
|
||||||
|
(status = 400, description = "Invalid input")
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
pub async fn create_cpu(
|
||||||
|
State(state): State<AppStateType>,
|
||||||
|
Json(payload): Json<CreateCpuRequest>,
|
||||||
|
) -> Result<Json<CpuResponse>, StatusCode> {
|
||||||
|
let cpu = CpuService::create_cpu(
|
||||||
|
&state.pool,
|
||||||
|
payload.brand,
|
||||||
|
payload.model,
|
||||||
|
payload.frequency_mhz,
|
||||||
|
payload.cores,
|
||||||
|
payload.threads,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
|
let response = CpuResponse {
|
||||||
|
id: cpu.id,
|
||||||
|
brand: cpu.brand,
|
||||||
|
model: cpu.model,
|
||||||
|
frequency_mhz: cpu.frequency_mhz,
|
||||||
|
cores: cpu.cores,
|
||||||
|
threads: cpu.threads,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Json(response))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
post,
|
||||||
|
path = "/api/cpu/{id}",
|
||||||
|
params(
|
||||||
|
("id" = i64, Path, description = "CPU ID")
|
||||||
|
),
|
||||||
|
request_body = UpdateCpuRequest,
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "CPU updated successfully", body = CpuResponse),
|
||||||
|
(status = 404, description = "CPU not found")
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
pub async fn update_cpu(
|
||||||
|
State(state): State<AppStateType>,
|
||||||
|
Path(id): Path<i64>,
|
||||||
|
Json(payload): Json<UpdateCpuRequest>,
|
||||||
|
) -> Result<Json<CpuResponse>, StatusCode> {
|
||||||
|
let cpu = CpuService::update_cpu(
|
||||||
|
&state.pool,
|
||||||
|
id,
|
||||||
|
payload.brand,
|
||||||
|
payload.model,
|
||||||
|
payload.frequency_mhz,
|
||||||
|
payload.cores,
|
||||||
|
payload.threads,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
|
match cpu {
|
||||||
|
Some(cpu) => {
|
||||||
|
let response = CpuResponse {
|
||||||
|
id: cpu.id,
|
||||||
|
brand: cpu.brand,
|
||||||
|
model: cpu.model,
|
||||||
|
frequency_mhz: cpu.frequency_mhz,
|
||||||
|
cores: cpu.cores,
|
||||||
|
threads: cpu.threads,
|
||||||
|
};
|
||||||
|
Ok(Json(response))
|
||||||
|
}
|
||||||
|
None => Err(StatusCode::NOT_FOUND),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
delete,
|
||||||
|
path = "/api/cpu/{id}",
|
||||||
|
params(
|
||||||
|
("id" = i64, Path, description = "CPU ID")
|
||||||
|
),
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "CPU deleted successfully"),
|
||||||
|
(status = 404, description = "CPU not found")
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
pub async fn delete_cpu(
|
||||||
|
State(state): State<AppStateType>,
|
||||||
|
Path(id): Path<i64>,
|
||||||
|
) -> Result<StatusCode, StatusCode> {
|
||||||
|
let deleted = CpuService::delete_cpu(&state.pool, id)
|
||||||
|
.await
|
||||||
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
|
if deleted {
|
||||||
|
Ok(StatusCode::OK)
|
||||||
|
} else {
|
||||||
|
Err(StatusCode::NOT_FOUND)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/api/openapi.json",
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "OpenAPI specification")
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
pub async fn openapi_json() -> Json<utoipa::openapi::OpenApi> {
|
||||||
|
Json(ApiDoc::openapi())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_router(state: AppStateType) -> Router {
|
||||||
|
Router::new()
|
||||||
|
.route("/api/cpu", get(get_all_cpus).post(create_cpu))
|
||||||
|
.route("/api/cpu/:id", post(update_cpu).delete(delete_cpu))
|
||||||
|
.route("/api/openapi.json", get(openapi_json))
|
||||||
|
.with_state(state)
|
||||||
|
}
|
||||||
30
src/schemas.rs
Normal file
30
src/schemas.rs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||||
|
pub struct CreateCpuRequest {
|
||||||
|
pub brand: String,
|
||||||
|
pub model: String,
|
||||||
|
pub frequency_mhz: i32,
|
||||||
|
pub cores: i32,
|
||||||
|
pub threads: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||||
|
pub struct UpdateCpuRequest {
|
||||||
|
pub brand: Option<String>,
|
||||||
|
pub model: Option<String>,
|
||||||
|
pub frequency_mhz: Option<i32>,
|
||||||
|
pub cores: Option<i32>,
|
||||||
|
pub threads: Option<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||||
|
pub struct CpuResponse {
|
||||||
|
pub id: i64,
|
||||||
|
pub brand: String,
|
||||||
|
pub model: String,
|
||||||
|
pub frequency_mhz: i32,
|
||||||
|
pub cores: i32,
|
||||||
|
pub threads: i32,
|
||||||
|
}
|
||||||
61
src/services.rs
Normal file
61
src/services.rs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
use crate::{dtos::CpuDto, models::Cpu, queries};
|
||||||
|
use sqlx::SqlitePool;
|
||||||
|
|
||||||
|
pub struct CpuService;
|
||||||
|
|
||||||
|
impl CpuService {
|
||||||
|
pub async fn get_all_cpus(pool: &SqlitePool) -> Result<Vec<Cpu>, sqlx::Error> {
|
||||||
|
let dtos = queries::get_all_cpus(pool).await?;
|
||||||
|
Ok(dtos.into_iter().map(|dto| dto.into()).collect())
|
||||||
|
}
|
||||||
|
pub async fn create_cpu(
|
||||||
|
pool: &SqlitePool,
|
||||||
|
brand: String,
|
||||||
|
model: String,
|
||||||
|
frequency_mhz: i32,
|
||||||
|
cores: i32,
|
||||||
|
threads: i32,
|
||||||
|
) -> Result<Cpu, sqlx::Error> {
|
||||||
|
let dto = queries::create_cpu(pool, &brand, &model, frequency_mhz, cores, threads).await?;
|
||||||
|
Ok(dto.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn update_cpu(
|
||||||
|
pool: &SqlitePool,
|
||||||
|
id: i64,
|
||||||
|
brand: Option<String>,
|
||||||
|
model: Option<String>,
|
||||||
|
frequency_mhz: Option<i32>,
|
||||||
|
cores: Option<i32>,
|
||||||
|
threads: Option<i32>,
|
||||||
|
) -> Result<Option<Cpu>, sqlx::Error> {
|
||||||
|
let dto = queries::update_cpu(
|
||||||
|
pool,
|
||||||
|
id,
|
||||||
|
brand.as_deref(),
|
||||||
|
model.as_deref(),
|
||||||
|
frequency_mhz,
|
||||||
|
cores,
|
||||||
|
threads,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok(dto.map(|dto| dto.into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete_cpu(pool: &SqlitePool, id: i64) -> Result<bool, sqlx::Error> {
|
||||||
|
queries::delete_cpu(pool, id).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<CpuDto> for Cpu {
|
||||||
|
fn from(dto: CpuDto) -> Self {
|
||||||
|
Cpu {
|
||||||
|
id: dto.id,
|
||||||
|
brand: dto.brand,
|
||||||
|
model: dto.model,
|
||||||
|
frequency_mhz: dto.frequency_mhz,
|
||||||
|
cores: dto.cores,
|
||||||
|
threads: dto.threads,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
src/settings.rs
Normal file
19
src/settings.rs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
use config::Config;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
pub struct Settings {
|
||||||
|
pub host: String,
|
||||||
|
pub port: u16,
|
||||||
|
pub database_url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Settings {
|
||||||
|
pub fn new() -> Result<Self, config::ConfigError> {
|
||||||
|
let settings = Config::builder()
|
||||||
|
.add_source(config::File::with_name("settings"))
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
settings.try_deserialize()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user