database pool, app config, simplify structure

This commit is contained in:
L-Nafaryus 2024-03-07 01:02:24 +05:00
parent 6b8371936e
commit 3ce3f894bc
Signed by: L-Nafaryus
GPG Key ID: 582F8B0866B294A1
23 changed files with 1339 additions and 116 deletions

2
.cargo/config.toml Normal file
View File

@ -0,0 +1,2 @@
[target.x86_64-unknown-linux-gnu]
#rustflags = ["-C", "link-arg=-fuse-ld=mold"]

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
result*
/temp
/.direnv
.env

1058
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,21 @@
[workspace]
resolver = "2"
members = [ "crates/db","crates/server"]
[package]
name = "elnafo"
version = "0.1.0"
edition = "2021"
authors = ["L-Nafaryus <l.nafaryus@elnafo.ru"]
[dependencies]
axum = "0.7.4"
tokio = { version = "1.36.0", default-features = false, features = [
"macros",
"fs",
"rt-multi-thread",
] }
dotenvy = "0.15.7"
tracing = "0.1.40"
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
diesel = { version = "2.1.4", features = ["postgres"] }
deadpool-diesel = { version = "0.5.0", features = ["postgres"] }
diesel_migrations = "2.1.0"
serde = { version = "1.0.197", features = ["derive"] }
serde_json = "1.0.114"

View File

@ -1 +0,0 @@
DATABASE_URL=postgres://elnafo:test@localhost/elnafo

View File

@ -1,8 +0,0 @@
[package]
name = "db"
version = "0.1.0"
edition = "2021"
[dependencies]
diesel = { version = "2.1.4", features = ["postgres"] }
dotenvy = "0.15.7"

View File

@ -1,9 +0,0 @@
# For documentation on how to configure this file,
# see https://diesel.rs/guides/configuring-diesel-cli
[print_schema]
file = "src/schema.rs"
custom_type_derives = ["diesel::query_builder::QueryId"]
[migrations_directory]
dir = "migrations"

View File

@ -1,51 +0,0 @@
pub mod models;
pub mod schema;
use diesel::prelude::*;
use dotenvy::dotenv;
use std::env;
use crate::models::{NewUser, User};
pub fn establish_connection() -> PgConnection {
dotenv().ok();
let db_url = env::var("DATABASE_URL").expect("Missed DATABASE_URL");
PgConnection::establish(&db_url).unwrap_or_else(|_| panic!("Error connecting to {}", db_url))
}
pub fn create_user(
connection: &mut PgConnection,
login: &str,
hashed_password: &str,
name: &str,
email: &str,
is_admin: bool,
) -> User {
use crate::schema::users;
let new_user = NewUser {
login,
hashed_password,
name,
email,
is_admin,
};
diesel::insert_into(users::table)
.values(&new_user)
.returning(User::as_returning())
.get_result(connection)
.expect("Error creating new user")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
unimplemented!();
}
}

View File

@ -1,13 +0,0 @@
[package]
name = "elnafo"
version = "0.1.0"
edition = "2021"
authors = ["L-Nafaryus <l.nafaryus@elnafo.ru"]
[[bin]]
name = "elnafo"
path = "src/main.rs"
[dependencies]
diesel = { version = "2.1.4", features = ["postgres"] }
db = { path = "../db" }

View File

@ -1,24 +0,0 @@
use db::{create_user, establish_connection, models::User};
use diesel::prelude::*;
fn main() {
use db::schema::users::dsl::*;
let connection = &mut establish_connection();
create_user(
connection,
"L-Nafaryus",
"asdasd",
"L-Nafaryus",
"l.nafaryus@elnafo.ru",
true,
);
let results = users
.select(User::as_select())
.load(connection)
.expect("Error loading users");
println!("Found {} users", results.len());
}

6
diesel.toml Normal file
View File

@ -0,0 +1,6 @@
[print_schema]
file = "src/db/schema.rs"
custom_type_derives = ["diesel::query_builder::QueryId"]
[migrations_directory]
dir = "src/db/migrations"

View File

@ -62,6 +62,8 @@
pkgs.ripgrep
pkgs.postgresql
pkgs.diesel-cli
pkgs.cargo-watch
pkgs.mold
];
shellHook = ''

14
src/api/v1/mod.rs Normal file
View File

@ -0,0 +1,14 @@
pub fn add(left: usize, right: usize) -> usize {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}

71
src/config.rs Normal file
View File

