ci: added basic workflows #2

Merged
sharang merged 14 commits from feat/CAI-5 into main 2026-02-18 09:46:29 +00:00
11 changed files with 316 additions and 58 deletions

177
.gitea/workflows/ci.yml Normal file
View File

@@ -0,0 +1,177 @@
name: CI
on:
push:
branches:
- "**"
pull_request:
branches:
- main
env:
CARGO_TERM_COLOR: always
RUSTFLAGS: "-D warnings"
# Cancel in-progress runs for the same branch/PR
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
# ---------------------------------------------------------------------------
# Stage 1: Code quality checks (run in parallel)
# ---------------------------------------------------------------------------
fmt:
name: Format
runs-on: docker
container:
image: rust:1.89-bookworm
steps:
- name: Checkout
run: |
git init
git remote add origin "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git"
git fetch --depth=1 origin "${GITHUB_SHA}"
git checkout FETCH_HEAD
- run: rustup component add rustfmt
- run: cargo fmt --check
- name: Install dx CLI
run: cargo install dioxus-cli@0.7.3 --locked
- name: RSX format check
run: dx fmt --check
clippy:
name: Clippy
runs-on: docker
container:
image: rust:1.89-bookworm
steps:
- name: Checkout
run: |
git init
git remote add origin "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git"
git fetch --depth=1 origin "${GITHUB_SHA}"
git checkout FETCH_HEAD
- run: rustup component add clippy
# Lint both feature sets independently
- name: Clippy (server)
run: cargo clippy --features server --no-default-features -- -D warnings
- name: Clippy (web)
run: cargo clippy --features web --no-default-features -- -D warnings
audit:
name: Security Audit
runs-on: docker
container:
image: rust:1.89-bookworm
steps:
- name: Checkout
run: |
git init
git remote add origin "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git"
git fetch --depth=1 origin "${GITHUB_SHA}"
git checkout FETCH_HEAD
- run: cargo install cargo-audit
- run: cargo audit
# ---------------------------------------------------------------------------
# Stage 2: Tests (only after all quality checks pass)
# ---------------------------------------------------------------------------
test:
name: Tests
runs-on: docker
needs: [fmt, clippy, audit]
container:
image: rust:1.89-bookworm
steps:
- name: Checkout
run: |
git init
git remote add origin "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git"
git fetch --depth=1 origin "${GITHUB_SHA}"
git checkout FETCH_HEAD
- name: Run tests (server)
run: cargo test --features server --no-default-features
- name: Run tests (web)
run: cargo test --features web --no-default-features
# ---------------------------------------------------------------------------
# Stage 3: Build Docker image and push to registry
# Only on main and release/* branches
# ---------------------------------------------------------------------------
build-and-push:
name: Build & Push Image
runs-on: docker
needs: [test]
if: >-
github.event_name == 'push' &&
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/'))
steps:
- name: Checkout
run: |
git init
git remote add origin "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git"
git fetch --depth=1 origin "${GITHUB_SHA}"
git checkout FETCH_HEAD
- name: Determine image tag
id: tag
run: |
BRANCH="${GITHUB_REF#refs/heads/}"
# Replace / with - for valid Docker tags (e.g. release/1.0 -> release-1.0)
BRANCH_SAFE=$(echo "$BRANCH" | tr '/' '-')
SHA=$(echo "$GITHUB_SHA" | head -c 8)
echo "tag=${BRANCH_SAFE}-${SHA}" >> "$GITHUB_OUTPUT"
- name: Log in to container registry
run: >-
echo "${{ secrets.REGISTRY_PASSWORD }}"
| docker login https://registry.meghsakha.com
-u "${{ secrets.REGISTRY_USERNAME }}"
--password-stdin
- name: Build Docker image
run: >-
docker build
-t registry.meghsakha.com/certifai/dashboard:${{ steps.tag.outputs.tag }}
-t registry.meghsakha.com/certifai/dashboard:latest
.
- name: Push Docker image
run: |
docker push registry.meghsakha.com/certifai/dashboard:${{ steps.tag.outputs.tag }}
docker push registry.meghsakha.com/certifai/dashboard:latest
# ---------------------------------------------------------------------------
# Stage 3b: Generate changelog from conventional commits
# Only on main and release/* branches
# ---------------------------------------------------------------------------
changelog:
name: Changelog
runs-on: docker
needs: [test]
if: >-
github.event_name == 'push' &&
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/'))
container:
image: rust:1.89-bookworm
steps:
- name: Checkout (full history)
run: |
git clone "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git" .
git checkout "${GITHUB_SHA}"
- name: Install git-cliff
run: cargo install git-cliff --locked
- name: Generate changelog
run: git cliff --output CHANGELOG.md
- name: Commit and push changelog
run: |
git config user.name "CI Bot"
git config user.email "ci@certifai.local"
git add CHANGELOG.md
if git diff --cached --quiet; then
echo "No changelog changes to commit"
else
git commit -m "docs: update CHANGELOG.md [skip ci]"
git push origin HEAD:"${GITHUB_REF_NAME}"
fi

