text/x-rust
•
7.10 KB
•
236 lines
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 stdout log for a specific job as plain text
pub async fn ci_log_stdout_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 (stdout, _) = ci::load_job_result_logs(
&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(stdout))
.unwrap()
}
/// Return stderr log for a specific job as plain text
pub async fn ci_log_stderr_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 (_, stderr) = ci::load_job_result_logs(
&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(stderr))
.unwrap()
}