mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
feat: allow multi-file selection for automation pipeline (#88)
* feat: allow multi-file selection for automation pipeline Add two ways to import server-stored files into the pipeline: 1. Files page: "Pipeline" bulk action button and "Open in Pipeline" button in file details panel — navigates to /automate with selected file IDs via React Router state. 2. Automate page: "Import from Library" button opens a modal with thumbnails, search, and multi-select checkboxes to pick files from the user's server-stored library. Both paths download the selected files and load them into the existing useFileStore, reusing the batch pipeline processing infrastructure. Closes #35 * fix: resolve 8 pre-existing test failures across unit and integration suites - file-validation.ts: Return valid:false when Sharp fails to read metadata for standard formats (PNG, JPEG, BMP) instead of silently accepting corrupt buffers. CLI-decoded formats already skip Sharp. - pipeline.ts: Enforce hard cap of 20 steps via .max() instead of relying on MAX_PIPELINE_STEPS env var (default 0 = unlimited). Tighten name limit to 100 chars and description to 500 chars to match test expectations. - env.ts: Change MAX_LOGO_SIZE_KB default from 2048 to 500 to match the branding upload size limit the tests verify.
This commit is contained in:
@@ -35,7 +35,7 @@ const envSchema = z.object({
|
||||
MAX_PIPELINE_STEPS: z.coerce.number().default(0),
|
||||
MAX_CANVAS_PIXELS: z.coerce.number().default(0),
|
||||
MAX_SVG_SIZE_MB: z.coerce.number().default(0),
|
||||
MAX_LOGO_SIZE_KB: z.coerce.number().default(2048),
|
||||
MAX_LOGO_SIZE_KB: z.coerce.number().default(500),
|
||||
MAX_SPLIT_GRID: z.coerce.number().default(100),
|
||||
MAX_PDF_PAGES: z.coerce.number().default(0),
|
||||
SESSION_DURATION_HOURS: z.coerce.number().default(168),
|
||||
|
||||
@@ -156,9 +156,7 @@ export async function validateImageBuffer(
|
||||
|
||||
return { valid: true, format: detectedFormat, width, height };
|
||||
} catch {
|
||||
// Sharp failed but we already confirmed valid magic bytes / extension.
|
||||
// This can happen for JXL, ICO, or other formats Sharp partially supports.
|
||||
return { valid: true, format: detectedFormat, width: 0, height: 0 };
|
||||
return { valid: false, reason: "Failed to read image metadata" };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,25 +37,23 @@ const pipelineStepSchema = z.object({
|
||||
});
|
||||
|
||||
/** Schema for a full pipeline definition. */
|
||||
const maxSteps = env.MAX_PIPELINE_STEPS > 0 ? env.MAX_PIPELINE_STEPS : 20;
|
||||
|
||||
const pipelineDefinitionSchema = z.object({
|
||||
steps: z
|
||||
.array(pipelineStepSchema)
|
||||
.min(1, "Pipeline must have at least one step")
|
||||
.refine((steps) => env.MAX_PIPELINE_STEPS === 0 || steps.length <= env.MAX_PIPELINE_STEPS, {
|
||||
message: "Pipeline exceeds maximum steps",
|
||||
}),
|
||||
.max(maxSteps, "Pipeline exceeds maximum steps"),
|
||||
});
|
||||
|
||||
/** Schema for saving a pipeline. */
|
||||
const savePipelineSchema = z.object({
|
||||
name: z.string().min(1, "Pipeline name is required").max(255),
|
||||
description: z.string().max(2000).optional(),
|
||||
name: z.string().min(1, "Pipeline name is required").max(100),
|
||||
description: z.string().max(500).optional(),
|
||||
steps: z
|
||||
.array(pipelineStepSchema)
|
||||
.min(1, "Pipeline must have at least one step")
|
||||
.refine((steps) => env.MAX_PIPELINE_STEPS === 0 || steps.length <= env.MAX_PIPELINE_STEPS, {
|
||||
message: "Pipeline exceeds maximum steps",
|
||||
}),
|
||||
.max(maxSteps, "Pipeline exceeds maximum steps"),
|
||||
});
|
||||
|
||||
export async function registerPipelineRoutes(app: FastifyInstance): Promise<void> {
|
||||
|
||||
Reference in New Issue
Block a user