Skip to content

Commit f04ae8d

Browse files
committed
status feature added with new ci cd
1 parent 60d5fd6 commit f04ae8d

File tree

15 files changed

+159
-20
lines changed

15 files changed

+159
-20
lines changed

.github/workflows/ci-cd.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ jobs:
4141
POSTGRES_DB: devdb
4242
ports:
4343
- 5432:5432
44+
options: >-
45+
--health-cmd pg_isready
46+
--health-interval 10s
47+
--health-timeout 5s
48+
--health-retries 5
4449
env:
4550
DATABASE_URL: postgres://devuser:devpassword@localhost:5432/devdb
4651
DB_HOST: localhost
@@ -63,6 +68,13 @@ jobs:
6368
sleep 1
6469
done
6570
71+
- name: Wait for Postgres (fallback)
72+
run: |
73+
for i in {1..30}; do
74+
nc -z localhost 5432 && break
75+
sleep 1
76+
done
77+
6678
- name: Run migrations
6779
run: sqlx migrate run --database-url $DATABASE_URL
6880

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ axum-extra = { version = "0.10.1", features = ["cookie"], optional = true }
3838
rand = { version = "0.9.2", optional = true }
3939
base64 = { version = "0.22", optional = true }
4040
governor = { version = "0.10.1", optional = true }
41+
reqwest = { version = "0.12", features = ["json", "rustls-tls"], optional = false }
4142

