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_RESOURCE_ID_PREFIX: &str = "editor-resource-";
|
||||||
pub(crate) const EDITOR_ASSET_FOLDER_ID_PREFIX: &str = "editor-asset-folder-";
|
pub(crate) const EDITOR_ASSET_FOLDER_ID_PREFIX: &str = "editor-asset-folder-";
|
||||||
pub(crate) const EDITOR_ASSET_ID_PREFIX: &str = "editor-asset-";
|
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 = "未命名画布";
|
pub(crate) const EDITOR_PROJECT_DEFAULT_TITLE: &str = "未命名画布";
|
||||||
const EDITOR_ASSET_METADATA_MAX_BYTES: usize = 64 * 1024;
|
const EDITOR_ASSET_METADATA_MAX_BYTES: usize = 64 * 1024;
|
||||||
const EDITOR_ASSET_DEFAULT_FOLDER_ID: &str = "project";
|
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]
|
#[test]
|
||||||
fn editor_asset_metadata_serialization_rejects_nested_inline_media() {
|
fn editor_asset_metadata_serialization_rejects_nested_inline_media() {
|
||||||
let error = serialize_editor_asset_metadata(Some(json!({
|
let error = serialize_editor_asset_metadata(Some(json!({
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use axum::{
|
use axum::{
|
||||||
Router,
|
Router,
|
||||||
extract::DefaultBodyLimit,
|
extract::DefaultBodyLimit,
|
||||||
|
handler::Handler,
|
||||||
middleware,
|
middleware,
|
||||||
routing::{get, patch, post},
|
routing::{get, patch, post},
|
||||||
};
|
};
|
||||||
@@ -13,12 +14,12 @@ use crate::{
|
|||||||
stream_editor_agent_message,
|
stream_editor_agent_message,
|
||||||
},
|
},
|
||||||
editor_project::{
|
editor_project::{
|
||||||
create_editor_asset, create_editor_asset_folder, create_editor_project,
|
EDITOR_LAYOUT_REQUEST_BODY_MAX_BYTES, create_editor_asset, create_editor_asset_folder,
|
||||||
create_editor_project_resource, delete_editor_asset, delete_editor_asset_folder,
|
create_editor_project, create_editor_project_resource, delete_editor_asset,
|
||||||
delete_editor_project, edit_editor_image, extract_editor_ui_design_assets,
|
delete_editor_asset_folder, delete_editor_project, edit_editor_image,
|
||||||
generate_editor_icon_spritesheet, generate_editor_image, get_editor_asset_library,
|
extract_editor_ui_design_assets, generate_editor_icon_spritesheet, generate_editor_image,
|
||||||
get_editor_generation_pricing, get_editor_project, list_editor_projects,
|
get_editor_asset_library, get_editor_generation_pricing, get_editor_project,
|
||||||
list_public_editor_project_resources, load_recent_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,
|
remove_editor_image_background, rename_editor_project, save_editor_project_layout,
|
||||||
split_editor_icon_spritesheet, submit_editor_asset_showcase,
|
split_editor_icon_spritesheet, submit_editor_asset_showcase,
|
||||||
toggle_editor_showcase_asset_like, update_editor_asset, update_editor_asset_folder,
|
toggle_editor_showcase_asset_like, update_editor_asset, update_editor_asset_folder,
|
||||||
@@ -58,7 +59,10 @@ pub fn router(state: AppState) -> Router<AppState> {
|
|||||||
.route(
|
.route(
|
||||||
"/api/editor/projects/{project_id}",
|
"/api/editor/projects/{project_id}",
|
||||||
get(get_editor_project)
|
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)
|
.delete(delete_editor_project)
|
||||||
.route_layer(middleware::from_fn_with_state(
|
.route_layer(middleware::from_fn_with_state(
|
||||||
state.clone(),
|
state.clone(),
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
use axum::{
|
use axum::{
|
||||||
Router, middleware,
|
Router,
|
||||||
|
extract::DefaultBodyLimit,
|
||||||
|
middleware,
|
||||||
routing::{get, patch, post},
|
routing::{get, patch, post},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
editor_project::EDITOR_LAYOUT_REQUEST_BODY_MAX_BYTES,
|
||||||
external_api_auth::require_external_api_key,
|
external_api_auth::require_external_api_key,
|
||||||
external_assets_api::{
|
external_assets_api::{
|
||||||
confirm_external_asset_object, create_external_direct_upload_ticket,
|
confirm_external_asset_object, create_external_direct_upload_ticket,
|
||||||
@@ -84,10 +87,12 @@ pub fn router(state: AppState) -> Router<AppState> {
|
|||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
"/api/external/v1/editor/projects/{project_id}/canvas",
|
"/api/external/v1/editor/projects/{project_id}/canvas",
|
||||||
patch(save_external_editor_canvas).route_layer(middleware::from_fn_with_state(
|
patch(save_external_editor_canvas)
|
||||||
state.clone(),
|
.layer(DefaultBodyLimit::max(EDITOR_LAYOUT_REQUEST_BODY_MAX_BYTES))
|
||||||
require_external_api_key,
|
.route_layer(middleware::from_fn_with_state(
|
||||||
)),
|
state.clone(),
|
||||||
|
require_external_api_key,
|
||||||
|
)),
|
||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
"/api/external/v1/editor/projects/{project_id}/resources",
|
"/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_ASSET_DEFAULT_FOLDER_LABEL: &str = "项目素材";
|
||||||
const EDITOR_PROJECT_MAX_TITLE_CHARS: usize = 80;
|
const EDITOR_PROJECT_MAX_TITLE_CHARS: usize = 80;
|
||||||
const EDITOR_ASSET_MAX_LABEL_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_PROJECT_SOURCE_TYPES: [&str; 3] = ["uploaded", "generated", "mock_generated"];
|
||||||
const EDITOR_SHOWCASE_STATUS_PENDING: &str = "pending";
|
const EDITOR_SHOWCASE_STATUS_PENDING: &str = "pending";
|
||||||
const EDITOR_SHOWCASE_STATUS_APPROVED: &str = "approved";
|
const EDITOR_SHOWCASE_STATUS_APPROVED: &str = "approved";
|
||||||
@@ -1549,6 +1549,7 @@ fn persist_editor_project_layout(
|
|||||||
input_layers_json,
|
input_layers_json,
|
||||||
now,
|
now,
|
||||||
)?;
|
)?;
|
||||||
|
let layers_json = normalize_layout_json(layers_json)?;
|
||||||
|
|
||||||
ctx.db.editor_canvas().canvas_id().delete(&canvas.canvas_id);
|
ctx.db.editor_canvas().canvas_id().delete(&canvas.canvas_id);
|
||||||
ctx.db.editor_canvas().insert(EditorCanvas {
|
ctx.db.editor_canvas().insert(EditorCanvas {
|
||||||
@@ -4997,6 +4998,74 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use serde_json::json;
|
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 {
|
fn public_showcase_asset() -> EditorShowcaseAsset {
|
||||||
let now = Timestamp::from_micros_since_unix_epoch(1_000_000);
|
let now = Timestamp::from_micros_since_unix_epoch(1_000_000);
|
||||||
EditorShowcaseAsset {
|
EditorShowcaseAsset {
|
||||||
|
|||||||
Reference in New Issue
Block a user