Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3478854385 | |||
| d0278f0370 |
@@ -96,7 +96,8 @@ pub(crate) const EDITOR_PROJECT_ID_PREFIX: &str = "proj-";
|
||||
pub(crate) const EDITOR_RESOURCE_ID_PREFIX: &str = "editor-resource-";
|
||||
pub(crate) const EDITOR_ASSET_FOLDER_ID_PREFIX: &str = "editor-asset-folder-";
|
||||
pub(crate) const EDITOR_ASSET_ID_PREFIX: &str = "editor-asset-";
|
||||
const EDITOR_LAYOUT_MAX_BYTES: usize = 256 * 1024;
|
||||
const EDITOR_LAYOUT_MAX_BYTES: usize = 2 * 1024 * 1024;
|
||||
pub(crate) const EDITOR_LAYOUT_REQUEST_BODY_MAX_BYTES: usize = 4 * 1024 * 1024;
|
||||
pub(crate) const EDITOR_PROJECT_DEFAULT_TITLE: &str = "未命名画布";
|
||||
const EDITOR_ASSET_METADATA_MAX_BYTES: usize = 64 * 1024;
|
||||
const EDITOR_ASSET_DEFAULT_FOLDER_ID: &str = "project";
|
||||
@@ -8314,6 +8315,24 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn editor_layout_serialization_accepts_payload_up_to_two_mib() {
|
||||
let payload = json!("x".repeat(EDITOR_LAYOUT_MAX_BYTES - 2));
|
||||
|
||||
let serialized = serialize_editor_layers(payload).expect("two MiB layout should save");
|
||||
|
||||
assert_eq!(serialized.len(), EDITOR_LAYOUT_MAX_BYTES);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn editor_layout_serialization_rejects_payload_over_two_mib() {
|
||||
let payload = json!("x".repeat(EDITOR_LAYOUT_MAX_BYTES - 1));
|
||||
|
||||
let error = serialize_editor_layers(payload).expect_err("oversized layout should fail");
|
||||
|
||||
assert!(error.body_text().contains("图层布局过大"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn editor_asset_metadata_serialization_rejects_nested_inline_media() {
|
||||
let error = serialize_editor_asset_metadata(Some(json!({
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use axum::{
|
||||
Router,
|
||||
extract::DefaultBodyLimit,
|
||||
handler::Handler,
|
||||
middleware,
|
||||
routing::{get, patch, post},
|
||||
};
|
||||
@@ -13,12 +14,12 @@ use crate::{
|
||||
stream_editor_agent_message,
|
||||
},
|
||||
editor_project::{
|
||||
create_editor_asset, create_editor_asset_folder, create_editor_project,
|
||||
create_editor_project_resource, delete_editor_asset, delete_editor_asset_folder,
|
||||
delete_editor_project, edit_editor_image, extract_editor_ui_design_assets,
|
||||
generate_editor_icon_spritesheet, generate_editor_image, get_editor_asset_library,
|
||||
get_editor_generation_pricing, get_editor_project, list_editor_projects,
|
||||
list_public_editor_project_resources, load_recent_editor_project,
|
||||
EDITOR_LAYOUT_REQUEST_BODY_MAX_BYTES, create_editor_asset, create_editor_asset_folder,
|
||||
create_editor_project, create_editor_project_resource, delete_editor_asset,
|
||||
delete_editor_asset_folder, delete_editor_project, edit_editor_image,
|
||||
extract_editor_ui_design_assets, generate_editor_icon_spritesheet, generate_editor_image,
|
||||
get_editor_asset_library, get_editor_generation_pricing, get_editor_project,
|
||||
list_editor_projects, list_public_editor_project_resources, load_recent_editor_project,
|
||||
remove_editor_image_background, rename_editor_project, save_editor_project_layout,
|
||||
split_editor_icon_spritesheet, submit_editor_asset_showcase,
|
||||
toggle_editor_showcase_asset_like, update_editor_asset, update_editor_asset_folder,
|
||||
@@ -58,7 +59,10 @@ pub fn router(state: AppState) -> Router<AppState> {
|
||||
.route(
|
||||
"/api/editor/projects/{project_id}",
|
||||
get(get_editor_project)
|
||||
.patch(save_editor_project_layout)
|
||||
.patch(
|
||||
save_editor_project_layout
|
||||
.layer(DefaultBodyLimit::max(EDITOR_LAYOUT_REQUEST_BODY_MAX_BYTES)),
|
||||
)
|
||||
.delete(delete_editor_project)
|
||||
.route_layer(middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
use axum::{
|
||||
Router, middleware,
|
||||
Router,
|
||||
extract::DefaultBodyLimit,
|
||||
middleware,
|
||||
routing::{get, patch, post},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
editor_project::EDITOR_LAYOUT_REQUEST_BODY_MAX_BYTES,
|
||||
external_api_auth::require_external_api_key,
|
||||
external_assets_api::{
|
||||
confirm_external_asset_object, create_external_direct_upload_ticket,
|
||||
@@ -84,10 +87,12 @@ pub fn router(state: AppState) -> Router<AppState> {
|
||||
)
|
||||
.route(
|
||||
"/api/external/v1/editor/projects/{project_id}/canvas",
|
||||
patch(save_external_editor_canvas).route_layer(middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
require_external_api_key,
|
||||
)),
|
||||
patch(save_external_editor_canvas)
|
||||
.layer(DefaultBodyLimit::max(EDITOR_LAYOUT_REQUEST_BODY_MAX_BYTES))
|
||||
.route_layer(middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
require_external_api_key,
|
||||
)),
|
||||
)
|
||||
.route(
|
||||
"/api/external/v1/editor/projects/{project_id}/resources",
|
||||
|
||||
@@ -7,7 +7,7 @@ const EDITOR_ASSET_DEFAULT_FOLDER_ID: &str = "project";
|
||||
const EDITOR_ASSET_DEFAULT_FOLDER_LABEL: &str = "项目素材";
|
||||
const EDITOR_PROJECT_MAX_TITLE_CHARS: usize = 80;
|
||||
const EDITOR_ASSET_MAX_LABEL_CHARS: usize = 80;
|
||||
const EDITOR_PROJECT_MAX_LAYOUT_JSON_BYTES: usize = 256 * 1024;
|
||||
const EDITOR_PROJECT_MAX_LAYOUT_JSON_BYTES: usize = 2 * 1024 * 1024;
|
||||
const EDITOR_PROJECT_SOURCE_TYPES: [&str; 3] = ["uploaded", "generated", "mock_generated"];
|
||||
const EDITOR_SHOWCASE_STATUS_PENDING: &str = "pending";
|
||||
const EDITOR_SHOWCASE_STATUS_APPROVED: &str = "approved";
|
||||
@@ -1549,6 +1549,7 @@ fn persist_editor_project_layout(
|
||||
input_layers_json,
|
||||
now,
|
||||
)?;
|
||||
let layers_json = normalize_layout_json(layers_json)?;
|
||||
|
||||
ctx.db.editor_canvas().canvas_id().delete(&canvas.canvas_id);
|
||||
ctx.db.editor_canvas().insert(EditorCanvas {
|
||||
@@ -4997,6 +4998,74 @@ mod tests {
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn editor_layout_accepts_json_up_to_two_mib() {
|
||||
let payload = format!(
|
||||
"\"{}\"",
|
||||
"x".repeat(EDITOR_PROJECT_MAX_LAYOUT_JSON_BYTES - 2)
|
||||
);
|
||||
|
||||
let normalized = normalize_layout_json(payload).expect("two MiB layout should save");
|
||||
|
||||
assert_eq!(normalized.len(), EDITOR_PROJECT_MAX_LAYOUT_JSON_BYTES);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn editor_layout_rejects_json_over_two_mib() {
|
||||
let payload = format!(
|
||||
"\"{}\"",
|
||||
"x".repeat(EDITOR_PROJECT_MAX_LAYOUT_JSON_BYTES - 1)
|
||||
);
|
||||
|
||||
let error = normalize_layout_json(payload).expect_err("oversized layout should fail");
|
||||
|
||||
assert_eq!(error, "图片画布图层布局过大");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn editor_layout_rejects_merged_result_over_two_mib() {
|
||||
let incoming = json!([{
|
||||
"itemType": "generation-dialog",
|
||||
"layerId": "generation-dialog:dialog-1",
|
||||
"resourceId": "generation-dialog:dialog-1",
|
||||
"padding": "x".repeat(EDITOR_PROJECT_MAX_LAYOUT_JSON_BYTES - 200),
|
||||
"dialog": {
|
||||
"id": "dialog-1",
|
||||
"mode": "generate",
|
||||
"status": "generating"
|
||||
}
|
||||
}]);
|
||||
let current = json!([
|
||||
{
|
||||
"layerId": "layer-generated",
|
||||
"resourceId": "resource-generated",
|
||||
"title": "生成结果"
|
||||
},
|
||||
{
|
||||
"itemType": "generation-dialog",
|
||||
"layerId": "generation-dialog:dialog-1",
|
||||
"resourceId": "generation-dialog:dialog-1",
|
||||
"dialog": {
|
||||
"id": "dialog-1",
|
||||
"mode": "generate",
|
||||
"status": "idle",
|
||||
"generatedLayerId": "layer-generated"
|
||||
}
|
||||
}
|
||||
]);
|
||||
let merged = merge_completed_canvas_generation_items(
|
||||
current.to_string().as_str(),
|
||||
incoming.to_string(),
|
||||
)
|
||||
.expect("merge itself should succeed");
|
||||
|
||||
assert!(merged.len() > EDITOR_PROJECT_MAX_LAYOUT_JSON_BYTES);
|
||||
assert_eq!(
|
||||
normalize_layout_json(merged).expect_err("final merged layout should fail"),
|
||||
"图片画布图层布局过大"
|
||||
);
|
||||
}
|
||||
|
||||
fn public_showcase_asset() -> EditorShowcaseAsset {
|
||||
let now = Timestamp::from_micros_since_unix_epoch(1_000_000);
|
||||
EditorShowcaseAsset {
|
||||
|
||||
Reference in New Issue
Block a user