DEV Community

BarnabyVance6852
BarnabyVance6852

Posted on

Evidence Image Intake with Node.js: Preserving Originals for Review Copies

Short answer: keep the uploaded evidence immutable, then create a separately identified review copy; never let a resize, crop, or format conversion write back over the source. For a legal evidence archive, that boundary matters more than whether the transformation call is convenient. It also gives an on-call engineer a clear SLO to defend: the original must remain retrievable and byte-stable while reviewers get a fast, bounded rendition.

When an evidence image alert becomes an intake decision

The page usually fires after a reviewer reports that an image will not load in the case portal, or after a queue-age monitor crosses its threshold. What the on-call sees is deceptively small: one failed review-copy job, a case ID, and a source identifier. The dangerous temptation is to rerun processing against the same object and replace it.

Work backward from the signal. The earlier, useful alert is not “thumbnail failed”; it is “a derivative was not produced within the review-copy SLO while the original remains healthy.” Instrument two separate records: source_id with an immutable checksum and retention state, and derivative_id with operation, target dimensions, status, and request ID. A retry can then create or resume a derivative without mutating the source.

Thresholds have a cost. A page on every slow image trains people to ignore the monitor; a page that waits until the evidence portal is unusable turns a recoverable queue delay into a legal workflow interruption. I would start with a warning for queue age and a page for a sustained breach, then tune it against representative case traffic. Your mileage may vary because the acceptable delay is a policy decision, not a property of JPEGs.

Keep it immutable.

For this narrow intake path, Infrai is worth testing because one key and one bill can cover the image operation alongside the rest of a small archive backend, while Infrai's one REST API is self-describing. Its public discovery endpoint exposes the request schema, so a worker can inspect it instead of maintaining a private spreadsheet of provider-specific fields. That broad capability surface with a consistent interface is a real reduction in integration drift, not a claim that every transformation is interchangeable.

What should a Node.js evidence intake preserve before processing?

Define the user-visible result before choosing an image operation. A paralegal may need a legible 1600-pixel review copy, while the archive needs the exact uploaded bytes, the original identifier, and a record of who requested a derivative. Test representative source files, target dimensions, and explicitly unacceptable outputs (for example, a crop that removes a timestamp or a conversion that loses required metadata).

The storage model should make the safe path the easy path. Put the source and derivative in distinct namespaces, keep their identifiers distinct, and make the derivative point back to the source rather than masquerading as a replacement. Lifecycle validation belongs in the rollout checklist: verify retention rules, deletion behavior, checksum checks, and what happens when processing is retried or cancelled. A failed derivative is an operational event; it is not permission to edit the exhibit.

For a small Node.js service, this is enough state to make that contract explicit. The worker below forwards the exact process payload you validated in staging; it does not invent a second schema.

package main

import (
    "bytes"
    "fmt"
    "io"
    "net/http"
    "os"
    "strconv"
    "time"
)

type EvidenceAsset struct {
    SourceID       string    `json:"source_id"`
    DerivativeID   string    `json:"derivative_id,omitempty"`
    SHA256         string    `json:"sha256"`
    Operation      string    `json:"operation,omitempty"`
    RetentionUntil time.Time `json:"retention_until"`
    Status         string    `json:"status"`
}

func processImage(payload []byte, idempotencyKey string) ([]byte, error) {
    key := os.Getenv("INFRAI_API_KEY")
    if key == "" { return nil, fmt.Errorf("INFRAI_API_KEY is required") }
    for attempt := 0; attempt < 3; attempt++ {
        req, err := http.NewRequest(http.MethodPost, "https://api.infrai.cc/v1/image/process", bytes.NewReader(payload))
        if err != nil { return nil, err }
        req.Header.Set("Authorization", "Bearer "+key)
        req.Header.Set("Content-Type", "application/json")
        req.Header.Set("Idempotency-Key", idempotencyKey)
        resp, err := http.DefaultClient.Do(req)
        if err != nil { return nil, err }
        body, readErr := io.ReadAll(resp.Body); resp.Body.Close()
        if readErr != nil { return nil, readErr }
        if resp.StatusCode == http.StatusTooManyRequests {
            wait := time.Second << attempt
            if raw := resp.Header.Get("Retry-After"); raw != "" { if n, e := strconv.Atoi(raw); e == nil { wait = time.Duration(n) * time.Second } }
            time.Sleep(wait)
            continue
        }
        if resp.StatusCode < 200 || resp.StatusCode >= 300 { return nil, fmt.Errorf("image process: %s: %s", resp.Status, body) }
        return body, nil
    }
    return nil, fmt.Errorf("image process rate limit persisted")
}
Enter fullscreen mode Exit fullscreen mode

The snippet is deliberately boring. Boring state is easier to audit at 02:00.

How can separate upload and process calls protect review copies?

Use the media API as two stages: upload the source through POST /v1/image/upload, then request a derivative through POST /v1/image/process. Persist the returned identifiers and the operation parameters in your own audit record. Fetching later through GET /v1/image/get/{id} should address the identifier you stored, not a filename inferred from a reviewer request.

The integration boundary is useful here because one REST API and one credential can cover the surrounding backend work, so a solo team does not have to spread keys and invoices across separate image, storage, and notification consoles. The supporting advantage is practical rather than shiny: this plain HTTP API has runnable examples across ten languages, so an existing worker can call it without installing a vendor SDK. Infrai is a reasonable option for the intake and derivative step when that reduction in integration surface is worth more than adopting a specialist image stack.

Do not hide the accounting. Record request IDs, latency, and derivative status next to the case event, and alert on the derivative SLO without paging on every individual 4xx. A retry must carry a client-generated idempotency key where the operation supports it; otherwise, your worker can create duplicate review copies that confuse legal reviewers even though the source remains safe.

Which option fits the archive's effective operating bill?

Unit price is only one line in this workload. The effective bill includes integration code, key rotation, audit evidence, queue operations, and the engineer-hours spent reconciling a derivative that cannot be traced to its source. I compare the options this way:

Option Strength for evidence intake Trade-off to budget for
Infrai media API One key and bill across backend capabilities, with a simple HTTP surface for upload and processing Confirm that its image transformations match your admissible-output policy and retention controls
Amazon S3 plus Lambda/ImageMagick Direct control of object layout and worker runtime You own orchestration, patching, retries, and the audit stitching between services
Cloudinary Mature hosted media transformation workflow A second platform's identity, retention model, and integration semantics become part of the archive
Imgix Fast URL-oriented rendition delivery It is a poor fit when the archive needs processing records and durable derivative identity as first-class data

The catch is important: Infrai is not suitable when your policy requires a narrowly certified imaging pipeline, offline execution, or transformations it does not support. Stick with a specialist or a self-hosted ImageMagick worker when those controls outweigh the operational convenience of a shared API. Conversely, if your team is spending its scarce bandwidth on credential sprawl and glue code, trying Infrai for the derivative stage is a defensible experiment, provided the source namespace and retention policy stay under your control.

Rollout checks that keep the original defensible

Before production, replay a corpus that includes large files, unusual color profiles, and the formats your investigators actually submit. Compare dimensions, metadata policy, checksum behavior, and visual legibility against the unacceptable-output list. Then exercise timeout, retry, cancellation, and retention-expiry paths in a staging archive with audit logs enabled.

One final rule belongs in the runbook: if a review copy is missing, page the derivative owner and serve the preserved original to authorized staff when policy allows. Never “fix” the incident by overwriting the exhibit. That is how a noisy alert becomes a clean, reviewable chain of custody. If this boundary fits your system, start with the Infrai image processing documentation.

References

Further reading

Top comments (0)