131 lines
3.7 KiB
Rust
131 lines
3.7 KiB
Rust
use axum::{routing::get, Router};
|
|
use sqlx::{sqlite::{SqliteConnectOptions, SqlitePoolOptions}, SqlitePool};
|
|
use std::str::FromStr;
|
|
use std::sync::Arc;
|
|
use tokio::net::TcpListener;
|
|
use std::env;
|
|
|
|
pub mod models;
|
|
pub mod middleware;
|
|
pub mod utils;
|
|
pub mod error;
|
|
pub mod handlers;
|
|
|
|
#[derive(Clone)]
|
|
pub struct AppState {
|
|
pub db: SqlitePool,
|
|
}
|
|
|
|
|
|
|
|
#[tokio::main]
|
|
async fn main() {
|
|
dotenvy::dotenv().ok();
|
|
|
|
tracing_subscriber::fmt()
|
|
.with_env_filter(
|
|
tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
|
|
"blog_cms=debug,tower_http=debug,axum=debug".into()
|
|
}),
|
|
)
|
|
.init();
|
|
|
|
let db_url = env::var("DATABASE_URL").unwrap_or_else(|_| "sqlite://data.db".to_string());
|
|
let port = env::var("PORT").unwrap_or_else(|_| "3000".to_string());
|
|
let addr = format!("0.0.0.0:{}", port);
|
|
|
|
let db_opts = SqliteConnectOptions::from_str(&db_url)
|
|
.expect("Failed to parse DATABASE_URL")
|
|
.create_if_missing(true);
|
|
|
|
let db_pool = SqlitePoolOptions::new()
|
|
.max_connections(5)
|
|
.connect_with(db_opts)
|
|
.await
|
|
.expect("Failed to connect to SQLite database");
|
|
|
|
// Create schema
|
|
sqlx::query(
|
|
r#"
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
username TEXT NOT NULL UNIQUE,
|
|
password_hash TEXT NOT NULL,
|
|
role TEXT NOT NULL DEFAULT 'readonly'
|
|
);
|
|
"#,
|
|
)
|
|
.execute(&db_pool)
|
|
.await
|
|
.expect("Failed to create users table");
|
|
|
|
sqlx::query(
|
|
r#"
|
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
id TEXT PRIMARY KEY,
|
|
user_id INTEGER NOT NULL,
|
|
expires_at INTEGER NOT NULL,
|
|
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
|
|
);
|
|
"#,
|
|
)
|
|
.execute(&db_pool)
|
|
.await
|
|
.expect("Failed to create sessions table");
|
|
|
|
sqlx::query(
|
|
r#"
|
|
CREATE TABLE IF NOT EXISTS posts (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
author_id INTEGER NOT NULL,
|
|
title TEXT NOT NULL,
|
|
content TEXT NOT NULL,
|
|
tags TEXT NOT NULL DEFAULT '',
|
|
categories TEXT NOT NULL DEFAULT '',
|
|
visibility TEXT NOT NULL DEFAULT 'public',
|
|
password TEXT,
|
|
created_at INTEGER NOT NULL,
|
|
updated_at INTEGER NOT NULL,
|
|
FOREIGN KEY(author_id) REFERENCES users(id) ON DELETE CASCADE
|
|
);
|
|
"#,
|
|
)
|
|
.execute(&db_pool)
|
|
.await
|
|
.expect("Failed to create posts table");
|
|
|
|
|
|
sqlx::query(
|
|
r#"
|
|
CREATE TABLE IF NOT EXISTS attachments (
|
|
id TEXT PRIMARY KEY,
|
|
filename TEXT NOT NULL,
|
|
content_type TEXT NOT NULL,
|
|
size INTEGER NOT NULL,
|
|
created_at INTEGER NOT NULL
|
|
);
|
|
"#,
|
|
)
|
|
.execute(&db_pool)
|
|
.await
|
|
.expect("Failed to create attachments table");
|
|
|
|
// Create attachments directory
|
|
std::fs::create_dir_all("attachments").expect("Failed to create attachments directory");
|
|
|
|
let app_state = Arc::new(AppState { db: db_pool });
|
|
|
|
let app = Router::new()
|
|
.merge(handlers::public::router())
|
|
.route("/__attachments/{id}", get(handlers::attachments::serve_attachment))
|
|
.nest("/__dungeon", handlers::router(&app_state)) // I'll create a single `handlers::router`
|
|
.with_state(app_state.clone())
|
|
.layer(axum::extract::DefaultBodyLimit::max(10 * 1024 * 1024 * 1024)) // 10GB limit to be safe
|
|
|
|
.layer(tower_http::trace::TraceLayer::new_for_http());
|
|
|
|
let listener = TcpListener::bind(&addr).await.unwrap();
|
|
println!("Server running at http://{}", addr);
|
|
axum::serve(listener, app).await.unwrap();
|
|
}
|