chore: set up skeleton project
commit
b6a8ee0bbe
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
data
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,14 @@
|
||||||
|
[package]
|
||||||
|
name = "otter"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
axum = "0.8.1"
|
||||||
|
diesel = { version = "2.2.7", features = ["r2d2", "sqlite"] }
|
||||||
|
diesel_migrations = { version = "2.2.0", features = ["sqlite"] }
|
||||||
|
libsqlite3-sys = { version = "0.31.0", features = ["bundled"] }
|
||||||
|
tokio = { version = "1.43.0", features = ["full"] }
|
||||||
|
tower-http = { version = "0.6.2", features = ["set-header", "trace"] }
|
||||||
|
tracing = "0.1.41"
|
||||||
|
tracing-subscriber = "0.3.19"
|
|
@ -0,0 +1,9 @@
|
||||||
|
# For documentation on how to configure this file,
|
||||||
|
# see https://diesel.rs/guides/configuring-diesel-cli
|
||||||
|
|
||||||
|
[print_schema]
|
||||||
|
file = "src/db/schema.rs"
|
||||||
|
custom_type_derives = ["diesel::query_builder::QueryId", "Clone"]
|
||||||
|
|
||||||
|
[migrations_directory]
|
||||||
|
dir = "migrations"
|
|
@ -0,0 +1,62 @@
|
||||||
|
mod models;
|
||||||
|
mod schema;
|
||||||
|
|
||||||
|
use diesel::{
|
||||||
|
r2d2::{ConnectionManager, Pool},
|
||||||
|
SqliteConnection,
|
||||||
|
};
|
||||||
|
use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};
|
||||||
|
|
||||||
|
use std::{error::Error, fmt, path::Path};
|
||||||
|
|
||||||
|
pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations");
|
||||||
|
|
||||||
|
pub type DbPool = Pool<ConnectionManager<SqliteConnection>>;
|
||||||
|
pub type DbResult<T> = Result<T, DbError>;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum DbError {
|
||||||
|
Pool(diesel::r2d2::PoolError),
|
||||||
|
Db(diesel::result::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for DbError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Pool(_) => write!(f, "failed to acquire connection from pool"),
|
||||||
|
Self::Db(_) => write!(f, "error while executing query"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for DbError {
|
||||||
|
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||||
|
match self {
|
||||||
|
Self::Pool(err) => Some(err),
|
||||||
|
Self::Db(err) => Some(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<diesel::r2d2::PoolError> for DbError {
|
||||||
|
fn from(value: diesel::r2d2::PoolError) -> Self {
|
||||||
|
Self::Pool(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<diesel::result::Error> for DbError {
|
||||||
|
fn from(value: diesel::result::Error) -> Self {
|
||||||
|
Self::Db(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initialize_db(path: impl AsRef<Path>, run_migrations: bool) -> Result<DbPool, DbError> {
|
||||||
|
let manager = ConnectionManager::<SqliteConnection>::new(path.as_ref().to_string_lossy());
|
||||||
|
let pool = Pool::new(manager)?;
|
||||||
|
|
||||||
|
if run_migrations {
|
||||||
|
pool.get()?.run_pending_migrations(MIGRATIONS).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(pool)
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
// @generated automatically by Diesel CLI.
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
mod db;
|
||||||
|
mod server;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
tracing::info!("Initializing database and running migrations");
|
||||||
|
|
||||||
|
let pool = db::initialize_db("data/db.sqlite3", true).unwrap();
|
||||||
|
|
||||||
|
let ctx = server::Context { pool };
|
||||||
|
let app = server::app().with_state(ctx);
|
||||||
|
|
||||||
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let address = "0.0.0.0:8080";
|
||||||
|
|
||||||
|
tracing::info!("Starting server on {address}");
|
||||||
|
|
||||||
|
rt.block_on(async {
|
||||||
|
let listener = tokio::net::TcpListener::bind(address).await.unwrap();
|
||||||
|
axum::serve(listener, app.into_make_service())
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
use axum::{
|
||||||
|
http::{HeaderName, HeaderValue},
|
||||||
|
Router,
|
||||||
|
};
|
||||||
|
use tower_http::{set_header::SetResponseHeaderLayer, trace::TraceLayer};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Context {
|
||||||
|
pub pool: crate::db::DbPool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn app() -> Router<Context> {
|
||||||
|
Router::new()
|
||||||
|
.layer(TraceLayer::new_for_http())
|
||||||
|
// https://gpoddernet.readthedocs.io/en/latest/api/reference/general.html#cors
|
||||||
|
// All endpoints should send this CORS header value so the endpoints can be used from web
|
||||||
|
// applications
|
||||||
|
.layer(SetResponseHeaderLayer::overriding(
|
||||||
|
HeaderName::from_static("access-control-allow-origin"),
|
||||||
|
HeaderValue::from_static("*"),
|
||||||
|
))
|
||||||
|
}
|
Loading…
Reference in New Issue