| 16 |
16 |
|
use crate::{
|
| 17 |
17 |
|
auth::AuthUser,
|
| 18 |
18 |
|
db,
|
| 19 |
|
- |
error::{Result, ResultExt},
|
|
19 |
+ |
error::{AppError, Result, ResultExt},
|
| 20 |
20 |
|
helpers::{is_htmx_request, sanitize_csv_cell},
|
| 21 |
21 |
|
templates::{ExportDownloadTemplate, FormStatusTemplate},
|
| 22 |
22 |
|
AppState,
|
| 161 |
161 |
|
// Get all projects and all items in 2 queries (not N+1)
|
| 162 |
162 |
|
let projects = db::projects::get_projects_by_user(&state.db, user.id).await?;
|
| 163 |
163 |
|
let all_items = db::items::get_items_by_user(&state.db, user.id).await?;
|
|
164 |
+ |
|
|
165 |
+ |
// DoS backstop: this handler materializes the entire catalog into an
|
|
166 |
+ |
// in-memory JSON tree and string on the request path. The ceiling is far
|
|
167 |
+ |
// above any real creator's catalog, so it never truncates a legitimate
|
|
168 |
+ |
// "full export" — it only refuses a pathological one with a clear message
|
|
169 |
+ |
// rather than pinning unbounded memory (Run 20 Perf).
|
|
170 |
+ |
if all_items.len() > EXPORT_MAX_ROWS {
|
|
171 |
+ |
tracing::warn!(user_id = %user.id, items = all_items.len(), "project export exceeds row ceiling, refusing");
|
|
172 |
+ |
let msg = "Your catalog is too large to export in a single request. Contact info@makenot.work for a bulk export.";
|
|
173 |
+ |
if is_htmx {
|
|
174 |
+ |
return export_error_html(msg);
|
|
175 |
+ |
}
|
|
176 |
+ |
return Err(AppError::BadRequest(msg.to_string()));
|
|
177 |
+ |
}
|
|
178 |
+ |
|
| 164 |
179 |
|
let all_item_ids: Vec<db::ItemId> = all_items.iter().map(|i| i.id).collect();
|
| 165 |
180 |
|
let tags_map = db::tags::get_tags_for_items(&state.db, &all_item_ids).await?;
|
| 166 |
181 |
|
|