jacquardSnapshot

← snapshot

2738 bytes
//! The commit endpoint.

use axum::Json;
use axum::extract::{Path, State};
use axum::http::StatusCode;
use jac_object::ObjectStore as _;
use jac_repo::RefName;
use jac_telemetry::Envelope;
use serde::Deserialize;
use serde_json::{Value, json};

use crate::dto::{self, ActorRef, SnapshotDto};
use crate::error::ApiError;
use crate::routes::with_repo_mut;
use crate::state::SharedState;

/// A commit request. `files` is the **entire** tree, not a delta —
/// milestone-1 honesty, straight from the engine's contract.
#[derive(Debug, Deserialize)]
pub(crate) struct CommitRequest {
    pub r#ref: String,
    pub actor: ActorRef,
    /// `human`, `agent`, or `mixed` — whose hands made it, hashed into the
    /// snapshot's identity.
    pub provenance: String,
    pub message: String,
    pub files: Vec<CommitFile>,
}

#[derive(Debug, Deserialize)]
pub(crate) struct CommitFile {
    pub path: String,
    pub content: String,
}

/// `POST /api/repos/{slug}/commits`.
pub(crate) async fn create(
    State(state): State<SharedState>,
    Path(slug): Path<String>,
    Json(req): Json<CommitRequest>,
) -> Result<(StatusCode, Json<Value>), ApiError> {
    let file_count = req.files.len();
    let provenance_label = req.provenance.clone();
    let ref_label = req.r#ref.clone();
    let actor_kind = req.actor.kind.clone();
    let body = with_repo_mut(&state, &slug, |entry| {
        let name = RefName::parse(&req.r#ref)?;
        let author = req.actor.resolve(entry)?;
        let provenance = dto::parse_provenance(&req.provenance)?;
        let owned: Vec<(String, Vec<u8>)> = req
            .files
            .iter()
            .map(|f| (f.path.clone(), f.content.clone().into_bytes()))
            .collect();
        let files: Vec<(&str, &[u8])> = owned
            .iter()
            .map(|(p, c)| (p.as_str(), c.as_slice()))
            .collect();
        let id = entry
            .repo
            .commit(&name, &files, author, provenance, &req.message)?;
        let snapshot = entry.repo.store().snapshot(id)?.clone();
        let at = snapshot.at.as_millis();
        Ok((
            json!({
                "snapshot": SnapshotDto::of(id, &snapshot, entry),
                "ref": name.as_str(),
            }),
            at,
        ))
    });
    let (body, at) = body?;
    state.telemetry.emit(
        Envelope::event("jac.commit.created", at)
            .attr("repo", slug)
            .attr("ref", ref_label)
            .attr("provenance", provenance_label)
            .attr("actor_kind", actor_kind)
            .measurement(
                "files",
                f64::from(u32::try_from(file_count).unwrap_or(u32::MAX)),
            ),
    );
    Ok((StatusCode::CREATED, Json(body)))
}