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