61
Dockerfile Normal file
View File

@@ -0,0 +1,61 @@
# Stage 1: Generate dependency recipe for caching
FROM rust:1.89-bookworm AS chef
RUN cargo install cargo-chef
WORKDIR /app
FROM chef AS planner
COPY . .
RUN cargo chef prepare --recipe-path recipe.json
# Stage 2: Build dependencies + application
FROM chef AS builder
# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
pkg-config libssl-dev curl unzip \
&& rm -rf /var/lib/apt/lists/*
# Install bun (for Tailwind CSS build step)
RUN curl -fsSL https://bun.sh/install | bash
ENV PATH="/root/.bun/bin:$PATH"
# Install dx CLI via cargo-binstall
RUN curl -L --proto '=https' --tlsv1.2 -sSf \
https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh \
| bash
RUN cargo binstall dioxus-cli@0.7.3 --root /.cargo -y --force
ENV PATH="/.cargo/bin:$PATH"
# Cook dependencies from recipe (cached layer)
COPY --from=planner /app/recipe.json recipe.json
RUN cargo chef cook --release --recipe-path recipe.json
# Copy source and build
COPY . .
# Ensure styles directory exists for build.rs tailwind step
RUN mkdir -p styles && touch styles/input.css
# Bundle the fullstack application
RUN dx bundle --platform fullstack
# Stage 3: Minimal runtime image
FROM debian:bookworm-slim AS runtime
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates libssl3 \
&& rm -rf /var/lib/apt/lists/*
RUN useradd --create-home --shell /bin/bash app
USER app
WORKDIR /home/app
# Copy the bundled output from builder
COPY --from=builder --chown=app:app /app/target/dx/dashboard/release/web/ ./
EXPOSE 8000
ENV IP=0.0.0.0
ENV PORT=8000
ENTRYPOINT ["./server"]

View File

@@ -1,6 +1,5 @@
#![allow(non_snake_case)]
#[allow(clippy::expect_used)]
fn main() {
// Init logger
dioxus_logger::init(tracing::Level::DEBUG).expect("Failed to init logger");

View File

@@ -1,8 +1,9 @@
#[allow(clippy::expect_used)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
use std::process::Command;
println!("cargo:rerun-if-changed=./styles/input.css");
Command::new("bunx")
// Tailwind build is optional - skip gracefully in CI or environments without bun
match Command::new("bunx")
.args([
"@tailwindcss/cli",
"-i",
@@ -11,7 +12,15 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
"./assets/tailwind.css",
])
.status()
.expect("could not run tailwind");
{
Ok(status) if !status.success() => {
println!("cargo:warning=tailwind build exited with {status}, skipping CSS generation");
}
Err(e) => {
println!("cargo:warning=bunx not found ({e}), skipping tailwind CSS generation");
}
Ok(_) => {}
}
Ok(())
}

52
cliff.toml Normal file
View File

@@ -0,0 +1,52 @@
[changelog]
header = """
# Changelog
All notable changes to this project will be documented in this file.
"""
body = """
{%- macro remote_url() -%}
https://gitea.meghsakha.com/{{ remote.github.owner }}/{{ remote.github.repo }}
{%- endmacro -%}
{% if version -%}
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
{% else -%}
## [Unreleased]
{% endif -%}
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | striptags | trim | upper_first }}
{% for commit in commits %}
- {% if commit.scope %}*({{ commit.scope }})* {% endif %}{{ commit.message | upper_first }}\
{% if commit.breaking %} [**breaking**]{% endif %}\
{% endfor %}
{% endfor %}\n
"""
footer = """
---
*Generated by [git-cliff](https://git-cliff.org)*
"""
trim = true
[git]
conventional_commits = true
filter_unconventional = true
split_commits = false
commit_parsers = [
{ message = "^feat", group = "Features" },
{ message = "^fix", group = "Bug Fixes" },
{ message = "^doc", group = "Documentation" },
{ message = "^perf", group = "Performance" },
{ message = "^refactor", group = "Refactoring" },
{ message = "^style", group = "Styling" },
{ message = "^test", group = "Testing" },
{ message = "^ci", group = "CI/CD" },
{ message = "^chore", group = "Miscellaneous" },
{ message = "^build", group = "Build" },
]
protect_breaking_commits = false
filter_commits = false
tag_pattern = "v[0-9].*"
sort_commits = "oldest"

View File

@@ -15,9 +15,7 @@ pub fn AppShell() -> Element {
email: "user@example.com".to_string(),
avatar_url: String::new(),
}
main { class: "main-content",
Outlet::<Route> {}
}
main { class: "main-content", Outlet::<Route> {} }
}
}
}

View File

@@ -9,12 +9,7 @@ use dioxus::prelude::*;
/// * `href` - URL the card links to when clicked.
/// * `icon` - Element rendered as the card icon (typically a `dioxus_free_icons::Icon`).
#[component]
pub fn DashboardCard(
title: String,
description: String,
href: String,
icon: Element,
) -> Element {
pub fn DashboardCard(title: String, description: String, href: String, icon: Element) -> Element {
rsx! {
a { class: "dashboard-card", href: "{href}",
div { class: "card-icon", {icon} }

View File

@@ -1,7 +1,6 @@
use dioxus::prelude::*;
use dioxus_free_icons::icons::bs_icons::{
BsBoxArrowRight, BsFileEarmarkText, BsGear, BsGithub, BsGrid,
BsHouseDoor, BsRobot,
BsBoxArrowRight, BsFileEarmarkText, BsGear, BsGithub, BsGrid, BsHouseDoor, BsRobot,
};
use dioxus_free_icons::icons::fa_solid_icons::FaCubes;
use dioxus_free_icons::Icon;
@@ -66,15 +65,9 @@ pub fn Sidebar(email: String, avatar_url: String) -> Element {
{
// Simple active check: highlight Overview only when on `/`.
let is_active = item.route == current_route;
let cls = if is_active {
"sidebar-link active"
} else {
"sidebar-link"
};
let cls = if is_active { "sidebar-link active" } else { "sidebar-link" };
rsx! {
Link {
to: item.route,
class: cls,
Link { to: item.route, class: cls,
{item.icon}
span { "{item.label}" }
}
@@ -135,16 +128,10 @@ fn SidebarFooter() -> Element {
rsx! {
footer { class: "sidebar-footer",
div { class: "sidebar-social",
a {
href: "#",
class: "social-link",
title: "GitHub",
a { href: "#", class: "social-link", title: "GitHub",
Icon { icon: BsGithub, width: 16, height: 16 }
}
a {
href: "#",
class: "social-link",
title: "Impressum",
a { href: "#", class: "social-link", title: "Impressum",
Icon { icon: BsGrid, width: 16, height: 16 }
}
}

View File

@@ -214,7 +214,7 @@ pub async fn auth_callback(
let client = reqwest::Client::new();
let token_resp = client
.post(&config.token_endpoint())
.post(config.token_endpoint())
.form(&[
("grant_type", "authorization_code"),
("client_id", &config.client_id),
@@ -237,7 +237,7 @@ pub async fn auth_callback(
// --- Fetch userinfo ---
let userinfo: UserinfoResponse = client
.get(&config.userinfo_endpoint())
.get(config.userinfo_endpoint())
.bearer_auth(&tokens.access_token)
.send()
.await

View File

@@ -1,11 +1,7 @@
use std::{
ops::{Deref, DerefMut},
sync::Arc,
};
use std::{ops::Deref, sync::Arc};
use axum::extract::FromRequestParts;
use serde::{Deserialize, Serialize};
use tracing::debug;
#[derive(Debug, Clone)]
pub struct UserState(Arc<UserStateInner>);

View File

@@ -40,11 +40,7 @@ pub fn OverviewPage() -> Element {
description: "Guides & API Reference".to_string(),
href: "#".to_string(),
icon: rsx! {
Icon {
icon: BsBook,
width: 28,
height: 28,
}
Icon { icon: BsBook, width: 28, height: 28 }
},
}
DashboardCard {
@@ -52,11 +48,7 @@ pub fn OverviewPage() -> Element {
description: "Observability & Analytics".to_string(),
href: "#".to_string(),
icon: rsx! {
Icon {
icon: FaChartLine,
width: 28,
height: 28,
}
Icon { icon: FaChartLine, width: 28, height: 28 }
},
}
DashboardCard {
@@ -64,11 +56,7 @@ pub fn OverviewPage() -> Element {
description: "Agent Framework".to_string(),
href: "#".to_string(),
icon: rsx! {
Icon {
icon: FaGears,
width: 28,
height: 28,
}
Icon { icon: FaGears, width: 28, height: 28 }
},
}
DashboardCard {
@@ -76,11 +64,7 @@ pub fn OverviewPage() -> Element {
description: "Browse Models".to_string(),
href: "#".to_string(),
icon: rsx! {
Icon {
icon: FaCubes,
width: 28,
height: 28,
}
Icon { icon: FaCubes, width: 28, height: 28 }
},
}
}