@ -0,0 +1,71 @@
use dotenvy::dotenv;
use std::env;
#[derive(Debug, Clone)]
pub struct Config {
pub database: Database,
pub server: Server,
pub jwt: Jwt,
}
#[derive(Debug, Clone)]
pub struct Database {
pub host: String,
pub port: i32,
pub user: String,
pub password: String,
pub name: String,
}
#[derive(Debug, Clone)]
pub struct Server {
pub address: String,
pub port: i32,
}
#[derive(Debug, Clone)]
pub struct Jwt {
pub secret: String,
pub expires_in: String,
pub maxage: i32,
}
impl Config {
pub fn new() -> Self {
dotenv().ok();
Config {
database: Database {
host: env::var("DATABASE_HOST").unwrap_or("localhost".to_string()),
port: env::var("DATABASE_PORT")
.unwrap_or("5432".to_string())
.parse()
.unwrap(),
user: env::var("DATABASE_USER").unwrap_or("elnafo".to_string()),
password: env::var("DATABASE_PASSWORD").unwrap_or("test".to_string()),
name: env::var("DATABASE_NAME").unwrap_or("elnafo".to_string()),
},
server: Server {
address: env::var("SERVER_ADDRESS").unwrap_or("0.0.0.0".to_string()),
port: env::var("SERVER_PORT")
.unwrap_or("54600".to_string())
.parse()
.unwrap(),
},
jwt: Jwt {
secret: env::var("JWT_SECRET").unwrap_or("change_this_secret".to_string()),
expires_in: env::var("JWT_EXPIRES_IN").unwrap_or("60m".to_string()),
maxage: env::var("JWT_MAXAGE")
.unwrap_or("60".to_string())
.parse()
.unwrap(),
},
}
}
}
impl Default for Config {
fn default() -> Self {
Config::new()
}
}

60
src/db/mod.rs Normal file
View File

@ -0,0 +1,60 @@
pub mod models;
pub mod schema;
use deadpool_diesel::postgres::Manager;
pub use deadpool_diesel::postgres::Pool;
use diesel::prelude::*;
use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};
use crate::db::models::{NewUser, User};
pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("src/db/migrations/");
pub fn create_pool(database_url: String) -> Pool {
let manager = Manager::new(database_url, deadpool_diesel::Runtime::Tokio1);
Pool::builder(manager).build().unwrap()
}
pub async fn run_migrations(pool: &Pool) {
let conn = pool.get().await.unwrap();
conn.interact(|conn| conn.run_pending_migrations(MIGRATIONS).map(|_| ()))
.await
.unwrap()
.unwrap();
}
pub fn create_user(
connection: &mut PgConnection,
login: &str,
hashed_password: &str,
name: &str,
email: &str,
is_admin: bool,
) -> User {
use crate::db::schema::users;
let new_user = NewUser {
login,
hashed_password,
name,
email,
is_admin,
};
diesel::insert_into(users::table)
.values(&new_user)
.returning(User::as_returning())
.get_result(connection)
.expect("Error creating new user")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
unimplemented!();
}
}

View File

@ -1,7 +1,7 @@
use crate::schema;
use crate::db::schema;
use diesel::prelude::*;
#[derive(Queryable, Selectable)]
#[derive(serde::Serialize, Queryable, Selectable)]
#[diesel(table_name = schema::users)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct User {
@ -13,7 +13,7 @@ pub struct User {
pub is_admin: bool,
}
#[derive(Insertable)]
#[derive(serde::Deserialize, Insertable)]
#[diesel(table_name = schema::users)]
pub struct NewUser<'a> {
pub login: &'a str,

105
src/main.rs Normal file
View File

@ -0,0 +1,105 @@
mod config;
mod db;
use axum::{extract::State, http::StatusCode, response::Json, routing::get, Router};
use diesel::RunQueryDsl;
use std::net::SocketAddr;
use std::{env, net::Ipv4Addr};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
use crate::config::Config;
use db::{create_user, models::User};
pub struct AppState {
database: db::Pool,
config: Config,
}
#[tokio::main]
async fn main() {
init_tracing();
let config = Config::new();
let database_url = format!(
"postgres://{}:{}@{}:{}/{}",
config.database.user,
config.database.password,
config.database.host,
config.database.port,
config.database.name
);
let pool = db::create_pool(database_url);
db::run_migrations(&pool).await;
let state = AppState {
database: pool.clone(),
config: config.clone(),
};
let address: SocketAddr = format!("{}:{}", config.server.address, config.server.port)
.parse()
.unwrap(); //SocketAddr::from((Ipv4Addr::UNSPECIFIED, 54600));
let lister = tokio::net::TcpListener::bind(&address).await.unwrap();
let app = Router::new()
.route("/api/v1/users", get(users))
.with_state(pool);
println!("listening on http://{}", address);
axum::serve(lister, app.into_make_service())
.await
.map_err(internal_error)
.unwrap();
}
async fn users(State(pool): State<db::Pool>) -> Result<Json<Vec<User>>, (StatusCode, String)> {
use db::schema::users::dsl::*;
let conn = pool.get().await.unwrap();
let result = conn
.interact(move |conn| users.load(conn))
.await
.map_err(internal_error)?
.map_err(internal_error)?;
Ok(Json(result))
}
fn init_tracing() {
tracing_subscriber::registry()
.with(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| "example_tokio_postgres=debug".into()),
)
.with(tracing_subscriber::fmt::layer())
.init();
}
fn internal_error<E>(err: E) -> (StatusCode, String)
where
E: std::error::Error,
{
(StatusCode::INTERNAL_SERVER_ERROR, err.to_string())
}
/*
create_user(
connection,
"L-Nafaryus",
"asdasd",
"L-Nafaryus",
"l.nafaryus@elnafo.ru",
true,
);
let results = users
.select(User::as_select())
.load(connection)
.expect("Error loading users");
println!("Found {} users", results.len());
*/