fix: remove superfluous /bags/:id/finish endpoint

This commit is contained in:
Jon Seager 2025-11-27 14:24:26 +00:00
parent 7eab263071
commit 57260a71c2
No known key found for this signature in database
4 changed files with 33 additions and 70 deletions

View file

@ -201,20 +201,37 @@ pub(crate) async fn get_bag(
Ok(Json(bag))
}
#[tracing::instrument(skip(state, _auth_user))]
#[tracing::instrument(skip(state, _auth_user, headers, query))]
pub(crate) async fn update_bag(
State(state): State<AppState>,
_auth_user: AuthenticatedUser,
headers: HeaderMap,
Path(id): Path<BagId>,
Json(payload): Json<UpdateBag>,
) -> Result<Json<Bag>, ApiError> {
Query(query): Query<ListQuery>,
Query(update_params): Query<UpdateBag>,
payload: Option<Json<UpdateBag>>,
) -> Result<Response, ApiError> {
let request = query.into_request::<BagSortKey>();
let body_update = payload.map(|Json(p)| p).unwrap_or(UpdateBag {
remaining: None,
closed: None,
finished_at: None,
});
let update = UpdateBag {
remaining: body_update.remaining.or(update_params.remaining),
closed: body_update.closed.or(update_params.closed),
finished_at: body_update.finished_at.or(update_params.finished_at),
};
let bag = state
.bag_repo
.update(id, payload.clone())
.update(id, update.clone())
.await
.map_err(AppError::from)?;
if let Some(true) = payload.closed {
if let Some(true) = update.closed {
// Fetch roast and roaster for timeline event
if let Ok(roast) = state.roast_repo.get(bag.roast_id).await
&& let Ok(roaster) = state.roaster_repo.get(roast.roaster_id).await
@ -234,7 +251,13 @@ pub(crate) async fn update_bag(
}
}
Ok(Json(bag))
if is_datastar_request(&headers) {
render_bag_list_fragment(state, request, true)
.await
.map_err(ApiError::from)
} else {
Ok(Json(bag).into_response())
}
}
#[tracing::instrument(skip(state, _auth_user, headers, query))]
@ -257,68 +280,6 @@ pub(crate) async fn delete_bag(
}
}
#[tracing::instrument(skip(state, _auth_user, headers, query))]
pub(crate) async fn finish_bag(
State(state): State<AppState>,
_auth_user: AuthenticatedUser,
headers: HeaderMap,
Path(id): Path<BagId>,
Query(query): Query<ListQuery>,
) -> Result<Response, StatusCode> {
let request = query.into_request::<BagSortKey>();
let bag = state
.bag_repo
.get(id)
.await
.map_err(|err| map_app_error(AppError::from(err)))?;
let update = UpdateBag {
remaining: Some(0.0),
closed: Some(true),
finished_at: Some(chrono::Utc::now().date_naive()),
};
let _ = state
.bag_repo
.update(id, update)
.await
.map_err(|err| map_app_error(AppError::from(err)))?;
// Add timeline event
if let Ok(roast) = state.roast_repo.get(bag.roast_id).await
&& let Ok(roaster) = state.roaster_repo.get(roast.roaster_id).await
{
let event = NewTimelineEvent {
entity_type: "bag".to_string(),
entity_id: bag.id.into_inner(),
occurred_at: chrono::Utc::now(),
title: roast.name.to_string(),
details: vec![
TimelineEventDetail {
label: "Roaster".to_string(),
value: roaster.name,
},
TimelineEventDetail {
label: "Amount".to_string(),
value: format!("{:.1}g", bag.amount),
},
],
tasting_notes: vec![],
};
let _ = state.timeline_repo.insert(event).await;
}
if is_datastar_request(&headers) {
render_bag_list_fragment(state, request, true)
.await
.map_err(map_app_error)
} else {
// Fallback for non-datastar requests (though the UI uses datastar)
Ok(Redirect::to(BAG_PAGE_PATH).into_response())
}
}
#[derive(Debug, Deserialize)]
pub struct BagsQuery {
pub roast_id: Option<RoastId>,

View file

@ -64,7 +64,6 @@ pub fn app_router(state: AppState) -> axum::Router {
get(roasts::roast_page),
)
.route("/bags", get(bags::bags_page))
.route("/bags/:id/finish", post(bags::finish_bag))
.route("/timeline", get(timeline::timeline_page))
.route("/styles.css", get(styles))
.route("/favicon.ico", get(favicon))

View file

@ -118,6 +118,9 @@ pub fn render_fragment<T: Template>(
.map_err(|err| AppError::unexpected(format!("failed to render fragment: {err}")))?;
let mut response = Html(html).into_response();
response
.headers_mut()
.insert(CONTENT_TYPE, HeaderValue::from_static("text/html"));
set_datastar_patch_headers(response.headers_mut(), selector);
Ok(response)
}

View file

@ -78,7 +78,7 @@
<div class="pt-4 border-t border-stone-100 flex justify-end">
<button
class="text-sm text-stone-600 hover:text-stone-900 font-medium"
data-on:click="@post('/bags/{{ bag.id }}/finish?{{ navigator.query() }}', {responseOverrides: {selector: '#bag-list', mode: 'replace'}})"
data-on:click="@put('/api/v1/bags/{{ bag.id }}?{{ navigator.query() }}&closed=true&remaining=0.0&finished_at=' + new Date().toISOString().split('T')[0], {responseOverrides: {selector: '#bag-list', mode: 'replace'}})"
>
Finish Bag
</button>