Files
webmanager-mvp/project_docs/COPY_MOVE_TASKS_DESIGN.md
2026-03-11 09:39:41 +01:00

7.4 KiB
Raw Permalink Blame History

COPY_MOVE_TASKS_DESIGN.md

Doel

Ontwerpvoorstel voor de volgende implementatieslice: copy, move en tasks.

Dit document bevat alleen ontwerpkeuzes. Er is geen code-implementatie in deze stap.

1. Destination-semantiek (expliciet)

V1 kiest een strikte semantiek:

  • destination is altijd het volledige beoogde eindpad (inclusief bestandsnaam).
  • destination mag in v1 niet geïnterpreteerd worden als "copy/move into existing directory".
  • Als destination al bestaat (file of directory) -> already_exists (409).

Voorbeeld v1:

  • source: storage1/a/file.txt
  • destination: storage2/b/file_copy.txt

Niet toegestaan als impliciete directory-target in v1:

  • destination: storage2/b/ met verwachting dat file.txt automatisch eronder komt.

Reden:

  • voorkomt ambigu gedrag
  • eenvoudiger API-contract
  • minder regressierisico

2. Scopevoorstel copy v1

Keuze

  • copy ondersteunt in v1 alleen files.
  • directory-recursie schuift door naar een latere fase.

Motivatie

  • Complexiteit: recursieve copy voegt veel edge-cases toe (diepe bomen, symlinks, partial failures).
  • Testbaarheid: file-only maakt golden/regressie tests kleiner en deterministischer.
  • Regressierisico: lagere kans op onverwachte performance/security regressies.

Conflictgedrag

  • destination bestaat al -> already_exists (409).
  • geen overwrite/merge in v1.

Uitvoering

  • altijd task-based (async).
  • create response: task_id + queued.

Foutmodel copy

  • invalid_request (400)
  • path_traversal_detected (403)
  • path_outside_whitelist (403)
  • invalid_root_alias (403)
  • path_not_found (404)
  • type_conflict (409) (source is geen file)
  • already_exists (409)
  • io_error (500)

3. Scopevoorstel move v1

Rename vs move

  • rename blijft naamwijziging binnen dezelfde parent directory.
  • move is padwijziging naar een ander eindpad (zelfde root of cross-root).

Cross-root gedrag

  • toegestaan als source en destination beide binnen whitelist vallen.
  • zelfde root: native rename/move waar mogelijk.
  • cross-root: copy+delete binnen dezelfde task.

Keuze

  • move ondersteunt in v1 alleen files.
  • directory-move buiten scope in v1.

Conflictgedrag

  • destination bestaat al -> already_exists (409).
  • geen overwrite in v1.

Foutmodel move

  • invalid_request (400)
  • path_traversal_detected (403)
  • path_outside_whitelist (403)
  • invalid_root_alias (403)
  • path_not_found (404)
  • type_conflict (409) (source is geen file)
  • already_exists (409)
  • io_error (500)

4. Symlinkbeleid (copy/move)

Source

  • v1 file-only: source mag geen symlink zijn.
  • als source symlink resolvet naar pad buiten whitelist -> geblokkeerd (path_outside_whitelist).
  • als source symlink resolvet binnen whitelist: in v1 alsnog afwijzen als type_conflict om semantiek simpel te houden.

Destination

  • destination wordt via path_guard canoniek gevalideerd.
  • destination parent moet binnen whitelist liggen.
  • destination parent die via symlink buiten whitelist valt -> blokkeren (path_outside_whitelist).

Recursieve escapes

  • Niet van toepassing in v1 (geen directory-recursie).
  • Voor latere directory-copy geldt: elk bezocht pad moet per entry containment-check krijgen.

5. Task persistence en history

Relatie tasks vs history

  • In v1 zijn tasks en history aparte modellen/tabelrollen:
    • tasks: actuele en afgeronde task-status/progress
    • history: audit-log van uitgevoerde operaties
  • history kan vanuit task-completion gevuld worden, maar is niet hetzelfde model.

Retentie

  • v1 bewaart completed en failed tasks persistent (geen automatische cleanup in scope).

