···11+//! Manages retrieving and updating project compose files.
22+33+use std::str::from_utf8;
44+55+use eyre::Context;
66+use salvo::Request;
77+use salvo::Writer;
88+use salvo::{
99+ Depot, Router,
1010+ oapi::{endpoint, extract::PathParam},
1111+};
1212+1313+use crate::{api::response::LuminaryResponse, core::LuminaryEngine, obtain};
1414+1515+/// Returns the router for compose related endpoints.
1616+pub fn router() -> Router {
1717+ return Router::with_path("compose").get(get_compose).put(put_compose);
1818+}
1919+2020+/// Retrieves the compose file for a given project.
2121+#[endpoint]
2222+async fn get_compose(project: PathParam<String>, depot: &mut Depot) -> LuminaryResponse<String> {
2323+ let engine = obtain!(depot, LuminaryEngine);
2424+ return Ok(engine.get_compose(&project.into_inner()).await?.into());
2525+}
2626+2727+/// Updates the compose file for a given project.
2828+#[endpoint]
2929+async fn put_compose(
3030+ project: PathParam<String>,
3131+ req: &mut Request,
3232+ depot: &mut Depot,
3333+) -> LuminaryResponse<()> {
3434+ let engine = obtain!(depot, LuminaryEngine);
3535+ let bytes = req.payload().await.wrap_err("Failed to read request body")?;
3636+ let compose = from_utf8(bytes).wrap_err("Failed to decode error")?;
3737+ engine.put_compose(&project.into_inner(), compose).await?;
3838+ return Ok(().into());
3939+}
+3-1
packages/node/src/api/mod.rs
···16161717mod action;
1818mod auth;
1919-pub mod realtime;
1919+mod compose;
2020+mod realtime;
2021mod response;
21222223/// Sets up the app router and all dependencies.
···7273 .push(Router::with_path("realtime").get(app_subscribe))
7374 .push(
7475 Router::with_path("/project/{project}")
7676+ .push(compose::router())
7577 .push(Router::with_path("logs").get(logs_subscribe))
7678 .push(Router::with_path("restart").post(action::restart_project))
7779 .push(Router::with_path("start").post(action::start_project))
+41
packages/node/src/core/compose.rs
···11+use std::path::{Path, PathBuf};
22+33+use eyre::{Context, Ok, Result};
44+use luminary_macros::wrap_err;
55+use tokio::fs::read_to_string;
66+77+use crate::core::{COMPOSE_FILENAME, LuminaryEngine};
88+99+impl LuminaryEngine {
1010+ fn compose_path(&self, project: &str) -> Result<PathBuf> {
1111+ let path = Path::new(&self.configuration.project_directory)
1212+ .join(project)
1313+ .join(COMPOSE_FILENAME);
1414+1515+ if !path.exists() {
1616+ eyre::bail!("Project '{}' does not exist", project);
1717+ }
1818+1919+ return Ok(path);
2020+ }
2121+2222+ /// Retrieves the docker compose file for a given project.
2323+ #[wrap_err("Failed to retrieve compose file")]
2424+ pub async fn get_compose(&self, project: &str) -> Result<String> {
2525+ let path = self.compose_path(project)?;
2626+2727+ return Ok(read_to_string(path).await.wrap_err("Failed to read file")?);
2828+ }
2929+3030+ /// Updates the docker compose file for a given project.
3131+ #[wrap_err("Failed to update compose file")]
3232+ pub async fn put_compose(&self, project: &str, compose: &str) -> Result<()> {
3333+ let path = self.compose_path(project)?;
3434+3535+ tokio::fs::write(path, compose)
3636+ .await
3737+ .wrap_err("Failed to write file")?;
3838+3939+ return Ok(());
4040+ }
4141+}
+3
packages/node/src/core/mod.rs
···11//! The core library for Luminary, containing all logic related to managing projects and interacting with the Docker engine.
2233+pub const COMPOSE_FILENAME: &str = "compose.yml";
44+35mod action;
66+mod compose;
47mod configuration;
58mod engine;
69mod logs;