chore: set up skeleton project

episode-actions
Jef Roosens 2025-02-23 10:31:03 +01:00
commit b6a8ee0bbe
No known key found for this signature in database
GPG Key ID: 21FD3D77D56BAF49
10 changed files with 1278 additions and 0 deletions

1
.env 100644
View File

@ -0,0 +1 @@
DATABASE_URL=data/db.sqlite3

2
.gitignore vendored 100644
View File

@ -0,0 +1,2 @@
/target
data

1137
Cargo.lock generated 100644

File diff suppressed because it is too large Load Diff

14
Cargo.toml 100644
View File

@ -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"

9
diesel.toml 100644
View File

@ -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"

62
src/db/mod.rs 100644
View File

@ -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)
}

View File

2
src/db/schema.rs 100644
View File

@ -0,0 +1,2 @@
// @generated automatically by Diesel CLI.

29
src/main.rs 100644
View File

@ -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()
});
}

22
src/server/mod.rs 100644
View File

@ -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("*"),
))
}