Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
75c8202
Feat(core & env): init Dockerfile and dockerignore for bff service an…
golkity May 22, 2026
ea1763c
Feat(auth.rs): impl login handler and init crate with models
golkity May 22, 2026
fe68147
Feat(auth.rs): impl register_handler
golkity May 22, 2026
86eedcc
Feat(stream.rs):impl stream handler and impl function GET live streams
golkity May 22, 2026
338b1b3
Feat(mod):init module handlers
golkity May 22, 2026
2e3f4bd
Feat(routes): impl route strcut and impl handlers with module and ini…
golkity May 22, 2026
33c8209
Feat(module): init mod
golkity May 22, 2026
495b222
Feat(grpc): init grpc client and imp module for auth client
golkity May 22, 2026
0dcddcd
Feat(models): init module file
golkity May 23, 2026
f6375e8
Feat(Response & Request): impl pub struct for request and response
golkity May 23, 2026
fc98af5
Feat(utils): init module for utils errors and state
golkity May 23, 2026
8d85006
Feat(utils): impl state pub struct is AppState
golkity May 23, 2026
85127ed
Feat(utils): impl custom errors for this service
golkity May 23, 2026
24c3553
Feat(config): realised impl Config with pub from-env and impl Config …
golkity May 23, 2026
464965a
Feat(middleware): impl middleware and i ustal boosss
golkity May 23, 2026
7589ff2
Fix(use crate): fix use crate and impl models
golkity May 23, 2026
2815704
Fix(use crate): add use AppState
golkity May 23, 2026
09e719f
Fix(field): add new pub strcut Login Request, add new field username …
golkity May 23, 2026
bdf4bcc
Feat(main): add entery points in this bff service
golkity May 23, 2026
95157a5
Feat(build): add builder which codegenerate protobuf conf -> rust code
golkity May 23, 2026
8d84c85
Update(lib): add new lib
golkity May 23, 2026
62ce0b6
Feat(proto gRPC): add initial protobuf schemas
golkity May 23, 2026
85e8868
ci(bff): setup ci pipeline with lint, test, and security scans
golkity May 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 134 additions & 0 deletions .github/workflows/bff-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
name: BFF Service CI (Rust)

on:
push:
branches: ["dev", "master"]
paths:
- "backend/service/bff_service/**"
- "backend/libs/rust-core/**"
- "backend/proto/**"
- ".github/workflows/bff-ci.yml"
pull_request:
branches: ["dev", "master"]
paths:
- "backend/service/bff_service/**"
- "backend/libs/rust-core/**"
- "backend/proto/**"

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

env:
CARGO_TERM_COLOR: always
WORKING_DIR: backend/service/bff_service

jobs:
lint-and-format:
name: Code Quality (Fmt & Clippy)
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Install Protoc (Required for tonic-build)
run: sudo apt-get update && sudo apt-get install -y protobuf-compiler

- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy

- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: backend

- name: Check Formatting
working-directory: ${{ env.WORKING_DIR }}
run: cargo fmt --all -- --check

- name: Run Clippy (Strict Mode)
working-directory: ${{ env.WORKING_DIR }}
run: cargo clippy --workspace --all-targets --all-features -- -D warnings

security-audit:
name: Security Audit (Cargo Audit)
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Rust
uses: dtolnay/rust-toolchain@stable

- name: Install cargo-audit
uses: taiki-e/install-action@cargo-audit

- name: Run Vulnerability Scan
working-directory: ${{ env.WORKING_DIR }}
run: cargo audit

test-and-coverage:
name: Unit & Integration Tests
runs-on: ubuntu-latest
needs: [lint-and-format, security-audit]
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Install Protoc
run: sudo apt-get update && sudo apt-get install -y protobuf-compiler

- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable

- name: Install cargo-llvm-cov
uses: taiki-e/install-action@cargo-llvm-cov

- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: backend

- name: Run Tests & Generate Coverage
working-directory: ${{ env.WORKING_DIR }}
run: cargo llvm-cov test --all-features --workspace --lcov --output-path lcov.info --fail-under-lines 80

