Files
51l3nt51n-blog/src/main.rs

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