Login
4 branches 0 tags
Ben (Desktop/Arch) CI log auto scroll af69dbd 15 days ago 244 Commits
rubhub / src / controllers / project / ci / view.rs
use std::sync::Arc;

use askama::Template;
use axum::{
    body::Body,
    extract::{Path, State},
    http::{Response, StatusCode, header},
    response::{Html, IntoResponse},
};
use rubhub_auth_store::User;
use tower_cookies::Cookies;

use crate::{
    AccessType, GlobalState, Project, UserModel,
    models::{CiJob, ContentPage},
    services::{ci, session},
    views::ThemedRender,
};

#[derive(Template)]
#[template(path = "project/ci/view.html")]
struct CiJobTemplate<'a> {
    owner: Arc<User>,
    project: &'a Project,
    access_level: AccessType,
    job: CiJob,
    logged_in_user: Option<Arc<User>>,
    sidebar_projects: Vec<Project>,
    content_pages: Vec<ContentPage>,
    active_tab: &'static str,
    selected_branch: String,
}

pub async fn ci_job_get(
    State(state): State<GlobalState>,
    cookies: Cookies,
    Path((username, slug, job_id)): Path<(String, String, String)>,
) -> Response<Body> {
    let logged_in_user = session::current_user(&state, &cookies).await.ok();
    let content_pages = state.config.content_pages.clone();
    let sidebar_projects = if let Some(ref user) = logged_in_user {
        user.sidebar_projects(&state).await
    } else {
        vec![]
    };

    // Load user and project (handle ~ prefix)
    let user_slug = username.strip_prefix('~').unwrap_or(&username);
    let Some(owner) = state.auth.get_user(user_slug) else {
        return crate::controllers::not_found(logged_in_user, sidebar_projects, content_pages);
    };

    let Ok(project) = Project::load(&state, user_slug, &slug).await else {
        return crate::controllers::not_found(logged_in_user, sidebar_projects, content_pages);
    };

    let access_level = project
        .access_level(logged_in_user.as_ref().map(|u| u.slug.clone()))
        .await;

    if access_level == AccessType::None {
        return crate::controllers::not_found(logged_in_user, sidebar_projects, content_pages);
    }

    // Load job
    let job = match ci::load_job(&state.config.ci_root, &owner.slug, &project.slug, &job_id).await {
        Ok(job) => job,
        Err(_) => {
            return crate::controllers::not_found(logged_in_user, sidebar_projects, content_pages);
        }
    };

    let template = CiJobTemplate {
        owner,
        project: &project,
        access_level,
        job,
        logged_in_user,
        sidebar_projects,
        content_pages,
        active_tab: "ci",
        selected_branch: project.main_branch.clone(),
    };
    Html(template.render_with_theme()).into_response()
}

/// Return CI run status and logs as JSON (for polling)
pub async fn ci_job_status_get(
    State(state): State<GlobalState>,
    cookies: Cookies,
    Path((username, slug, job_id)): Path<(String, String, String)>,
) -> Response<Body> {
    let logged_in_user = session::current_user(&state, &cookies).await.ok();

    // Load user and project (handle ~ prefix)
    let user_slug = username.strip_prefix('~').unwrap_or(&username);
    let Some(_owner) = state.auth.get_user(user_slug) else {
        return Response::builder()
            .status(StatusCode::NOT_FOUND)
            .body(Body::empty())
            .unwrap();
    };

    let Ok(project) = Project::load(&state, user_slug, &slug).await else {
        return Response::builder()
            .status(StatusCode::NOT_FOUND)
            .body(Body::empty())
            .unwrap();
    };

    let access_level = project
        .access_level(logged_in_user.as_ref().map(|u| u.slug.clone()))
        .await;

    if access_level == AccessType::None {
        return Response::builder()
            .status(StatusCode::NOT_FOUND)
            .body(Body::empty())
            .unwrap();
    }

    match ci::load_run_with_all_logs(&state.config.ci_root, user_slug, &project.slug, &job_id).await
    {
        Ok(response) => {
            let json = serde_json::to_string(&response).unwrap_or_default();
            Response::builder()
                .header(header::CONTENT_TYPE, "application/json")
                .body(Body::from(json))
                .unwrap()
        }
        Err(_) => Response::builder()
            .status(StatusCode::NOT_FOUND)
            .body(Body::empty())
            .unwrap(),
    }
}

/// Return log for a specific job as plain text
pub async fn ci_log_get(
    State(state): State<GlobalState>,
    cookies: Cookies,
    Path((username, slug, run_id, job_name)): Path<(String, String, String, String)>,
) -> Response<Body> {
    let logged_in_user = session::current_user(&state, &cookies).await.ok();

    // Load user and project (handle ~ prefix)
    let user_slug = username.strip_prefix('~').unwrap_or(&username);
    let Some(_owner) = state.auth.get_user(user_slug) else {
        return Response::builder()
            .status(StatusCode::NOT_FOUND)
            .body(Body::from("Not found"))
            .unwrap();
    };

    let Ok(project) = Project::load(&state, user_slug, &slug).await else {
        return Response::builder()
            .status(StatusCode::NOT_FOUND)
            .body(Body::from("Not found"))
            .unwrap();
    };

    let access_level = project
        .access_level(logged_in_user.as_ref().map(|u| u.slug.clone()))
        .await;

    if access_level == AccessType::None {
        return Response::builder()
            .status(StatusCode::NOT_FOUND)
            .body(Body::from("Not found"))
            .unwrap();
    }

    let log = ci::load_job_log(
        &state.config.ci_root,
        user_slug,
        &project.slug,
        &run_id,
        &job_name,
    )
    .await;

    Response::builder()
        .header(header::CONTENT_TYPE, "text/plain; charset=utf-8")
        .body(Body::from(log))
        .unwrap()
}