refactor: ♻️ client server
This commit is contained in:
parent
679e9b99bd
commit
fecd38fda2
12 changed files with 12 additions and 3 deletions
15
server/Cargo.toml
Normal file
15
server/Cargo.toml
Normal file
|
@ -0,0 +1,15 @@
|
|||
[package]
|
||||
name = "rust-package-manager-server"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
axum = { version = "0.7.7", features = ["multipart"] }
|
||||
futures = "0.3.31"
|
||||
serde = { version = "1.0.210", features = ["derive"] }
|
||||
serde_json = "1.0.128"
|
||||
sha3 = "0.10.8"
|
||||
surrealdb = "2.0.4"
|
||||
tokio = { version = "1.40.0", features = ["full"] }
|
||||
tokio-util = { version = "0.7.12", features = ["io"] }
|
||||
tower-http = { version = "0.6.1", features = ["cors"] }
|
53
server/src/database.rs
Normal file
53
server/src/database.rs
Normal file
|
@ -0,0 +1,53 @@
|
|||
use std::{sync::LazyLock, time::Duration};
|
||||
|
||||
use surrealdb::{
|
||||
engine::remote::ws::{Client, Ws},
|
||||
Surreal,
|
||||
};
|
||||
use tokio::time::sleep;
|
||||
|
||||
use crate::package::package::Package;
|
||||
|
||||
static DB: LazyLock<Surreal<Client>> = LazyLock::new(Surreal::init);
|
||||
|
||||
pub async fn establish_connection() -> Result<(), surrealdb::Error> {
|
||||
DB.connect::<Ws>("localhost:8000").await?;
|
||||
DB.use_ns("Packages").await?;
|
||||
DB.use_db("Packages").await
|
||||
}
|
||||
|
||||
pub async fn is_alive() -> bool {
|
||||
tokio::select! {
|
||||
db_result = DB.health() => { match db_result {
|
||||
Ok(_) => true,
|
||||
Err(_) => false,
|
||||
} },
|
||||
_ = sleep(Duration::from_secs(1)) => false
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn create_package(package: Package) -> Option<Package> {
|
||||
DB.create::<Option<Package>>(package.get_name())
|
||||
.content(package)
|
||||
.await
|
||||
.map_or_else(|_| None, |package| package)
|
||||
}
|
||||
|
||||
pub async fn read_package(package_name: String) -> Option<Package> {
|
||||
DB.select::<Vec<Package>>(package_name)
|
||||
.await
|
||||
.map_or_else(|_| None, |mut package| package.pop())
|
||||
}
|
||||
|
||||
pub async fn update_package(package_name: String, package: Package) -> Option<Package> {
|
||||
DB.update::<Vec<Package>>(package_name)
|
||||
.content(package)
|
||||
.await
|
||||
.map_or_else(|_| None, |mut package| package.pop())
|
||||
}
|
||||
|
||||
pub async fn delete_package(package_name: String) -> Option<Package> {
|
||||
DB.delete::<Vec<Package>>(package_name)
|
||||
.await
|
||||
.map_or_else(|_| None, |mut package| package.pop())
|
||||
}
|
1
server/src/http.rs
Normal file
1
server/src/http.rs
Normal file
|
@ -0,0 +1 @@
|
|||
|
9
server/src/lib.rs
Normal file
9
server/src/lib.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
pub mod database;
|
||||
pub mod http;
|
||||
pub mod package;
|
||||
pub mod routing;
|
||||
|
||||
pub const PACKAGE_PATH: &str = "/packages";
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AppState {}
|
13
server/src/main.rs
Normal file
13
server/src/main.rs
Normal file
|
@ -0,0 +1,13 @@
|
|||
use rust_package_manager_server::{database, routing, AppState};
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
println!("Hello, world!");
|
||||
|
||||
let listener = TcpListener::bind("localhost:2345").await.unwrap();
|
||||
database::establish_connection().await.unwrap();
|
||||
let app_state = AppState {};
|
||||
let router = routing::route(axum::extract::State(app_state)).await;
|
||||
axum::serve(listener, router).await.unwrap();
|
||||
}
|
2
server/src/package.rs
Normal file
2
server/src/package.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
pub mod package;
|
||||
pub mod utils;
|
92
server/src/package/package.rs
Normal file
92
server/src/package/package.rs
Normal file
|
@ -0,0 +1,92 @@
|
|||
use std::fmt::Display;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha3::{Digest, Sha3_512};
|
||||
use surrealdb::sql::Datetime;
|
||||
use tokio::{fs::File, io::AsyncReadExt};
|
||||
use tokio_util::io::ReaderStream;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Package {
|
||||
name: String,
|
||||
publisher: Publisher,
|
||||
version: Version,
|
||||
size: u64,
|
||||
hash: Vec<u8>,
|
||||
dependencies: Vec<String>,
|
||||
publish_date_time: Datetime,
|
||||
last_update_date_time: Datetime,
|
||||
location: String,
|
||||
}
|
||||
|
||||
impl Package {
|
||||
pub fn new(name: String, publisher: Publisher, version: Version) -> Self {
|
||||
Self {
|
||||
name,
|
||||
publisher,
|
||||
version,
|
||||
size: 0,
|
||||
hash: vec![],
|
||||
dependencies: vec![],
|
||||
publish_date_time: Datetime::default(),
|
||||
last_update_date_time: Datetime::default(),
|
||||
location: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_name(&self) -> String {
|
||||
self.name.to_string()
|
||||
}
|
||||
|
||||
pub fn get_location(&self) -> String {
|
||||
self.location.to_string()
|
||||
}
|
||||
|
||||
pub async fn serve(&self) -> Option<ReaderStream<File>> {
|
||||
match File::open(self.get_location()).await {
|
||||
Ok(package_file) => Some(ReaderStream::new(package_file)),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_location(&mut self, location: &String) {
|
||||
self.location = location.to_owned()
|
||||
}
|
||||
|
||||
pub fn get_dependencies(&self) -> &[String] {
|
||||
self.dependencies.as_slice()
|
||||
}
|
||||
|
||||
pub fn set_last_update_date_time(&mut self) {
|
||||
self.last_update_date_time = Datetime::default();
|
||||
}
|
||||
|
||||
pub async fn set_hash(&mut self) {
|
||||
if let Ok(mut package_file) = File::open(self.get_location()).await {
|
||||
let mut hasher = Sha3_512::new();
|
||||
let mut data_buffer = vec![];
|
||||
if let Ok(_) = package_file.read_to_end(&mut data_buffer).await {
|
||||
hasher.update(data_buffer);
|
||||
self.hash = hasher.finalize().to_vec();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Version {
|
||||
first: u8,
|
||||
second: u8,
|
||||
third: u8,
|
||||
}
|
||||
|
||||
impl Display for Version {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}.{}.{}", self.first, self.second, self.third)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Publisher {
|
||||
name: String,
|
||||
}
|
56
server/src/package/utils.rs
Normal file
56
server/src/package/utils.rs
Normal file
|
@ -0,0 +1,56 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use axum::extract::Multipart;
|
||||
use tokio::{fs::File, io::AsyncWriteExt};
|
||||
use tokio_util::io::ReaderStream;
|
||||
|
||||
use crate::{database, routing, PACKAGE_PATH};
|
||||
|
||||
use super::package::Package;
|
||||
|
||||
pub async fn create_package(package: routing::Package) -> Option<Package> {
|
||||
let package = Package::new(package.name, package.publisher, package.version);
|
||||
database::create_package(package).await
|
||||
}
|
||||
|
||||
pub async fn read_package(package_name: String) -> Option<Package> {
|
||||
database::read_package(package_name).await
|
||||
}
|
||||
|
||||
pub async fn update_package(package_name: String, mut package: Package) -> Option<Package> {
|
||||
for dependency in package.get_dependencies() {
|
||||
database::read_package(dependency.to_string()).await?;
|
||||
}
|
||||
package.set_last_update_date_time();
|
||||
database::update_package(package_name, package).await
|
||||
}
|
||||
|
||||
pub async fn delete_package(package_name: String) -> Option<Package> {
|
||||
database::delete_package(package_name).await
|
||||
}
|
||||
|
||||
pub async fn download_package(package_name: String) -> Option<ReaderStream<File>> {
|
||||
let package = crate::package::utils::read_package(package_name).await?;
|
||||
let package_file_stream = package.serve().await?;
|
||||
Some(package_file_stream)
|
||||
}
|
||||
|
||||
pub async fn upload_package(mut package_file: Multipart) -> Option<Package> {
|
||||
let package_file_part = package_file.next_field().await.ok()??;
|
||||
let package_file_name = package_file_part.file_name()?.to_string();
|
||||
|
||||
let file_location = format!("./{}/{}", PACKAGE_PATH, package_file_name);
|
||||
let file_location = PathBuf::from(file_location).canonicalize().ok()?;
|
||||
let file_location = file_location.to_str()?;
|
||||
|
||||
let package_file_data = package_file_part.bytes().await.ok()?;
|
||||
let mut package = crate::package::utils::read_package(package_file_name).await?;
|
||||
|
||||
let mut file_descriptor = File::create_new(&file_location).await.ok()?;
|
||||
file_descriptor.write_all(&package_file_data).await.ok()?;
|
||||
|
||||
package.set_location(&file_location.to_string());
|
||||
package.set_hash().await;
|
||||
|
||||
Some(package)
|
||||
}
|
94
server/src/routing.rs
Normal file
94
server/src/routing.rs
Normal file
|
@ -0,0 +1,94 @@
|
|||
use axum::{
|
||||
body::Body,
|
||||
extract::{Multipart, Path, State},
|
||||
http::StatusCode,
|
||||
response::IntoResponse,
|
||||
routing::{delete, get, patch, post},
|
||||
Json, Router,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tower_http::cors::CorsLayer;
|
||||
|
||||
use crate::{
|
||||
database,
|
||||
package::package::{Publisher, Version},
|
||||
AppState,
|
||||
};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Package {
|
||||
pub name: String,
|
||||
pub publisher: Publisher,
|
||||
pub version: Version,
|
||||
}
|
||||
|
||||
pub async fn route(State(app_state): State<AppState>) -> Router {
|
||||
Router::new()
|
||||
.route("/", get(alive))
|
||||
.route("/package", post(create_package))
|
||||
.route("/package/:package_name", get(read_package))
|
||||
.route("/package/:package_name", patch(update_package))
|
||||
.route("/package/:package_name", delete(delete_package))
|
||||
.route("/package/download/:package_name", get(download_package))
|
||||
.route("/package/upload", post(upload_package))
|
||||
.layer(CorsLayer::permissive())
|
||||
.with_state(app_state)
|
||||
}
|
||||
|
||||
async fn alive() -> impl IntoResponse {
|
||||
let db_status = match database::is_alive().await {
|
||||
true => "alive",
|
||||
false => "dead",
|
||||
};
|
||||
let alive_json = Json(serde_json::json!({
|
||||
"server_status": "alive",
|
||||
"database_status": db_status
|
||||
}));
|
||||
|
||||
(StatusCode::OK, alive_json)
|
||||
}
|
||||
|
||||
async fn create_package(Json(package): Json<Package>) -> impl IntoResponse {
|
||||
match crate::package::utils::create_package(package).await {
|
||||
Some(package) => (StatusCode::CREATED, Json(serde_json::json!(package))),
|
||||
None => (StatusCode::BAD_REQUEST, Json(serde_json::json!(""))),
|
||||
}
|
||||
}
|
||||
|
||||
async fn read_package(Path(package_name): Path<String>) -> impl IntoResponse {
|
||||
match crate::package::utils::read_package(package_name).await {
|
||||
Some(package) => (StatusCode::OK, Json(serde_json::json!(package))),
|
||||
None => (StatusCode::BAD_REQUEST, Json(serde_json::json!(""))),
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_package(
|
||||
Path(package_name): Path<String>,
|
||||
Json(package): Json<crate::package::package::Package>,
|
||||
) -> impl IntoResponse {
|
||||
match crate::package::utils::update_package(package_name, package).await {
|
||||
Some(package) => (StatusCode::ACCEPTED, Json(serde_json::json!(package))),
|
||||
None => (StatusCode::BAD_REQUEST, Json(serde_json::json!(""))),
|
||||
}
|
||||
}
|
||||
|
||||
async fn delete_package(Path(package_name): Path<String>) -> impl IntoResponse {
|
||||
match crate::package::utils::delete_package(package_name).await {
|
||||
Some(package) => (StatusCode::NO_CONTENT, Json(serde_json::json!(package))),
|
||||
None => (StatusCode::BAD_REQUEST, Json(serde_json::json!(""))),
|
||||
}
|
||||
}
|
||||
|
||||
async fn download_package(Path(package_name): Path<String>) -> impl IntoResponse {
|
||||
match crate::package::utils::download_package(package_name).await {
|
||||
Some(package_file_stream) => (StatusCode::OK, Body::from_stream(package_file_stream)),
|
||||
None => (StatusCode::BAD_REQUEST, Body::empty()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn upload_package(package_file: Multipart) -> impl IntoResponse {
|
||||
match crate::package::utils::upload_package(package_file).await {
|
||||
Some(package) => (StatusCode::ACCEPTED, Json(serde_json::json!(package))),
|
||||
None => (StatusCode::BAD_REQUEST, Json(serde_json::json!(""))),
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue