Skip to content

Commit c743480

Browse files
committed
modify README.md and add ci-cd
1 parent 5a05a26 commit c743480

File tree

13 files changed

+113
-63
lines changed

13 files changed

+113
-63
lines changed

.github/workflows/ci-cd.yaml

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
name: Leptos WASM Blog CI/CD
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
workflow_dispatch:
9+
10+
jobs:
11+
check:
12+
name: Check & Lint
13+
runs-on: ubuntu-latest
14+
steps:
15+
- name: Checkout code
16+
uses: actions/checkout@v4
17+
18+
- name: Install Rust toolchain
19+
uses: dtolnay/rust-toolchain@master
20+
with:
21+
toolchain: stable
22+
components: rustfmt, clippy
23+
targets: wasm32-unknown-unknown
24+
25+
- name: Check formatting
26+
run: cargo fmt --all -- --check
27+
28+
- name: Run clippy
29+
run: cargo clippy --all-targets --features ssr -- -D warnings
30+
31+
test:
32+
name: Test
33+
runs-on: ubuntu-latest
34+
needs: check
35+
steps:
36+
- name: Checkout code
37+
uses: actions/checkout@v4
38+
39+
- name: Install Rust toolchain
40+
uses: dtolnay/rust-toolchain@master
41+
with:
42+
toolchain: stable
43+
targets: wasm32-unknown-unknown
44+
45+
- name: Run tests
46+
run: cargo test --features ssr
47+
48+
build:
49+
name: Build WASM
50+
runs-on: ubuntu-latest
51+
needs: test
52+
steps:
53+
- name: Checkout code
54+
uses: actions/checkout@v4
55+
56+
- name: Install Rust toolchain
57+
uses: dtolnay/rust-toolchain@master
58+
with:
59+
toolchain: stable
60+
targets: wasm32-unknown-unknown
61+
62+
- name: Install cargo-leptos
63+
run: cargo install cargo-leptos
64+
65+
- name: Build Leptos WASM
66+
run: cargo leptos build --release

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ leptos_meta = { version = "0.8.0" }
1717
tokio = { version = "1", features = ["rt-multi-thread"], optional = true }
1818
wasm-bindgen = { version = "0.2.101", optional = true }
1919
sqlx = { version = "0.8.6", features = ["runtime-tokio", "postgres", "macros", "uuid", "chrono"], optional = true }
20-
chrono = { version = "0.4.41", features = ["serde"] }
20+
chrono = { version = "0.4.42", features = ["serde"] }
2121
uuid = { version = "1.18.1", features = ["v4", "v5", "serde", "js"] }
2222
thiserror = "2.0.16"
2323
serde = { version = "1.0.219", features = ["derive"] }

README.md

Lines changed: 23 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
# Willian's Personal Blog
22