Sortering GET /api/tasks

  • standaard sortering: created_at DESC (nieuwste eerst).

6. Taskmodel

Wanneer task verplicht is

  • copy en move altijd task-based.

Statussen

  • queued
  • running
  • completed
  • failed

Progress

  • file-only v1:
    • done_bytes
    • total_bytes
  • done_items/total_items optioneel en kunnen null blijven in v1.

Partial failure

  • fail-fast.
  • eerste fatale fout -> failed.
  • geen rollback in v1.
  • taskdetail bevat failed_item, error_code, error_message.

Duplicate create requests (correctie)

  • v1 gedrag is niet idempotent.
  • twee identieke requests mogen twee verschillende tasks creëren.

7. API-voorstel

Endpoints

  • POST /api/files/copy
  • POST /api/files/move
  • GET /api/tasks/{task_id}
  • GET /api/tasks

Request/response shapes

POST /api/files/copy

Request:

{
  "source": "storage1/path/file.txt",
  "destination": "storage2/path/file_copy.txt"
}

Response (202):

{
  "task_id": "<uuid>",
  "status": "queued"
}

POST /api/files/move

Request:

{
  "source": "storage1/path/file.txt",
  "destination": "storage2/path/file_moved.txt"
}

Response (202):

{
  "task_id": "<uuid>",
  "status": "queued"
}

GET /api/tasks/{task_id}

Response (200):

{
  "id": "<uuid>",
  "operation": "copy",
  "status": "running",
  "source": "storage1/a/file.txt",
  "destination": "storage2/b/file.txt",
  "done_bytes": 1024,
  "total_bytes": 4096,
  "done_items": null,
  "total_items": null,
  "current_item": "storage1/a/file.txt",
  "failed_item": null,
  "error_code": null,
  "error_message": null,
  "created_at": "2026-03-10T00:00:00Z",
  "started_at": "2026-03-10T00:00:01Z",
  "finished_at": null
}

GET /api/tasks

Response (200):

{
  "items": [
    {
      "id": "<uuid>",
      "operation": "move",
      "status": "completed",
      "source": "storage1/a/file.txt",
      "destination": "storage2/b/file.txt",
      "created_at": "2026-03-10T00:00:00Z",
      "finished_at": "2026-03-10T00:00:05Z"
    }
  ]
}

Waarom source en destination ook in task-list

  • Ja, opnemen in GET /api/tasks.
  • Reden: operator kan zonder extra detail-calls direct zien wat elke task doet.
  • Dit maakt UI en troubleshooting eenvoudiger.

Error codes (versmald)

  • invalid_request (400)
  • path_traversal_detected (403)
  • path_outside_whitelist (403)
  • invalid_root_alias (403)
  • path_not_found (404)
  • task_not_found (404)
  • type_conflict (409)
  • already_exists (409)
  • io_error (500)

internal_error wordt in v1 niet als aparte publieke code gebruikt; onverwachte fouten worden genormaliseerd naar io_error of framework-500.

8. Teststrategie

Golden tests

  • POST /api/files/copy success (task create shape)
  • POST /api/files/move success (task create shape)
  • already_exists voor copy/move
  • type_conflict als source geen file is
  • invalid_request voor ongeldige payload
  • traversal/root errors voor source en destination
  • GET /api/tasks/{task_id} shapes voor queued/running/completed/failed
  • GET /api/tasks list shape inclusief source en destination

Regressietests

  • cross-root move gebruikt copy+delete pad correct
  • file copy/move met unicode namen
  • grote files progress updates blijven monotone stijging
  • duplicate create requests leveren 2 verschillende task_ids (niet-idempotent)

Securitytests

  • traversal blokkade op source/destination
  • destination outside whitelist blokkade
  • invalid root alias blokkade
  • symlink source wordt afgewezen
  • symlinked destination parent buiten whitelist wordt afgewezen

Implementatiegrenzen voor volgende stap

  • Geen wijziging aan bestaande endpoints:
    • browse
    • mkdir
    • rename
    • delete
  • Geen nieuwe dependencies zonder expliciete motivatie.