max / makenotwork
1 file changed,
+43 insertions,
-40 deletions
| @@ -14,7 +14,10 @@ use chrono::{DateTime, Utc}; | |||
| 14 | 14 | use serde::{Deserialize, Serialize}; | |
| 15 | 15 | use tower_governor::GovernorLayer; | |
| 16 | 16 | ||
| 17 | + | use sqlx::PgPool; | |
| 18 | + | ||
| 17 | 19 | use crate::{ | |
| 20 | + | config::Config, | |
| 18 | 21 | constants, | |
| 19 | 22 | csrf::{post_csrf_skip, with_csrf_skip, CsrfRouter}, | |
| 20 | 23 | db::{self, BuildConfigId, BuildId, BuildStatus, GitRepoId, OtaReleaseId, SyncAppId}, | |
| @@ -79,11 +82,11 @@ fn tag_to_version(tag: &str) -> Result<String> { | |||
| 79 | 82 | ||
| 80 | 83 | /// Verify the authenticated user owns the given sync app. | |
| 81 | 84 | async fn verify_app_owner( | |
| 82 | - | state: &AppState, | |
| 85 | + | db: &PgPool, | |
| 83 | 86 | sync_user: &SyncUser, | |
| 84 | 87 | app_id: SyncAppId, | |
| 85 | 88 | ) -> Result<db::DbSyncApp> { | |
| 86 | - | let app = db::synckit::get_sync_app_by_id(&state.db, app_id) | |
| 89 | + | let app = db::synckit::get_sync_app_by_id(db, app_id) | |
| 87 | 90 | .await? | |
| 88 | 91 | .ok_or(AppError::NotFound)?; | |
| 89 | 92 | ||
| @@ -213,17 +216,17 @@ struct HookTriggerRequest { | |||
| 213 | 216 | /// `POST /api/sync/builds/apps/{app_id}/config` | |
| 214 | 217 | #[tracing::instrument(skip_all, name = "builds::create_config")] | |
| 215 | 218 | async fn create_config( | |
| 216 | - | State(state): State<AppState>, | |
| 219 | + | State(db): State<PgPool>, | |
| 217 | 220 | sync_user: SyncUser, | |
| 218 | 221 | Path(app_id): Path<SyncAppId>, | |
| 219 | 222 | Json(req): Json<CreateConfigRequest>, | |
| 220 | 223 | ) -> Result<impl IntoResponse> { | |
| 221 | - | verify_app_owner(&state, &sync_user, app_id).await?; | |
| 224 | + | verify_app_owner(&db, &sync_user, app_id).await?; | |
| 222 | 225 | validate_targets(&req.targets)?; | |
| 223 | 226 | validate_build_config_fields(&req.build_command, &req.artifact_path, &req.signing_key_path)?; | |
| 224 | 227 | ||
| 225 | 228 | // Verify repo ownership | |
| 226 | - | let repo = db::git_repos::get_repo_by_id(&state.db, req.repo_id) | |
| 229 | + | let repo = db::git_repos::get_repo_by_id(&db, req.repo_id) | |
| 227 | 230 | .await? | |
| 228 | 231 | .ok_or(AppError::NotFound)?; | |
| 229 | 232 | if repo.user_id != sync_user.user_id { | |
| @@ -231,7 +234,7 @@ async fn create_config( | |||
| 231 | 234 | } | |
| 232 | 235 | ||
| 233 | 236 | let config = db::builds::create_build_config( | |
| 234 | - | &state.db, | |
| 237 | + | &db, | |
| 235 | 238 | app_id, | |
| 236 | 239 | req.repo_id, | |
| 237 | 240 | &req.build_command, | |
| @@ -249,13 +252,13 @@ async fn create_config( | |||
| 249 | 252 | /// `GET /api/sync/builds/apps/{app_id}/config` | |
| 250 | 253 | #[tracing::instrument(skip_all, name = "builds::get_config")] | |
| 251 | 254 | async fn get_config( | |
| 252 | - | State(state): State<AppState>, | |
| 255 | + | State(db): State<PgPool>, | |
| 253 | 256 | sync_user: SyncUser, | |
| 254 | 257 | Path(app_id): Path<SyncAppId>, | |
| 255 | 258 | ) -> Result<impl IntoResponse> { | |
| 256 | - | verify_app_owner(&state, &sync_user, app_id).await?; | |
| 259 | + | verify_app_owner(&db, &sync_user, app_id).await?; | |
| 257 | 260 | ||
| 258 | - | let config = db::builds::get_build_config_by_app(&state.db, app_id) | |
| 261 | + | let config = db::builds::get_build_config_by_app(&db, app_id) | |
| 259 | 262 | .await? | |
| 260 | 263 | .ok_or(AppError::NotFound)?; | |
| 261 | 264 | ||
| @@ -267,21 +270,21 @@ async fn get_config( | |||
| 267 | 270 | /// `PUT /api/sync/builds/apps/{app_id}/config` | |
| 268 | 271 | #[tracing::instrument(skip_all, name = "builds::update_config")] | |
| 269 | 272 | async fn update_config( | |
| 270 | - | State(state): State<AppState>, | |
| 273 | + | State(db): State<PgPool>, | |
| 271 | 274 | sync_user: SyncUser, | |
| 272 | 275 | Path(app_id): Path<SyncAppId>, | |
| 273 | 276 | Json(req): Json<UpdateConfigRequest>, | |
| 274 | 277 | ) -> Result<impl IntoResponse> { | |
| 275 | - | verify_app_owner(&state, &sync_user, app_id).await?; | |
| 278 | + | verify_app_owner(&db, &sync_user, app_id).await?; | |
| 276 | 279 | validate_targets(&req.targets)?; | |
| 277 | 280 | validate_build_config_fields(&req.build_command, &req.artifact_path, &req.signing_key_path)?; | |
| 278 | 281 | ||
| 279 | - | let existing = db::builds::get_build_config_by_app(&state.db, app_id) | |
| 282 | + | let existing = db::builds::get_build_config_by_app(&db, app_id) | |
| 280 | 283 | .await? | |
| 281 | 284 | .ok_or(AppError::NotFound)?; | |
| 282 | 285 | ||
| 283 | 286 | let config = db::builds::update_build_config( | |
| 284 | - | &state.db, | |
| 287 | + | &db, | |
| 285 | 288 | existing.id, | |
| 286 | 289 | &req.build_command, | |
| 287 | 290 | &req.artifact_path, | |
| @@ -299,17 +302,17 @@ async fn update_config( | |||
| 299 | 302 | /// `DELETE /api/sync/builds/apps/{app_id}/config` | |
| 300 | 303 | #[tracing::instrument(skip_all, name = "builds::delete_config")] | |
| 301 | 304 | async fn delete_config( | |
| 302 | - | State(state): State<AppState>, | |
| 305 | + | State(db): State<PgPool>, | |
| 303 | 306 | sync_user: SyncUser, | |
| 304 | 307 | Path(app_id): Path<SyncAppId>, | |
| 305 | 308 | ) -> Result<impl IntoResponse> { | |
| 306 | - | verify_app_owner(&state, &sync_user, app_id).await?; | |
| 309 | + | verify_app_owner(&db, &sync_user, app_id).await?; | |
| 307 | 310 | ||
| 308 | - | let config = db::builds::get_build_config_by_app(&state.db, app_id) | |
| 311 | + | let config = db::builds::get_build_config_by_app(&db, app_id) | |
| 309 | 312 | .await? | |
| 310 | 313 | .ok_or(AppError::NotFound)?; | |
| 311 | 314 | ||
| 312 | - | db::builds::delete_build_config(&state.db, config.id).await?; | |
| 315 | + | db::builds::delete_build_config(&db, config.id).await?; | |
| 313 | 316 | ||
| 314 | 317 | Ok(StatusCode::NO_CONTENT) | |
| 315 | 318 | } | |
| @@ -319,16 +322,16 @@ async fn delete_config( | |||
| 319 | 322 | /// `POST /api/sync/builds/apps/{app_id}/trigger` | |
| 320 | 323 | #[tracing::instrument(skip_all, name = "builds::manual_trigger")] | |
| 321 | 324 | async fn manual_trigger( | |
| 322 | - | State(state): State<AppState>, | |
| 325 | + | State(db): State<PgPool>, | |
| 323 | 326 | sync_user: SyncUser, | |
| 324 | 327 | Path(app_id): Path<SyncAppId>, | |
| 325 | 328 | Json(req): Json<ManualTriggerRequest>, | |
| 326 | 329 | ) -> Result<impl IntoResponse> { | |
| 327 | - | verify_app_owner(&state, &sync_user, app_id).await?; | |
| 330 | + | verify_app_owner(&db, &sync_user, app_id).await?; | |
| 328 | 331 | ||
| 329 | 332 | let version = tag_to_version(&req.tag)?; | |
| 330 | 333 | ||
| 331 | - | let config = db::builds::get_build_config_by_app(&state.db, app_id) | |
| 334 | + | let config = db::builds::get_build_config_by_app(&db, app_id) | |
| 332 | 335 | .await? | |
| 333 | 336 | .ok_or_else(|| AppError::BadRequest("No build config found for this app".to_string()))?; | |
| 334 | 337 | ||
| @@ -336,14 +339,14 @@ async fn manual_trigger( | |||
| 336 | 339 | return Err(AppError::BadRequest("Build config is disabled".to_string())); | |
| 337 | 340 | } | |
| 338 | 341 | ||
| 339 | - | if db::builds::has_active_build(&state.db, config.id).await? { | |
| 342 | + | if db::builds::has_active_build(&db, config.id).await? { | |
| 340 | 343 | return Err(AppError::BadRequest( | |
| 341 | 344 | "A build is already pending or running for this app".to_string(), | |
| 342 | 345 | )); | |
| 343 | 346 | } | |
| 344 | 347 | ||
| 345 | 348 | let build = | |
| 346 | - | db::builds::create_build(&state.db, config.id, app_id, &version, &req.tag, "manual") | |
| 349 | + | db::builds::create_build(&db, config.id, app_id, &version, &req.tag, "manual") | |
| 347 | 350 | .await?; | |
| 348 | 351 | ||
| 349 | 352 | Ok((StatusCode::CREATED, Json(BuildResponse::from(build)))) | |
| @@ -354,14 +357,14 @@ async fn manual_trigger( | |||
| 354 | 357 | /// `GET /api/sync/builds/apps/{app_id}/builds` | |
| 355 | 358 | #[tracing::instrument(skip_all, name = "builds::list_builds")] | |
| 356 | 359 | async fn list_builds( | |
| 357 | - | State(state): State<AppState>, | |
| 360 | + | State(db): State<PgPool>, | |
| 358 | 361 | sync_user: SyncUser, | |
| 359 | 362 | Path(app_id): Path<SyncAppId>, | |
| 360 | 363 | ) -> Result<impl IntoResponse> { | |
| 361 | - | verify_app_owner(&state, &sync_user, app_id).await?; | |
| 364 | + | verify_app_owner(&db, &sync_user, app_id).await?; | |
| 362 | 365 | ||
| 363 | 366 | let builds = | |
| 364 | - | db::builds::list_builds_by_app(&state.db, app_id, constants::BUILD_HISTORY_LIMIT).await?; | |
| 367 | + | db::builds::list_builds_by_app(&db, app_id, constants::BUILD_HISTORY_LIMIT).await?; | |
| 365 | 368 | let response: Vec<BuildResponse> = builds.into_iter().map(BuildResponse::from).collect(); | |
| 366 | 369 | ||
| 367 | 370 | Ok(Json(response)) | |
| @@ -372,13 +375,13 @@ async fn list_builds( | |||
| 372 | 375 | /// `GET /api/sync/builds/apps/{app_id}/builds/{build_id}` | |
| 373 | 376 | #[tracing::instrument(skip_all, name = "builds::get_build")] | |
| 374 | 377 | async fn get_build( | |
| 375 | - | State(state): State<AppState>, | |
| 378 | + | State(db): State<PgPool>, | |
| 376 | 379 | sync_user: SyncUser, | |
| 377 | 380 | Path((app_id, build_id)): Path<(SyncAppId, BuildId)>, | |
| 378 | 381 | ) -> Result<impl IntoResponse> { | |
| 379 | - | verify_app_owner(&state, &sync_user, app_id).await?; | |
| 382 | + | verify_app_owner(&db, &sync_user, app_id).await?; | |
| 380 | 383 | ||
| 381 | - | let build = db::builds::get_build(&state.db, build_id) | |
| 384 | + | let build = db::builds::get_build(&db, build_id) | |
| 382 | 385 | .await? | |
| 383 | 386 | .ok_or(AppError::NotFound)?; | |
| 384 | 387 | ||
| @@ -394,13 +397,13 @@ async fn get_build( | |||
| 394 | 397 | /// `POST /api/sync/builds/apps/{app_id}/builds/{build_id}/cancel` | |
| 395 | 398 | #[tracing::instrument(skip_all, name = "builds::cancel_build")] | |
| 396 | 399 | async fn cancel_build( | |
| 397 | - | State(state): State<AppState>, | |
| 400 | + | State(db): State<PgPool>, | |
| 398 | 401 | sync_user: SyncUser, | |
| 399 | 402 | Path((app_id, build_id)): Path<(SyncAppId, BuildId)>, | |
| 400 | 403 | ) -> Result<impl IntoResponse> { | |
| 401 | - | verify_app_owner(&state, &sync_user, app_id).await?; | |
| 404 | + | verify_app_owner(&db, &sync_user, app_id).await?; | |
| 402 | 405 | ||
| 403 | - | let build = db::builds::get_build(&state.db, build_id) | |
| 406 | + | let build = db::builds::get_build(&db, build_id) | |
| 404 | 407 | .await? | |
| 405 | 408 | .ok_or(AppError::NotFound)?; | |
| 406 | 409 | ||
| @@ -415,7 +418,7 @@ async fn cancel_build( | |||
| 415 | 418 | } | |
| 416 | 419 | ||
| 417 | 420 | db::builds::update_build_status( | |
| 418 | - | &state.db, | |
| 421 | + | &db, | |
| 419 | 422 | build_id, | |
| 420 | 423 | BuildStatus::Cancelled, | |
| 421 | 424 | Some("Cancelled by user"), | |
| @@ -432,14 +435,14 @@ async fn cancel_build( | |||
| 432 | 435 | /// `POST /api/internal/builds/trigger` | |
| 433 | 436 | #[tracing::instrument(skip_all, name = "builds::hook_trigger")] | |
| 434 | 437 | async fn hook_trigger( | |
| 435 | - | State(state): State<AppState>, | |
| 438 | + | State(db): State<PgPool>, | |
| 439 | + | State(config): State<Config>, | |
| 436 | 440 | headers: axum::http::HeaderMap, | |
| 437 | 441 | Json(req): Json<HookTriggerRequest>, | |
| 438 | 442 | ) -> Result<impl IntoResponse> { | |
| 439 | 443 | // Authenticate via per-repo HMAC derived from BUILD_TRIGGER_TOKEN. | |
| 440 | 444 | // The hook file contains HMAC(token, owner:repo), not the raw token. | |
| 441 | - | let trigger_token = state | |
| 442 | - | .config | |
| 445 | + | let trigger_token = config | |
| 443 | 446 | .build.trigger_token | |
| 444 | 447 | .as_deref() | |
| 445 | 448 | .ok_or_else(|| AppError::ServiceUnavailable("Build triggers not configured".to_string()))?; | |
| @@ -462,7 +465,7 @@ async fn hook_trigger( | |||
| 462 | 465 | ||
| 463 | 466 | // Look up repo by owner + name | |
| 464 | 467 | let owner = db::users::get_user_by_username( | |
| 465 | - | &state.db, | |
| 468 | + | &db, | |
| 466 | 469 | &db::Username::new(&req.repo_owner) | |
| 467 | 470 | .map_err(|_| AppError::BadRequest("Invalid repo owner".to_string()))?, | |
| 468 | 471 | ) | |
| @@ -470,25 +473,25 @@ async fn hook_trigger( | |||
| 470 | 473 | .ok_or(AppError::NotFound)?; | |
| 471 | 474 | ||
| 472 | 475 | let repo = | |
| 473 | - | db::git_repos::get_repo_by_user_and_name(&state.db, owner.id, &req.repo_name) | |
| 476 | + | db::git_repos::get_repo_by_user_and_name(&db, owner.id, &req.repo_name) | |
| 474 | 477 | .await? | |
| 475 | 478 | .ok_or(AppError::NotFound)?; | |
| 476 | 479 | ||
| 477 | 480 | // Find build config for this repo | |
| 478 | - | let config = db::builds::get_build_config_by_repo(&state.db, repo.id) | |
| 481 | + | let config = db::builds::get_build_config_by_repo(&db, repo.id) | |
| 479 | 482 | .await? | |
| 480 | 483 | .ok_or_else(|| { | |
| 481 | 484 | AppError::BadRequest("No build config found for this repository".to_string()) | |
| 482 | 485 | })?; | |
| 483 | 486 | ||
| 484 | - | if db::builds::has_active_build(&state.db, config.id).await? { | |
| 487 | + | if db::builds::has_active_build(&db, config.id).await? { | |
| 485 | 488 | return Err(AppError::BadRequest( | |
| 486 | 489 | "A build is already pending or running".to_string(), | |
| 487 | 490 | )); | |
| 488 | 491 | } | |
| 489 | 492 | ||
| 490 | 493 | let build = db::builds::create_build( | |
| 491 | - | &state.db, | |
| 494 | + | &db, | |
| 492 | 495 | config.id, | |
| 493 | 496 | config.app_id, | |
| 494 | 497 | &version, |