max / makenotwork
18 files changed,
+68 insertions,
-66 deletions
| @@ -120,7 +120,7 @@ pub(super) async fn get_blog_post( | |||
| 120 | 120 | AuthUser(user): AuthUser, | |
| 121 | 121 | Path(blog_post_id): Path<BlogPostId>, | |
| 122 | 122 | ) -> Result<impl IntoResponse> { | |
| 123 | - | let post = verify_blog_post_ownership(&state, blog_post_id, user.id).await?; | |
| 123 | + | let post = verify_blog_post_ownership(&state.db, blog_post_id, user.id).await?; | |
| 124 | 124 | Ok(Json(blog_post_edit_response(&post))) | |
| 125 | 125 | } | |
| 126 | 126 | ||
| @@ -133,7 +133,7 @@ pub(super) async fn create_blog_post( | |||
| 133 | 133 | ValidatedJson(req): ValidatedJson<CreateBlogPostRequest>, | |
| 134 | 134 | ) -> Result<impl IntoResponse> { | |
| 135 | 135 | user.check_not_suspended()?; | |
| 136 | - | verify_project_ownership(&state, project_id, user.id).await?; | |
| 136 | + | verify_project_ownership(&state.db, project_id, user.id).await?; | |
| 137 | 137 | ||
| 138 | 138 | // Validate title | |
| 139 | 139 | validation::validate_blog_post_title(&req.title)?; | |
| @@ -191,7 +191,7 @@ pub(super) async fn update_blog_post( | |||
| 191 | 191 | ValidatedJson(req): ValidatedJson<UpdateBlogPostRequest>, | |
| 192 | 192 | ) -> Result<impl IntoResponse> { | |
| 193 | 193 | user.check_not_suspended()?; | |
| 194 | - | let existing = verify_blog_post_ownership(&state, id, user.id).await?; | |
| 194 | + | let existing = verify_blog_post_ownership(&state.db, id, user.id).await?; | |
| 195 | 195 | ||
| 196 | 196 | validation::validate_blog_post_title(&req.title)?; | |
| 197 | 197 | // slug is validated by Slug's Deserialize impl | |
| @@ -262,7 +262,7 @@ pub(super) async fn delete_blog_post( | |||
| 262 | 262 | Path(id): Path<BlogPostId>, | |
| 263 | 263 | ) -> Result<impl IntoResponse> { | |
| 264 | 264 | user.check_not_suspended()?; | |
| 265 | - | let post = verify_blog_post_ownership(&state, id, user.id).await?; | |
| 265 | + | let post = verify_blog_post_ownership(&state.db, id, user.id).await?; | |
| 266 | 266 | ||
| 267 | 267 | db::blog_posts::delete_blog_post(&state.db, id, user.id).await?; | |
| 268 | 268 | db::projects::bump_cache_generation(&state.db, post.project_id).await?; |
| @@ -353,7 +353,7 @@ pub(super) async fn list_placements( | |||
| 353 | 353 | AuthUser(user): AuthUser, | |
| 354 | 354 | Path(item_id): Path<ItemId>, | |
| 355 | 355 | ) -> Result<impl IntoResponse> { | |
| 356 | - | let (item, _project) = verify_item_ownership(&state, item_id, user.id).await?; | |
| 356 | + | let (item, _project) = verify_item_ownership(&state.db, item_id, user.id).await?; | |
| 357 | 357 | ||
| 358 | 358 | let placements = db::content_insertions::list_placements_for_item(&state.db, item_id).await?; | |
| 359 | 359 | // Only offer clips that can legally be placed on this item (a video clip on an | |
| @@ -406,7 +406,7 @@ pub(super) async fn create_placement( | |||
| 406 | 406 | Json(req): Json<CreatePlacementRequest>, | |
| 407 | 407 | ) -> Result<impl IntoResponse> { | |
| 408 | 408 | user.check_not_suspended()?; | |
| 409 | - | let (item, _project) = verify_item_ownership(&state, item_id, user.id).await?; | |
| 409 | + | let (item, _project) = verify_item_ownership(&state.db, item_id, user.id).await?; | |
| 410 | 410 | ||
| 411 | 411 | // Verify the insertion belongs to this user | |
| 412 | 412 | let insertion = db::content_insertions::get_insertion(&state.db, req.insertion_id, user.id) | |
| @@ -463,7 +463,7 @@ pub(super) async fn delete_placement( | |||
| 463 | 463 | .await? | |
| 464 | 464 | .ok_or(AppError::NotFound)?; | |
| 465 | 465 | ||
| 466 | - | verify_item_ownership(&state, placement.item_id, user.id).await?; | |
| 466 | + | verify_item_ownership(&state.db, placement.item_id, user.id).await?; | |
| 467 | 467 | ||
| 468 | 468 | db::content_insertions::delete_placement(&state.db, placement_id).await?; | |
| 469 | 469 |
| @@ -43,7 +43,7 @@ pub(super) async fn start_import( | |||
| 43 | 43 | user.check_not_sandbox()?; | |
| 44 | 44 | ||
| 45 | 45 | // Validate project ownership | |
| 46 | - | super::verify_project_ownership(&state, req.project_id, user.id).await?; | |
| 46 | + | super::verify_project_ownership(&state.db, req.project_id, user.id).await?; | |
| 47 | 47 | ||
| 48 | 48 | // Only generic_csv is currently supported | |
| 49 | 49 | if req.source != ImportSource::GenericCsv { |
| @@ -62,7 +62,7 @@ async fn verify_bulk_ownership( | |||
| 62 | 62 | } | |
| 63 | 63 | ||
| 64 | 64 | // Verify the user owns that project | |
| 65 | - | verify_project_ownership(state, project_id, user_id).await?; | |
| 65 | + | verify_project_ownership(&state.db, project_id, user_id).await?; | |
| 66 | 66 | ||
| 67 | 67 | Ok(project_id) | |
| 68 | 68 | } |
| @@ -36,7 +36,7 @@ pub async fn bundle_add( | |||
| 36 | 36 | Json(req): Json<BundleAddRequest>, | |
| 37 | 37 | ) -> Result<impl IntoResponse> { | |
| 38 | 38 | user.check_not_suspended()?; | |
| 39 | - | let (item, _project) = verify_item_ownership(&state, bundle_id, user.id).await?; | |
| 39 | + | let (item, _project) = verify_item_ownership(&state.db, bundle_id, user.id).await?; | |
| 40 | 40 | if item.item_type != ItemType::Bundle { | |
| 41 | 41 | return Err(AppError::BadRequest("Item is not a bundle".to_string())); | |
| 42 | 42 | } | |
| @@ -62,7 +62,7 @@ pub async fn bundle_remove( | |||
| 62 | 62 | Path((bundle_id, child_id)): Path<(ItemId, ItemId)>, | |
| 63 | 63 | ) -> Result<impl IntoResponse> { | |
| 64 | 64 | user.check_not_suspended()?; | |
| 65 | - | let (item, _project) = verify_item_ownership(&state, bundle_id, user.id).await?; | |
| 65 | + | let (item, _project) = verify_item_ownership(&state.db, bundle_id, user.id).await?; | |
| 66 | 66 | if item.item_type != ItemType::Bundle { | |
| 67 | 67 | return Err(AppError::BadRequest("Item is not a bundle".to_string())); | |
| 68 | 68 | } | |
| @@ -79,7 +79,7 @@ pub async fn bundle_toggle_listed( | |||
| 79 | 79 | Json(req): Json<BundleListedRequest>, | |
| 80 | 80 | ) -> Result<impl IntoResponse> { | |
| 81 | 81 | user.check_not_suspended()?; | |
| 82 | - | let (item, _project) = verify_item_ownership(&state, bundle_id, user.id).await?; | |
| 82 | + | let (item, _project) = verify_item_ownership(&state.db, bundle_id, user.id).await?; | |
| 83 | 83 | if item.item_type != ItemType::Bundle { | |
| 84 | 84 | return Err(AppError::BadRequest("Item is not a bundle".to_string())); | |
| 85 | 85 | } | |
| @@ -112,7 +112,7 @@ pub async fn bundle_create_child( | |||
| 112 | 112 | Json(req): Json<BundleCreateChildRequest>, | |
| 113 | 113 | ) -> Result<impl IntoResponse> { | |
| 114 | 114 | user.check_not_suspended()?; | |
| 115 | - | let (bundle, _project) = verify_item_ownership(&state, bundle_id, user.id).await?; | |
| 115 | + | let (bundle, _project) = verify_item_ownership(&state.db, bundle_id, user.id).await?; | |
| 116 | 116 | if bundle.item_type != ItemType::Bundle { | |
| 117 | 117 | return Err(AppError::BadRequest("Item is not a bundle".to_string())); | |
| 118 | 118 | } |
| @@ -57,7 +57,7 @@ pub(in crate::routes::api) async fn create_chapter( | |||
| 57 | 57 | ) -> Result<impl IntoResponse> { | |
| 58 | 58 | user.check_not_suspended()?; | |
| 59 | 59 | validation::validate_chapter_title(&req.title)?; | |
| 60 | - | let (item, _) = verify_item_ownership(&state, item_id, user.id).await?; | |
| 60 | + | let (item, _) = verify_item_ownership(&state.db, item_id, user.id).await?; | |
| 61 | 61 | ||
| 62 | 62 | let chapter = db::chapters::create_chapter( | |
| 63 | 63 | &state.db, | |
| @@ -117,7 +117,7 @@ pub(in crate::routes::api) async fn update_chapter( | |||
| 117 | 117 | .await? | |
| 118 | 118 | .ok_or(AppError::NotFound)?; | |
| 119 | 119 | ||
| 120 | - | let (item, _) = verify_item_ownership(&state, chapter.item_id, user.id).await?; | |
| 120 | + | let (item, _) = verify_item_ownership(&state.db, chapter.item_id, user.id).await?; | |
| 121 | 121 | ||
| 122 | 122 | let updated = db::chapters::update_chapter( | |
| 123 | 123 | &state.db, | |
| @@ -153,7 +153,7 @@ pub(in crate::routes::api) async fn delete_chapter( | |||
| 153 | 153 | .await? | |
| 154 | 154 | .ok_or(AppError::NotFound)?; | |
| 155 | 155 | ||
| 156 | - | let (item, _) = verify_item_ownership(&state, chapter.item_id, user.id).await?; | |
| 156 | + | let (item, _) = verify_item_ownership(&state.db, chapter.item_id, user.id).await?; | |
| 157 | 157 | ||
| 158 | 158 | db::chapters::delete_chapter(&state.db, chapter_id).await?; | |
| 159 | 159 | db::projects::bump_cache_generation(&state.db, item.project_id).await?; |
| @@ -71,7 +71,7 @@ pub(in crate::routes::api) async fn create_item( | |||
| 71 | 71 | validation::validate_item_description(desc)?; | |
| 72 | 72 | } | |
| 73 | 73 | ||
| 74 | - | verify_project_ownership(&state, project_id, user.id).await?; | |
| 74 | + | verify_project_ownership(&state.db, project_id, user.id).await?; | |
| 75 | 75 | ||
| 76 | 76 | // Validate item type against project features | |
| 77 | 77 | let project = db::projects::get_project_by_id(&state.db, project_id) | |
| @@ -172,7 +172,7 @@ pub(in crate::routes::api) async fn update_item( | |||
| 172 | 172 | ) -> Result<Response> { | |
| 173 | 173 | tracing::Span::current().record("item_id", tracing::field::display(&id)); | |
| 174 | 174 | user.check_not_suspended()?; | |
| 175 | - | verify_item_ownership(&state, id, user.id).await?; | |
| 175 | + | verify_item_ownership(&state.db, id, user.id).await?; | |
| 176 | 176 | ||
| 177 | 177 | // Validate input (same rules as create_item, but all fields are optional) | |
| 178 | 178 | if let Some(ref title) = req.title { | |
| @@ -284,7 +284,7 @@ pub(in crate::routes::api) async fn delete_item( | |||
| 284 | 284 | ) -> Result<Response> { | |
| 285 | 285 | tracing::Span::current().record("item_id", tracing::field::display(&id)); | |
| 286 | 286 | user.check_not_suspended()?; | |
| 287 | - | let (item, _project) = verify_item_ownership(&state, id, user.id).await?; | |
| 287 | + | let (item, _project) = verify_item_ownership(&state.db, id, user.id).await?; | |
| 288 | 288 | ||
| 289 | 289 | db::items::delete_item(&state.db, id, user.id).await?; | |
| 290 | 290 | db::projects::bump_cache_generation(&state.db, item.project_id).await?; | |
| @@ -302,7 +302,7 @@ pub(in crate::routes::api) async fn restore_item( | |||
| 302 | 302 | Path(id): Path<ItemId>, | |
| 303 | 303 | ) -> Result<impl IntoResponse> { | |
| 304 | 304 | user.check_not_suspended()?; | |
| 305 | - | verify_item_ownership(&state, id, user.id).await?; | |
| 305 | + | verify_item_ownership(&state.db, id, user.id).await?; | |
| 306 | 306 | ||
| 307 | 307 | let restored = db::items::restore_item(&state.db, id, user.id).await?; | |
| 308 | 308 | if !restored { | |
| @@ -322,7 +322,7 @@ pub(in crate::routes::api) async fn duplicate_item( | |||
| 322 | 322 | ) -> Result<Response> { | |
| 323 | 323 | tracing::Span::current().record("item_id", tracing::field::display(&id)); | |
| 324 | 324 | user.check_not_suspended()?; | |
| 325 | - | verify_item_ownership(&state, id, user.id).await?; | |
| 325 | + | verify_item_ownership(&state.db, id, user.id).await?; | |
| 326 | 326 | ||
| 327 | 327 | let new_item = db::items::duplicate_item(&state.db, id, user.id).await?; | |
| 328 | 328 | ||
| @@ -370,7 +370,7 @@ pub(in crate::routes::api) async fn move_item( | |||
| 370 | 370 | ) -> Result<impl IntoResponse> { | |
| 371 | 371 | tracing::Span::current().record("item_id", tracing::field::display(&id)); | |
| 372 | 372 | user.check_not_suspended()?; | |
| 373 | - | let (item, _project) = verify_item_ownership(&state, id, user.id).await?; | |
| 373 | + | let (item, _project) = verify_item_ownership(&state.db, id, user.id).await?; | |
| 374 | 374 | ||
| 375 | 375 | db::items::move_item(&state.db, item.project_id, user.id, id, &req.direction).await?; | |
| 376 | 376 | db::projects::bump_cache_generation(&state.db, item.project_id).await?; | |
| @@ -409,7 +409,7 @@ pub(in crate::routes::api) async fn update_item_text( | |||
| 409 | 409 | tracing::Span::current().record("item_id", tracing::field::display(&id)); | |
| 410 | 410 | user.check_not_suspended()?; | |
| 411 | 411 | validation::validate_item_text_body(&req.body)?; | |
| 412 | - | verify_item_ownership(&state, id, user.id).await?; | |
| 412 | + | verify_item_ownership(&state.db, id, user.id).await?; | |
| 413 | 413 | ||
| 414 | 414 | let item = db::items::update_item_text(&state.db, id, user.id, &req.body).await?; | |
| 415 | 415 | db::projects::bump_cache_generation(&state.db, item.project_id).await?; |
| @@ -34,7 +34,7 @@ pub(in crate::routes::api) async fn refund_transaction( | |||
| 34 | 34 | Json(req): Json<RefundRequest>, | |
| 35 | 35 | ) -> Result<impl IntoResponse> { | |
| 36 | 36 | user.check_not_suspended()?; | |
| 37 | - | verify_item_ownership(&state, id, user.id).await?; | |
| 37 | + | verify_item_ownership(&state.db, id, user.id).await?; | |
| 38 | 38 | ||
| 39 | 39 | // Fetch the transaction and validate it belongs to this item | |
| 40 | 40 | let tx = db::transactions::get_transaction_by_id(&state.db, req.transaction_id) |
| @@ -82,7 +82,7 @@ pub(in crate::routes::api) async fn create_section( | |||
| 82 | 82 | validation::validate_section_title(&title)?; | |
| 83 | 83 | validation::validate_section_body(&req.body)?; | |
| 84 | 84 | ||
| 85 | - | let (item, _) = verify_item_ownership(&state, item_id, user.id).await?; | |
| 85 | + | let (item, _) = verify_item_ownership(&state.db, item_id, user.id).await?; | |
| 86 | 86 | ||
| 87 | 87 | // Enforce max sections limit | |
| 88 | 88 | let count = db::item_sections::count_by_item(&state.db, item_id).await?; | |
| @@ -147,7 +147,7 @@ pub(in crate::routes::api) async fn update_section( | |||
| 147 | 147 | .await? | |
| 148 | 148 | .ok_or(AppError::NotFound)?; | |
| 149 | 149 | ||
| 150 | - | let (item, _) = verify_item_ownership(&state, section.item_id, user.id).await?; | |
| 150 | + | let (item, _) = verify_item_ownership(&state.db, section.item_id, user.id).await?; | |
| 151 | 151 | ||
| 152 | 152 | // Same dedup as create. Re-saving the section under its own slug is not a | |
| 153 | 153 | // conflict (same row), so the bare base succeeds; only a collision with a | |
| @@ -179,7 +179,7 @@ pub(in crate::routes::api) async fn delete_section( | |||
| 179 | 179 | .await? | |
| 180 | 180 | .ok_or(AppError::NotFound)?; | |
| 181 | 181 | ||
| 182 | - | let (item, _) = verify_item_ownership(&state, section.item_id, user.id).await?; | |
| 182 | + | let (item, _) = verify_item_ownership(&state.db, section.item_id, user.id).await?; | |
| 183 | 183 | ||
| 184 | 184 | db::item_sections::delete(&state.db, section_id).await?; | |
| 185 | 185 | db::projects::bump_cache_generation(&state.db, item.project_id).await?; | |
| @@ -200,7 +200,7 @@ pub(in crate::routes::api) async fn reorder_sections( | |||
| 200 | 200 | Json(req): Json<ReorderSectionsRequest>, | |
| 201 | 201 | ) -> Result<impl IntoResponse> { | |
| 202 | 202 | user.check_not_suspended()?; | |
| 203 | - | let (item, _) = verify_item_ownership(&state, item_id, user.id).await?; | |
| 203 | + | let (item, _) = verify_item_ownership(&state.db, item_id, user.id).await?; | |
| 204 | 204 | ||
| 205 | 205 | db::item_sections::reorder(&state.db, item_id, &req.section_ids).await?; | |
| 206 | 206 | db::projects::bump_cache_generation(&state.db, item.project_id).await?; |
| @@ -39,7 +39,7 @@ pub(in crate::routes::api) async fn add_tag( | |||
| 39 | 39 | Form(form): Form<AddTagForm>, | |
| 40 | 40 | ) -> Result<impl IntoResponse> { | |
| 41 | 41 | user.check_not_suspended()?; | |
| 42 | - | let (item, _) = verify_item_ownership(&state, id, user.id).await?; | |
| 42 | + | let (item, _) = verify_item_ownership(&state.db, id, user.id).await?; | |
| 43 | 43 | ||
| 44 | 44 | let tag = db::tags::get_tag_by_id(&state.db, form.tag_id) | |
| 45 | 45 | .await? | |
| @@ -79,7 +79,7 @@ pub(in crate::routes::api) async fn remove_tag( | |||
| 79 | 79 | Path((id, tag_id)): Path<(ItemId, TagId)>, | |
| 80 | 80 | ) -> Result<impl IntoResponse> { | |
| 81 | 81 | user.check_not_suspended()?; | |
| 82 | - | let (item, _) = verify_item_ownership(&state, id, user.id).await?; | |
| 82 | + | let (item, _) = verify_item_ownership(&state.db, id, user.id).await?; | |
| 83 | 83 | ||
| 84 | 84 | db::tags::remove_tag_from_item(&state.db, id, tag_id).await?; | |
| 85 | 85 | db::projects::bump_cache_generation(&state.db, item.project_id).await?; | |
| @@ -101,7 +101,7 @@ pub(in crate::routes::api) async fn set_primary_tag( | |||
| 101 | 101 | Form(form): Form<SetPrimaryTagForm>, | |
| 102 | 102 | ) -> Result<impl IntoResponse> { | |
| 103 | 103 | user.check_not_suspended()?; | |
| 104 | - | let (item, _) = verify_item_ownership(&state, id, user.id).await?; | |
| 104 | + | let (item, _) = verify_item_ownership(&state.db, id, user.id).await?; | |
| 105 | 105 | ||
| 106 | 106 | db::tags::set_primary_tag(&state.db, id, form.tag_id).await?; | |
| 107 | 107 | db::projects::bump_cache_generation(&state.db, item.project_id).await?; |
| @@ -56,7 +56,7 @@ pub(in crate::routes::api) async fn create_version( | |||
| 56 | 56 | validation::validate_changelog(changelog)?; | |
| 57 | 57 | } | |
| 58 | 58 | ||
| 59 | - | let (item, _) = verify_item_ownership(&state, item_id, user.id).await?; | |
| 59 | + | let (item, _) = verify_item_ownership(&state.db, item_id, user.id).await?; | |
| 60 | 60 | ||
| 61 | 61 | let version = db::versions::create_version( | |
| 62 | 62 | &state.db, | |
| @@ -91,7 +91,7 @@ pub(in crate::routes::api) async fn delete_version( | |||
| 91 | 91 | Path((item_id, version_id)): Path<(ItemId, VersionId)>, | |
| 92 | 92 | ) -> Result<impl IntoResponse> { | |
| 93 | 93 | user.check_not_suspended()?; | |
| 94 | - | verify_item_ownership(&state, item_id, user.id).await?; | |
| 94 | + | verify_item_ownership(&state.db, item_id, user.id).await?; | |
| 95 | 95 | ||
| 96 | 96 | let version = db::versions::get_version_by_id(&state.db, version_id) | |
| 97 | 97 | .await? |
| @@ -355,7 +355,7 @@ pub(super) async fn update_license_settings( | |||
| 355 | 355 | ) -> Result<Response> { | |
| 356 | 356 | user.check_not_suspended()?; | |
| 357 | 357 | ||
| 358 | - | verify_item_ownership(&state, id, user.id).await?; | |
| 358 | + | verify_item_ownership(&state.db, id, user.id).await?; | |
| 359 | 359 | ||
| 360 | 360 | let enable = req.enable_license_keys.is_some(); | |
| 361 | 361 | db::items::update_item_license_settings(&state.db, id, user.id, enable, req.default_max_activations).await?; | |
| @@ -412,7 +412,7 @@ pub(super) async fn generate_key( | |||
| 412 | 412 | ) -> Result<Response> { | |
| 413 | 413 | user.check_not_suspended()?; | |
| 414 | 414 | ||
| 415 | - | let (item, _project) = verify_item_ownership(&state, item_id, user.id).await?; | |
| 415 | + | let (item, _project) = verify_item_ownership(&state.db, item_id, user.id).await?; | |
| 416 | 416 | ||
| 417 | 417 | if !item.enable_license_keys { | |
| 418 | 418 | return Err(AppError::BadRequest( | |
| @@ -468,7 +468,7 @@ pub(super) async fn list_keys( | |||
| 468 | 468 | AuthUser(user): AuthUser, | |
| 469 | 469 | Path(item_id): Path<ItemId>, | |
| 470 | 470 | ) -> Result<Response> { | |
| 471 | - | verify_item_ownership(&state, item_id, user.id).await?; | |
| 471 | + | verify_item_ownership(&state.db, item_id, user.id).await?; | |
| 472 | 472 | ||
| 473 | 473 | let keys = db::license_keys::get_license_keys_by_item(&state.db, item_id).await?; | |
| 474 | 474 | ||
| @@ -503,7 +503,7 @@ pub(super) async fn revoke_key( | |||
| 503 | 503 | .await? | |
| 504 | 504 | .ok_or(AppError::NotFound)?; | |
| 505 | 505 | ||
| 506 | - | verify_item_ownership(&state, key.item_id, user.id).await?; | |
| 506 | + | verify_item_ownership(&state.db, key.item_id, user.id).await?; | |
| 507 | 507 | ||
| 508 | 508 | db::license_keys::revoke_license_key(&state.db, key_id).await?; | |
| 509 | 509 |
| @@ -55,6 +55,8 @@ use serde::{Deserialize, Serialize}; | |||
| 55 | 55 | use serde_json::json; | |
| 56 | 56 | use tower_governor::GovernorLayer; | |
| 57 | 57 | ||
| 58 | + | use sqlx::PgPool; | |
| 59 | + | ||
| 58 | 60 | use crate::{ | |
| 59 | 61 | constants, | |
| 60 | 62 | csrf::{delete_csrf, post_csrf, post_csrf_skip, put_csrf, CsrfRouter}, | |
| @@ -69,11 +71,11 @@ const GUEST_CHECKOUT_SKIP: &str = "guest checkout: pre-auth, no session"; | |||
| 69 | 71 | /// Fetch a project and verify the user owns it. Shared by all ownership checks | |
| 70 | 72 | /// that go through a project (items, blog posts, direct project access). | |
| 71 | 73 | pub(super) async fn verify_project_ownership( | |
| 72 | - | state: &AppState, | |
| 74 | + | db: &sqlx::PgPool, | |
| 73 | 75 | project_id: ProjectId, | |
| 74 | 76 | user_id: UserId, | |
| 75 | 77 | ) -> Result<db::DbProject> { | |
| 76 | - | let project = db::projects::get_project_by_id(&state.db, project_id) | |
| 78 | + | let project = db::projects::get_project_by_id(db, project_id) | |
| 77 | 79 | .await? | |
| 78 | 80 | .ok_or(AppError::NotFound)?; | |
| 79 | 81 | if project.user_id != user_id { | |
| @@ -83,26 +85,26 @@ pub(super) async fn verify_project_ownership( | |||
| 83 | 85 | } | |
| 84 | 86 | ||
| 85 | 87 | pub(super) async fn verify_item_ownership( | |
| 86 | - | state: &AppState, | |
| 88 | + | db: &sqlx::PgPool, | |
| 87 | 89 | item_id: ItemId, | |
| 88 | 90 | user_id: UserId, | |
| 89 | 91 | ) -> Result<(db::DbItem, db::DbProject)> { | |
| 90 | - | let item = db::items::get_item_by_id(&state.db, item_id) | |
| 92 | + | let item = db::items::get_item_by_id(db, item_id) | |
| 91 | 93 | .await? | |
| 92 | 94 | .ok_or(AppError::NotFound)?; | |
| 93 | - | let project = verify_project_ownership(state, item.project_id, user_id).await?; | |
| 95 | + | let project = verify_project_ownership(db, item.project_id, user_id).await?; | |
| 94 | 96 | Ok((item, project)) | |
| 95 | 97 | } | |
| 96 | 98 | ||
| 97 | 99 | pub(super) async fn verify_blog_post_ownership( | |
| 98 | - | state: &AppState, | |
| 100 | + | db: &sqlx::PgPool, | |
| 99 | 101 | blog_post_id: BlogPostId, | |
| 100 | 102 | user_id: UserId, | |
| 101 | 103 | ) -> Result<db::DbBlogPost> { | |
| 102 | - | let post = db::blog_posts::get_blog_post_by_id(&state.db, blog_post_id) | |
| 104 | + | let post = db::blog_posts::get_blog_post_by_id(db, blog_post_id) | |
| 103 | 105 | .await? | |
| 104 | 106 | .ok_or(AppError::NotFound)?; | |
| 105 | - | verify_project_ownership(state, post.project_id, user_id).await?; | |
| 107 | + | verify_project_ownership(db, post.project_id, user_id).await?; | |
| 106 | 108 | Ok(post) | |
| 107 | 109 | } | |
| 108 | 110 | ||
| @@ -136,9 +138,9 @@ struct PublicProject { | |||
| 136 | 138 | ||
| 137 | 139 | #[tracing::instrument(skip_all, name = "api::public_projects")] | |
| 138 | 140 | async fn public_projects( | |
| 139 | - | State(state): State<AppState>, | |
| 141 | + | State(db): State<PgPool>, | |
| 140 | 142 | ) -> Result<impl IntoResponse> { | |
| 141 | - | let rows = db::discover::discover_projects(&state.db, None, None, None, false, 50, 0).await?; | |
| 143 | + | let rows = db::discover::discover_projects(&db, None, None, None, false, 50, 0).await?; | |
| 142 | 144 | let data: Vec<PublicProject> = rows | |
| 143 | 145 | .into_iter() | |
| 144 | 146 | .map(|r| PublicProject { | |
| @@ -162,11 +164,11 @@ struct EmailSignupForm { | |||
| 162 | 164 | ||
| 163 | 165 | #[tracing::instrument(skip_all, name = "api::email_signup")] | |
| 164 | 166 | async fn email_signup( | |
| 165 | - | State(state): State<AppState>, | |
| 167 | + | State(db): State<PgPool>, | |
| 166 | 168 | Json(form): Json<EmailSignupForm>, | |
| 167 | 169 | ) -> Result<impl IntoResponse> { | |
| 168 | 170 | let email = db::Email::new(&form.email)?; | |
| 169 | - | db::email_signups::insert_email_signup(&state.db, email.as_str(), "landing").await?; | |
| 171 | + | db::email_signups::insert_email_signup(&db, email.as_str(), "landing").await?; | |
| 170 | 172 | Ok(Json(json!({"success": true}))) | |
| 171 | 173 | } | |
| 172 | 174 |
| @@ -78,7 +78,7 @@ pub(super) async fn create_section( | |||
| 78 | 78 | validation::validate_section_title(&title)?; | |
| 79 | 79 | validation::validate_section_body(&req.body)?; | |
| 80 | 80 | ||
| 81 | - | verify_project_ownership(&state, project_id, user.id).await?; | |
| 81 | + | verify_project_ownership(&state.db, project_id, user.id).await?; | |
| 82 | 82 | ||
| 83 | 83 | let count = db::project_sections::count_by_project(&state.db, project_id).await?; | |
| 84 | 84 | if count >= MAX_SECTIONS_PER_PROJECT { | |
| @@ -139,7 +139,7 @@ pub(super) async fn update_section( | |||
| 139 | 139 | .await? | |
| 140 | 140 | .ok_or(AppError::NotFound)?; | |
| 141 | 141 | ||
| 142 | - | verify_project_ownership(&state, section.project_id, user.id).await?; | |
| 142 | + | verify_project_ownership(&state.db, section.project_id, user.id).await?; | |
| 143 | 143 | ||
| 144 | 144 | // Same dedup as create. Re-saving under the section's own slug is not a | |
| 145 | 145 | // conflict (same row); only a collision with a different section triggers | |
| @@ -170,7 +170,7 @@ pub(super) async fn delete_section( | |||
| 170 | 170 | .await? | |
| 171 | 171 | .ok_or(AppError::NotFound)?; | |
| 172 | 172 | ||
| 173 | - | verify_project_ownership(&state, section.project_id, user.id).await?; | |
| 173 | + | verify_project_ownership(&state.db, section.project_id, user.id).await?; | |
| 174 | 174 | ||
| 175 | 175 | db::project_sections::delete(&state.db, section_id).await?; | |
| 176 | 176 | db::projects::bump_cache_generation(&state.db, section.project_id).await?; | |
| @@ -190,7 +190,7 @@ pub(super) async fn reorder_sections( | |||
| 190 | 190 | Json(req): Json<ReorderSectionsRequest>, | |
| 191 | 191 | ) -> Result<impl IntoResponse> { | |
| 192 | 192 | user.check_not_suspended()?; | |
| 193 | - | verify_project_ownership(&state, project_id, user.id).await?; | |
| 193 | + | verify_project_ownership(&state.db, project_id, user.id).await?; | |
| 194 | 194 | ||
| 195 | 195 | db::project_sections::reorder(&state.db, project_id, &req.section_ids).await?; | |
| 196 | 196 | db::projects::bump_cache_generation(&state.db, project_id).await?; |
| @@ -219,7 +219,7 @@ pub(super) async fn update_project( | |||
| 219 | 219 | ) -> Result<impl IntoResponse> { | |
| 220 | 220 | tracing::Span::current().record("project_id", tracing::field::display(&id)); | |
| 221 | 221 | user.check_not_suspended()?; | |
| 222 | - | verify_project_ownership(&state, id, user.id).await?; | |
| 222 | + | verify_project_ownership(&state.db, id, user.id).await?; | |
| 223 | 223 | ||
| 224 | 224 | // Validate input (same rules as create_project, but all fields are optional) | |
| 225 | 225 | if let Some(ref title) = req.title { | |
| @@ -331,7 +331,7 @@ pub(super) async fn update_project_theme( | |||
| 331 | 331 | ) -> Result<impl IntoResponse> { | |
| 332 | 332 | tracing::Span::current().record("project_id", tracing::field::display(&id)); | |
| 333 | 333 | user.check_not_suspended()?; | |
| 334 | - | verify_project_ownership(&state, id, user.id).await?; | |
| 334 | + | verify_project_ownership(&state.db, id, user.id).await?; | |
| 335 | 335 | ||
| 336 | 336 | let theme_id = crate::theming::normalize_theme_id(req.theme_id.as_deref()) | |
| 337 | 337 | .map_err(|t| AppError::validation(format!("Unknown theme: {t}")))?; | |
| @@ -353,7 +353,7 @@ pub(super) async fn delete_project( | |||
| 353 | 353 | ) -> Result<impl IntoResponse> { | |
| 354 | 354 | tracing::Span::current().record("project_id", tracing::field::display(&id)); | |
| 355 | 355 | user.check_not_suspended()?; | |
| 356 | - | let project = verify_project_ownership(&state, id, user.id).await?; | |
| 356 | + | let project = verify_project_ownership(&state.db, id, user.id).await?; | |
| 357 | 357 | ||
| 358 | 358 | // Collect all S3 keys from items + versions + galleries before CASCADE | |
| 359 | 359 | // delete destroys them. Gallery rows (item_images / project_images) cascade | |
| @@ -431,7 +431,7 @@ pub(super) async fn link_repo( | |||
| 431 | 431 | Json(req): Json<LinkRepoRequest>, | |
| 432 | 432 | ) -> Result<impl IntoResponse> { | |
| 433 | 433 | user.check_not_suspended()?; | |
| 434 | - | verify_project_ownership(&state, id, user.id).await?; | |
| 434 | + | verify_project_ownership(&state.db, id, user.id).await?; | |
| 435 | 435 | ||
| 436 | 436 | let repo = db::git_repos::get_repo_by_user_and_name(&state.db, user.id, &req.name) | |
| 437 | 437 | .await? | |
| @@ -451,7 +451,7 @@ pub(super) async fn unlink_repo( | |||
| 451 | 451 | Path((id, repo_name)): Path<(ProjectId, String)>, | |
| 452 | 452 | ) -> Result<impl IntoResponse> { | |
| 453 | 453 | user.check_not_suspended()?; | |
| 454 | - | verify_project_ownership(&state, id, user.id).await?; | |
| 454 | + | verify_project_ownership(&state.db, id, user.id).await?; | |
| 455 | 455 | ||
| 456 | 456 | let repo = db::git_repos::get_repo_by_user_and_name(&state.db, user.id, &repo_name) | |
| 457 | 457 | .await? | |
| @@ -616,7 +616,7 @@ pub async fn add_project_member( | |||
| 616 | 616 | Path(project_id): Path<ProjectId>, | |
| 617 | 617 | Form(form): Form<AddMemberForm>, | |
| 618 | 618 | ) -> Result<Response> { | |
| 619 | - | let _project = verify_project_ownership(&state, project_id, session_user.id).await?; | |
| 619 | + | let _project = verify_project_ownership(&state.db, project_id, session_user.id).await?; | |
| 620 | 620 | ||
| 621 | 621 | // Validate split percent | |
| 622 | 622 | if form.split_percent < 1 || form.split_percent > 99 { | |
| @@ -661,7 +661,7 @@ pub async fn remove_project_member( | |||
| 661 | 661 | AuthUser(session_user): AuthUser, | |
| 662 | 662 | Path((project_id, user_id)): Path<(ProjectId, db::UserId)>, | |
| 663 | 663 | ) -> Result<Response> { | |
| 664 | - | verify_project_ownership(&state, project_id, session_user.id).await?; | |
| 664 | + | verify_project_ownership(&state.db, project_id, session_user.id).await?; | |
| 665 | 665 | ||
| 666 | 666 | let removed = db::project_members::remove_project_member(&state.db, project_id, user_id).await?; | |
| 667 | 667 |
| @@ -159,7 +159,7 @@ pub(super) async fn create_promo_code( | |||
| 159 | 159 | } else { | |
| 160 | 160 | let item_id: ItemId = id_str.parse() | |
| 161 | 161 | .map_err(|_| AppError::BadRequest("Invalid item ID".to_string()))?; | |
| 162 | - | verify_item_ownership(&state, item_id, user.id).await?; | |
| 162 | + | verify_item_ownership(&state.db, item_id, user.id).await?; | |
| 163 | 163 | Some(item_id) | |
| 164 | 164 | } | |
| 165 | 165 | } else { |
| @@ -59,7 +59,7 @@ pub(super) async fn create_tier( | |||
| 59 | 59 | ) -> Result<impl IntoResponse> { | |
| 60 | 60 | user.check_not_suspended()?; | |
| 61 | 61 | // Verify ownership | |
| 62 | - | let project = verify_project_ownership(&state, project_id, user.id).await?; | |
| 62 | + | let project = verify_project_ownership(&state.db, project_id, user.id).await?; | |
| 63 | 63 | ||
| 64 | 64 | // Validate input | |
| 65 | 65 | validation::validate_tier_name(&req.name)?; | |
| @@ -131,7 +131,7 @@ pub(super) async fn list_tiers( | |||
| 131 | 131 | Path(project_id): Path<ProjectId>, | |
| 132 | 132 | ) -> Result<impl IntoResponse> { | |
| 133 | 133 | // Verify ownership (only creator can see all tiers including inactive) | |
| 134 | - | verify_project_ownership(&state, project_id, user.id).await?; | |
| 134 | + | verify_project_ownership(&state.db, project_id, user.id).await?; | |
| 135 | 135 | ||
| 136 | 136 | let tiers = db::subscriptions::get_all_tiers_by_project(&state.db, project_id).await?; | |
| 137 | 137 | ||
| @@ -161,7 +161,7 @@ pub(super) async fn update_tier( | |||
| 161 | 161 | .ok_or(AppError::NotFound)?; | |
| 162 | 162 | ||
| 163 | 163 | let tier_project_id = tier.project_id.ok_or(AppError::NotFound)?; | |
| 164 | - | verify_project_ownership(&state, tier_project_id, user.id).await?; | |
| 164 | + | verify_project_ownership(&state.db, tier_project_id, user.id).await?; | |
| 165 | 165 | ||
| 166 | 166 | // Validate input | |
| 167 | 167 | validation::validate_tier_name(&req.name)?; | |
| @@ -203,7 +203,7 @@ pub(super) async fn delete_tier( | |||
| 203 | 203 | .ok_or(AppError::NotFound)?; | |
| 204 | 204 | ||
| 205 | 205 | let tier_project_id = tier.project_id.ok_or(AppError::NotFound)?; | |
| 206 | - | verify_project_ownership(&state, tier_project_id, user.id).await?; | |
| 206 | + | verify_project_ownership(&state.db, tier_project_id, user.id).await?; | |
| 207 | 207 | ||
| 208 | 208 | db::subscriptions::delete_subscription_tier(&state.db, tier_id).await?; | |
| 209 | 209 |
| @@ -62,7 +62,7 @@ pub(super) async fn suggest_tags( | |||
| 62 | 62 | AuthUser(user): AuthUser, | |
| 63 | 63 | Path(item_id): Path<ItemId>, | |
| 64 | 64 | ) -> Result<Response> { | |
| 65 | - | let (item, _project) = super::verify_item_ownership(&state, item_id, user.id).await?; | |
| 65 | + | let (item, _project) = super::verify_item_ownership(&state.db, item_id, user.id).await?; | |
| 66 | 66 | ||
| 67 | 67 | let tags = db::tags::suggest_tags_for_item( | |
| 68 | 68 | &state.db, |