- name: Upload Coverage to Codecov (Optional)
uses: codecov/codecov-action@v4
with:
file: ${{ env.WORKING_DIR }}/lcov.info
fail_ci_if_error: false
token: ${{ secrets.CODECOV_TOKEN }}

docker-build-scan:
name: Build Docker & Trivy Scan
runs-on: ubuntu-latest
needs: test-and-coverage
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build Docker Image
uses: docker/build-push-action@v5
with:
context: backend
file: backend/service/bff_service/Dockerfile
push: false
tags: stremo/bff-service:dev-test
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: "stremo/bff-service:dev-test"
format: "table"
exit-code: "1"
ignore-unfixed: true
vuln-type: "os,library"
severity: "CRITICAL,HIGH"
30 changes: 30 additions & 0 deletions backend/proto/auth.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
syntax = "proto3";

package stremo.auth.v1;

service AuthService {
rpc Login(LoginRequest) returns (LoginResponse);
rpc Register(RegisterRequest) returns (RegisterResponse);
}

message LoginRequest {
string email = 1;
string password = 2;
}

message LoginResponse {
string access_token = 1;
string refresh_token = 2;
uint32 expires_in = 3;
}

message RegisterRequest {
string username = 1;
string email = 2;
string password = 3;
}

message RegisterResponse {
string id = 1;
string message = 2;
}
24 changes: 24 additions & 0 deletions backend/proto/stream_meta.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
syntax = "proto3";

package stremo.stream_meta.v1;

service StreamMetaService {
rpc GetLiveStreams(GetLiveStreamsRequest) returns (GetLiveStreamsResponse);
}

message GetLiveStreamsRequest {
int32 limit = 1;
string cursor = 2;
}

message StreamDto {
string stream_id = 1;
string title = 2;
string category = 3;
int32 viewers_count = 4;
}