4243
[features]
4344
hydrate = [

README.md

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ The final product is [williannguyen.com](https://williannguyen.com).
44

55
Full personal reflection on this project can be found on this [post](https://williannguyen.com/posts/4).
66

7-
PS: It works. If it does not, please dm me. Thank you for your helps.
7+
**PS**:
8+
9+
There is a regular status service to check that the website should work. This is one of the feature can be seen in the source code. If it does not, please dm me. Thank you for your helps.
10+
11+
![Website Status](https://img.shields.io/endpoint?url=https://williannguyen.com/status-badge)
812

913
## ARCHITECTURE OVERVIEW
1014

@@ -88,20 +92,25 @@ However, the industry-graded architecture is purposely used to study fullstack t
8892
│ ├── cors.rs # Cross-Origin Resource Sharing
8993
│ └── security_headers.rs # Security headers middleware
9094
├── models.rs # Data model coordinator
91-
├── models/
92-
│ └── subscriber.rs # Newsletter subscriber model
93-
├── repositories.rs # Data access coordinator
94-
├── repositories/
95-
│ └── subscriber.rs # Database queries & data access
96-
├── services.rs # Business logic coordinator
97-
├── services/
98-
│ └── subscriber.rs # Newsletter business logic
99-
├── handlers.rs # Request handler coordinator
100-
├── handlers/
101-
│ └── subscriber.rs # HTTP request/response handling
102-
├── routes.rs # API route coordinator
103-
└── routes/
104-
└── subscriber.rs # Newsletter API endpoints
95+
├── models/
96+
│ ├── subscriber.rs # Newsletter subscriber model
97+
│ └── status.rs # Status badge model (for shields.io)
98+
├── repositories.rs # Data access coordinator
99+
├── repositories/
100+
│ ├── subscriber.rs # Database queries & data access
101+
│ └── status.rs # Status badge logic (checks and aggregates status)
102+
├── services.rs # Business logic coordinator
103+
├── services/
104+
│ ├── subscriber.rs # Newsletter business logic
105+
│ └── status.rs # Status badge update logic (periodic background updater)
106+
├── handlers.rs # Request handler coordinator
107+
├── handlers/
108+
│ ├── subscriber.rs # HTTP request/response handling
109+
│ └── status.rs # Status badge API handler (serves cached status)
110+
├── routes.rs # API route coordinator
111+
└── routes/
112+
├── subscriber.rs # Newsletter API endpoints
113+
└── status.rs # Status badge API endpoint (`/status-badge` for shields.io)
105114
```
106115

107116
### 🏘️ Backend Layer Relationships

src/server.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,27 @@ use crate::app::shell;
1212
use crate::server::{
1313
db::{config, pool, state::AppState},
1414
middleware::global_layer::{cors_layer, security_headers},
15-
routes::subscriber::subscriber_routes,
15+
models::status::StatusBadge,
16+
routes::{status::status_routes, subscriber::subscriber_routes},
17+
services::status::StatusService,
1618
};
1719
use axum::{Router, middleware::from_fn};
1820
use leptos::prelude::*;
1921
use leptos_axum::{LeptosRoutes, generate_route_list};
22+
use std::sync::{Arc, Mutex};
2023
use std::time::Duration;
2124
use tower_http::{compression::CompressionLayer, timeout::TimeoutLayer, trace::TraceLayer};
2225
use tracing_subscriber::{EnvFilter, fmt, layer::SubscriberExt, util::SubscriberInitExt};
2326

2427
/// Main server run function - called by main.rs
2528
#[cfg(feature = "ssr")]
2629
pub async fn run() {
30+
// Shared status state
31+
let status: Arc<Mutex<StatusBadge>> = Arc::new(Mutex::new(StatusBadge::unknown()));
32+
33+
// Start periodic status monitor
34+
StatusService::start_status_monitor(status.clone());
35+
2736
// Load environment variables from .env file
2837
dotenvy::dotenv().ok();
2938

@@ -64,7 +73,8 @@ pub async fn run() {
6473
})
6574
.fallback(leptos_axum::file_and_error_handler(shell))
6675
.with_state(leptos_options.clone())
67-
.merge(subscriber_routes().with_state(app_state));
76+
.merge(subscriber_routes().with_state(app_state))
77+
.merge(status_routes(status));
6878

6979
// Start the server
7080
let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();

src/server/handlers.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
pub mod status;
12
pub mod subscriber;

src/server/handlers/status.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
use crate::server::models::status::StatusBadge;
2+
use crate::server::services::status::StatusData;
3+
use axum::{Json, extract::State};
4+
5+
pub async fn status_badge(State(status): State<StatusData>) -> Json<StatusBadge> {
6+
let data = status.lock().unwrap().clone();
7+
Json(data)
8+
}

src/server/handlers/subscriber.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::server::db::state::AppState;
22
use crate::server::services::subscriber::SubscriberService;
33
use crate::shared::dto::SubscribeResponse;
4-
use axum::{Json, extract::State};
4+
use axum::{Json, extract::State, http::StatusCode};
55
use serde::Deserialize;
66

77
/// DTO for incoming subscription requests
@@ -17,7 +17,7 @@ pub struct SubscribeRequest {
1717
pub async fn subscribe_handler(
1818
State(app_state): State<AppState>,
1919
Json(request): Json<SubscribeRequest>,
20-
) -> Result<Json<SubscribeResponse>, axum::http::StatusCode> {
20+
) -> Result<Json<SubscribeResponse>, StatusCode> {
2121
match SubscriberService::subscribe(
2222
&app_state.db_pool,
2323
request.email,
@@ -43,7 +43,7 @@ pub async fn subscribe_handler(
4343
message: "You're already subscribed with this email address.".to_string(),
4444
}));
4545
}
46-
Err(axum::http::StatusCode::INTERNAL_SERVER_ERROR)
46+
Err(StatusCode::INTERNAL_SERVER_ERROR)
4747
}
4848
}
4949
}

src/server/models.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
pub mod status;
12
pub mod subscriber;

src/server/models/status.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
use serde::{Deserialize, Serialize};
2+
3+
#[derive(Debug, Clone, Serialize, Deserialize)]
4+
pub struct StatusBadge {
5+
pub schema_version: u8,
6+
pub label: String,
7+
pub message: String,
8+
pub color: String,
9+
}

src/server/repositories.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
pub mod status;
12
pub mod subscriber;

0 commit comments

Comments
 (0)