feat: Initialize Rust blog CMS project with Axum, Askama, SQLite, and DevContainer setup.
This commit is contained in:
10
.devcontainer/Dockerfile
Normal file
10
.devcontainer/Dockerfile
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/main/containers/rust/.devcontainer/base.Dockerfile
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/devcontainers/rust:1-1-bookworm
|
||||||
|
|
||||||
|
# [Optional] Uncomment this section to install additional packages.
|
||||||
|
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||||
|
# && apt-get -y install --no-install-recommends <your-package-list-here>
|
||||||
|
|
||||||
|
# [Optional] Uncomment to install additional Cargo tools
|
||||||
|
# RUN cargo install <your-cargo-package-list-here>
|
||||||
46
.devcontainer/devcontainer.json
Normal file
46
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"name": "Rust Dev Environment",
|
||||||
|
"build": {
|
||||||
|
"dockerfile": "Dockerfile"
|
||||||
|
},
|
||||||
|
// Features to add to the dev container. More info: https://containers.dev/features.
|
||||||
|
"features": {
|
||||||
|
"ghcr.io/devcontainers/features/common-utils:1": {
|
||||||
|
"installZsh": true,
|
||||||
|
"configureZshOhMyZsh": true,
|
||||||
|
"upgradePackages": true,
|
||||||
|
"userExperienceCustomizations": true
|
||||||
|
},
|
||||||
|
"ghcr.io/devcontainers/features/git:1": "latest"
|
||||||
|
},
|
||||||
|
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||||
|
// "forwardPorts": [8000],
|
||||||
|
// Use 'postCreateCommand' to run commands after the container is created.
|
||||||
|
"postCreateCommand": "rustup component add rust-analyzer rustfmt clippy && cargo install cargo-watch cargo-edit cargo-expand cargo-audit cargo-nextest cargo-tarpaulin",
|
||||||
|
// Configure tool-specific properties.
|
||||||
|
"customizations": {
|
||||||
|
// Configure properties specific to VS Code.
|
||||||
|
"vscode": {
|
||||||
|
// Add the IDs of extensions you want installed when the container is created.
|
||||||
|
"extensions": [
|
||||||
|
"rust-lang.rust-analyzer",
|
||||||
|
"tamasfe.even-better-toml",
|
||||||
|
"serayuzgur.crates",
|
||||||
|
"vadimcn.vscode-lldb",
|
||||||
|
"mutantdino.resourcemonitor",
|
||||||
|
"fill-labs.dependi"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"rust-analyzer.check.command": "clippy",
|
||||||
|
"rust-analyzer.cargo.buildScripts.enable": true,
|
||||||
|
"rust-analyzer.procMacro.enable": true,
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"[rust]": {
|
||||||
|
"editor.defaultFormatter": "rust-lang.rust-analyzer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Use 'remoteUser' to connect as a non-root user. More info: https://aka.ms/vscode-remote/containers/non-root.
|
||||||
|
"remoteUser": "vscode"
|
||||||
|
}
|
||||||
2
.env.example
Normal file
2
.env.example
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
PORT=3000
|
||||||
|
DATABASE_URL=sqlite://data.db
|
||||||
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
/target
|
||||||
|
|
||||||
|
# Env files
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
|
# SQLite data files
|
||||||
|
*.db
|
||||||
|
*.db-shm
|
||||||
|
*.db-wal
|
||||||
2001
Cargo.lock
generated
Normal file
2001
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
11
Cargo.toml
Normal file
11
Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
[package]
|
||||||
|
name = "blog_cms"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
askama = "0.15.4"
|
||||||
|
axum = "0.8.8"
|
||||||
|
dotenvy = "0.15.7"
|
||||||
|
sqlx = { version = "0.8.6", features = ["runtime-tokio", "sqlite"] }
|
||||||
|
tokio = { version = "1.49.0", features = ["rt-multi-thread", "macros"] }
|
||||||
38
README.md
Normal file
38
README.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# Rust Blog CMS
|
||||||
|
|
||||||
|
A simple blog CMS built with Rust, Axum, Askama, and SQLite.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- [Rust & Cargo](https://rustup.rs/) (latest stable version)
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
1. Copy the `.env.example` file to create your local `.env` file:
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Open the `.env` file and set your environment variables:
|
||||||
|
- `PORT`: The port your server will listen on (default is `3000`).
|
||||||
|
- `DATABASE_URL`: The connection string for your SQLite database (default is `sqlite://data.db`).
|
||||||
|
|
||||||
|
## Database Setup
|
||||||
|
|
||||||
|
*If you are using the `sqlx` CLI, you can create the database with:*
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo install sqlx-cli --no-default-features --features rustls,sqlite
|
||||||
|
sqlx database create
|
||||||
|
```
|
||||||
|
*(Optionally run migrations if any exist: `sqlx migrate run`)*
|
||||||
|
|
||||||
|
## Running the app
|
||||||
|
|
||||||
|
To start the server, simply run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo run
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, open your web browser and navigate to `http://localhost:3000` (or whichever port you configured).
|
||||||
77
src/main.rs
Normal file
77
src/main.rs
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
use askama::Template;
|
||||||
|
use axum::{
|
||||||
|
extract::State,
|
||||||
|
response::{Html, IntoResponse},
|
||||||
|
routing::get,
|
||||||
|
Router,
|
||||||
|
};
|
||||||
|
use sqlx::{sqlite::{SqliteConnectOptions, SqlitePoolOptions}, SqlitePool};
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tokio::net::TcpListener;
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct AppState {
|
||||||
|
db: SqlitePool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "index.html")]
|
||||||
|
struct IndexTemplate {
|
||||||
|
title: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct HtmlTemplate<T>(T);
|
||||||
|
|
||||||
|
impl<T> IntoResponse for HtmlTemplate<T>
|
||||||
|
where
|
||||||
|
T: Template,
|
||||||
|
{
|
||||||
|
fn into_response(self) -> axum::response::Response {
|
||||||
|
match self.0.render() {
|
||||||
|
Ok(html) => Html(html).into_response(),
|
||||||
|
Err(err) => (
|
||||||
|
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
format!("Failed to render template: {}", err),
|
||||||
|
)
|
||||||
|
.into_response(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn index(State(_state): State<Arc<AppState>>) -> impl IntoResponse {
|
||||||
|
let template = IndexTemplate {
|
||||||
|
title: "Coming Soon".to_string(),
|
||||||
|
};
|
||||||
|
HtmlTemplate(template)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
dotenvy::dotenv().ok();
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
let app_state = Arc::new(AppState { db: db_pool });
|
||||||
|
|
||||||
|
let app = Router::new()
|
||||||
|
.route("/", get(index))
|
||||||
|
.with_state(app_state);
|
||||||
|
|
||||||
|
let listener = TcpListener::bind(&addr).await.unwrap();
|
||||||
|
println!("Server running at http://{}", addr);
|
||||||
|
axum::serve(listener, app).await.unwrap();
|
||||||
|
}
|
||||||
42
templates/index.html
Normal file
42
templates/index.html
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{{ title }}</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background-color: #121212;
|
||||||
|
color: #ffffff;
|
||||||
|
font-family: 'Inter', 'Roboto', sans-serif;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
font-size: 4rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
background: linear-gradient(90deg, #ff8a00, #e52e71);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
animation: pulse 2s infinite alternate;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: #b3b3b3;
|
||||||
|
}
|
||||||
|
@keyframes pulse {
|
||||||
|
0% { opacity: 0.8; transform: scale(0.98); }
|
||||||
|
100% { opacity: 1; transform: scale(1.02); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Coming Soon</h1>
|
||||||
|
<p>We are working hard to build something amazing.</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user