message GetLiveStreamsResponse {
repeated StreamDto streams = 1;
string next_cursor = 2;
}
6 changes: 6 additions & 0 deletions backend/service/bff_service/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
target/
**/*.rs.bk
Cargo.lock
.git/
.env
Dockerfile
27 changes: 27 additions & 0 deletions backend/service/bff_service/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[package]
name = "bff-service"
version = "0.1.0"
edition = "2021"

[dependencies]
rust-core = { path = "../../libs/rust-core" }
axum = "0.7"
tokio = { version = "1.37", features = ["full"] }
tower = "0.4"
tower-http = { version = "0.5", features = ["trace", "cors"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
thiserror = "1.0"
jsonwebtoken = "9.3"
tonic = "0.11"
prost = "0.12"
redis = { version = "0.25", features = ["tokio-comp"] }
bb8 = "0.8"
bb8-redis = "0.15"
dotenvy = "0.15"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
uuid = { version = "1.8", features = ["v4", "serde"] }
anyhow = "1.0.102"
[build-dependencies]
tonic-build = "0.11"
14 changes: 14 additions & 0 deletions backend/service/bff_service/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM rust:1.85-slim-bookworm AS builder
RUN apt-get update && apt-get install -y pkg-config libssl-dev cmake build-essential
WORKDIR /app

COPY . .
RUN cargo build --release -p bff-service

FROM gcr.io/distroless/cc-debian12
WORKDIR /app
COPY --from=builder /app/target/release/bff-service /app/bff-service
ENV RUST_LOG=info
ENV PORT=8000
EXPOSE 8000
CMD ["/app/bff-service"]
20 changes: 20 additions & 0 deletions backend/service/bff_service/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("cargo:rerun-if-changed=../../proto/auth.proto");
println!("cargo:rerun-if-changed=../../proto/stream_meta.proto");
println!("cargo:rerun-if-changed=../../proto/errors.proto");

tonic_build::configure()
.build_server(false)
.build_client(true)
.out_dir(std::env::var("OUT_DIR").unwrap())
.compile(
&[
"../../proto/auth.proto",
"../../proto/stream_meta.proto",
"../../proto/errors.proto",
],
&["../../proto"],
)?;

Ok(())
}
51 changes: 51 additions & 0 deletions backend/service/bff_service/src/api/handlers/auth.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use axum::{extract::State, Json};
use std::sync::Arc;

use crate::{
models::{
requests::{LoginRequest, RegisterRequest},
responses::{LoginResponse, RegisterResponse},
},
utils::errors::AppError,
utils::state::AppState,
};

pub async fn login_handler(
State(state): State<Arc<AppState>>,
Json(payload): Json<LoginRequest>,
) -> Result<Json<LoginResponse>, AppError> {
if payload.email.is_empty() || payload.password.is_empty() {
return Err(AppError::BadRequest(
"Email and password are required".to_string(),
));
}

let mut auth_client = state.auth_grpc_client.clone();

let grpc_res = auth_client.login(payload.email, payload.password).await?;

Ok(Json(LoginResponse {
access_token: grpc_res.access_token,
refresh_token: grpc_res.refresh_token,
expires_in: grpc_res.expires_in,
}))
}

pub async fn register_handler(
State(state): State<Arc<AppState>>,
Json(payload): Json<RegisterRequest>,
) -> Result<Json<RegisterResponse>, AppError> {
if payload.username.is_empty() {
return Err(AppError::BadRequest("Username is required".to_string()));
}

let mut auth_client = state.auth_grpc_client.clone();
let grpc_res = auth_client
.register(payload.username, payload.email, payload.password)
.await?;

Ok(Json(RegisterResponse {
id: grpc_res.id,
message: grpc_res.message,
}))
}
2 changes: 2 additions & 0 deletions backend/service/bff_service/src/api/handlers/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod auth;
pub mod stream;
41 changes: 41 additions & 0 deletions backend/service/bff_service/src/api/handlers/stream.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use axum::{
extract::{Query, State},
Json,
};
use std::sync::Arc;

use crate::{
models::{
requests::GetStreamsQuery,
responses::{StreamCatalogItem, StreamCatalogResponse},
},
utils::errors::AppError,
utils::state::AppState,
};

pub async fn get_live_streams_handler(
State(state): State<Arc<AppState>>,
Query(query): Query<GetStreamsQuery>,
) -> Result<Json<StreamCatalogResponse>, AppError> {
let limit = query.limit.unwrap_or(20);
let cursor = query.cursor.unwrap_or_default();

let mut stream_client = state.stream_meta_grpc_client.clone();
let grpc_res = stream_client.get_live_streams(limit, cursor).await?;

let streams = grpc_res
.streams
.into_iter()
.map(|s| StreamCatalogItem {
stream_id: s.stream_id,
title: s.title,
category: s.category,
viewers_count: s.viewers_count,
})
.collect();

Ok(Json(StreamCatalogResponse {
data: streams,
next_cursor: grpc_res.next_cursor,
}))
}
2 changes: 2 additions & 0 deletions backend/service/bff_service/src/api/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod handlers;
pub mod routes;
9 changes: 9 additions & 0 deletions backend/service/bff_service/src/api/routes/auth.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use crate::{api::handlers::auth, utils::state::AppState};
use axum::{routing::post, Router};
use std::sync::Arc;

pub fn router() -> Router<Arc<AppState>> {
Router::new()
.route("/login", post(auth::login_handler))
.route("/register", post(auth::register_handler))
}
2 changes: 2 additions & 0 deletions backend/service/bff_service/src/api/routes/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod auth;
pub mod stream;
7 changes: 7 additions & 0 deletions backend/service/bff_service/src/api/routes/stream.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use crate::{api::handlers::stream, utils::state::AppState};
use axum::{routing::get, Router};
use std::sync::Arc;

pub fn router() -> Router<Arc<AppState>> {
Router::new().route("/live", get(stream::get_live_streams_handler))
}
Loading
Loading