3-
The blog is at [wiiliannguyen.com](https://www.williannguyen.com)
3+
The final product is [williannguyen.com](https://williannguyen.com).
44

5-
## Architecture Overview
5+
Full personal reflection on this project can be found on this [post](https://williannguyen.com/posts/4).
6+
7+
PS: It works. If it does not, please dm me. Thank you for your helps.
8+
9+
## ARCHITECTURE OVERVIEW
610

711
This application follows a **full-stack Rust architecture** using:
812

@@ -12,7 +16,7 @@ This application follows a **full-stack Rust architecture** using:
1216
- **Content**: Markdown-based blog posts with syntax highlighting
1317
- **Build System**: Custom build script for static content generation
1418

15-
## Tech Stack
19+
## TECH STACK
1620

1721
### Core Framework
1822

@@ -32,27 +36,18 @@ This application follows a **full-stack Rust architecture** using:
3236
- **[syntect](https://docs.rs/syntect/)** - Syntax highlighting for code blocks
3337
- **Static Content Generation** - Build-time markdown processing
3438

35-
### Middleware & Security
36-
37-
- **Rate Limiting** - Per-IP request throttling using Governor
38-
- **CORS** - Cross-Origin Resource Sharing configuration
39-
- **CSRF Protection** - Token-based CSRF mitigation
40-
- **Security Headers** - Comprehensive HTTP security headers
41-
- **Request Timeout** - Configurable request timeouts
42-
- **Compression** - Brotli compression for responses
43-
4439
### Development & Deployment
4540

4641
- **[cargo-leptos](https://crates.io/crates/cargo-leptos)** - Leptos build tool
4742
- **Hot Reload** - Development server with live reloading
4843
- **WASM Optimization** - Size-optimized WebAssembly builds
4944
- **End-to-End Testing** - Playwright integration
5045

51-
## Project Structure
46+
## PROJECT STRUCTURE
5247

5348
### 🌐 Frontend (Leptos)
5449

55-
The implementation is obtained from cargo-leptos axum template provided from. For more detail, please follow the the [instruction](https://github.com/leptos-rs/start-axum).
50+
The implementation is obtained from cargo-leptos axum template. For more detail, please follow the the [instruction](https://github.com/leptos-rs/start-axum).
5651

5752
```text
5853
src/
@@ -124,17 +119,14 @@ graph TD
124119

125120
#### Middleware & Security Features
126121

127-
- Rate Limiting: Per-IP request throttling using Governor
128-
- CORS: Cross-Origin Resource Sharing configuration
129-
- CSRF Protection: Token-based CSRF mitigation
130-
- Security Headers: Comprehensive HTTP security headers
131-
- Request Timeout: Configurable request timeouts
132-
- Compression: Brotli compression for responses
133-
134-
#### Middleware Implementation
135-
136-
- Global: Applied in server.rs (compression, timeout, CORS, security headers)
137-
- Route-specific: Applied in routes/ modules. For example, subscriber routes apply no_cache, governor, throttle, and CSRF layers.
122+
- **Global**
123+
- Compression: Brotli compression for responses
124+
- Request Timeout: Configurable request timeouts
125+
- Security Headers: Comprehensive HTTP security headers
126+
- **Route specific**
127+
- Rate Limiting: Per-IP request throttling using Governor
128+
- CORS: Cross-Origin Resource Sharing configuration
129+
- CSRF Protection: Token-based CSRF mitigation
138130

139131
#### Layer Responsibilities
140132

@@ -145,7 +137,7 @@ graph TD
145137
- Models: Data structures, serialization, validation rules
146138
- DB: Connection pooling, configuration, state management
147139

148-
## Build System
140+
## BUILD SYSTEM
149141

150142
The project uses a custom build script (`build.rs`) that:
151143

@@ -154,7 +146,7 @@ The project uses a custom build script (`build.rs`) that:
154146
3. **Static Generation**: Converts markdown to HTML at build time
155147
4. **Optimized Output**: Generates Rust code with static post data
156148

157-
## Performance Features
149+
## PERFORMANCE FEATURES
158150

159151
- **Server-Side Rendering (SSR)**: Fast initial page loads
160152
- **Hydration**: Interactive client-side features without full SPA overhead
@@ -165,7 +157,7 @@ The project uses a custom build script (`build.rs`) that:
165157
- **HTTP Caching Strategy**: Multi-tier caching system for optimal performance
166158
- **WASM Optimization**: Aggressive size optimization for client-side bundles
167159

168-
## WASM Bundle Optimization
160+
## WASM BUNDLE OPTIMIZATION
169161

170162
The WebAssembly build process includes several standard optimization techniques to minimize bundle size:
171163

@@ -195,7 +187,7 @@ Below information is obtained from the actual implementation on the project and
195187
- **Better mobile experience**: Smaller bundles improve performance on slower connections
196188
- **Production ready**: Size is now within reasonable limits for web deployment
197189

198-
## Caching
190+
## CACHING
199191

200192
Deploy standard industry practices
201193

@@ -212,15 +204,11 @@ Deploy standard industry practices
212204

213205
- Centralized Error Handling (**Status**: Planned)
214206

215-
Will add a middleware layer to catch errors, log them, and provide consistent user-friendly responses. Implementation is postponed until the app grows in complexity.
207+
To add a middleware layer to catch errors, log them, and provide consistent user-friendly responses. Implementation is postponed until the app grows in complexity.
216208

217209
- Modulized global layer (**Status**: Planned)
218210

219-
This was planned out at the begginning with `tower` crate 's ServiceBuilder as a global layer which is then called into server.rs.
220-
221-
However, refactoring this seperated out from server.rs run is more troublesome than expected.
222-
223-
Until the project expands further, it is placed directly in server.rs.
211+
This was planned out at the begginning with `tower` crate 's ServiceBuilder as a global layer which is then called into server.rs. However, refactoring this seperated out from server.rs run is more troublesome than expected. Until the project expands further, it is placed directly in server.rs.
224212

225213
## Reference
226214

src/app/components/nav.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ pub fn Nav() -> impl IntoView {
3838
#[cfg(feature = "hydrate")]
3939
Effect::new(move |_| {
4040
use wasm_bindgen::JsCast;
41-
use web_sys::{window, js_sys};
41+
use web_sys::{js_sys, window};
4242

4343
if let Some(window) = window() {
4444
let closure = wasm_bindgen::closure::Closure::wrap(Box::new(move || {
@@ -49,7 +49,7 @@ pub fn Nav() -> impl IntoView {
4949
let _ = js_sys::Reflect::set(
5050
&window,
5151
&"__nav_theme_toggle".into(),
52-
closure.as_ref().unchecked_ref()
52+
closure.as_ref().unchecked_ref(),
5353
);
5454
closure.forget();
5555
}

src/app/pages/homepage.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,3 @@ pub fn HomePage() -> impl IntoView {
1010
<p>"Anything that I learn and find interesting, I will write about it on my blog."</p>
1111
}
1212
}
13-
14-

src/server/db.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
pub mod pool;
21
pub mod config;
3-
pub mod state;
42
pub mod error;
3+
pub mod pool;
4+
pub mod state;

src/server/handlers/subscriber.rs

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,15 @@ pub async fn subscribe_handler(
3333
})),
3434
Err(e) => {
3535
// Example: check for unique violation (already subscribed)
36-
if let Some(db_err) = e.as_database_error() {
37-
if let Some(code) = db_err.code() {
38-
if code == "23505" {
39-
// Unique violation in Postgres
40-
return Ok(Json(SubscribeResponse {
41-
status: "warning".to_string(),
42-
message: "You're already subscribed with this email address."
43-
.to_string(),
44-
}));
45-
}
46-
}
36+
if let Some(db_err) = e.as_database_error()
37+
&& let Some(code) = db_err.code()
38+
&& code == "23505"
39+
{
40+
// Unique violation in Postgres
41+
return Ok(Json(SubscribeResponse {
42+
status: "warning".to_string(),
43+
message: "You're already subscribed with this email address.".to_string(),
44+
}));
4745
}
4846
Err(axum::http::StatusCode::INTERNAL_SERVER_ERROR)
4947
}

src/server/middleware.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
pub mod cache;
2+
pub mod csrf;
23
pub mod global_layer;
34
pub mod governor;
45
pub mod throttle;
5-
pub mod csrf;

src/server/middleware/csrf.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
use axum::{
2+
body::Body,
23
http::{Request, StatusCode},
34
middleware::Next,
45
response::Response,
5-
body::Body,
66
};
77
use axum_extra::extract::cookie::CookieJar;
8-
use rand::RngCore;
98
use base64::{Engine as _, engine::general_purpose};
9+
use rand::RngCore;
1010

1111
const CSRF_HEADER: &str = "x-csrf-token";
1212
const CSRF_COOKIE: &str = "csrf_token";

src/server/middleware/global_layer/security_headers.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use axum::{
1010

1111
pub async fn security_headers(req: Request<Body>, next: Next) -> Response {
1212
let mut res = next.run(req).await;
13-
13+
1414
let headers: [(&str, &str); 5] = [
1515
("x-content-type-options", "nosniff"),
1616
("x-frame-options", "DENY"),

0 commit comments

Comments
 (0)