upload volledige repo

This commit is contained in:
kodi
2026-03-11 09:39:41 +01:00
commit ce420cbb0e
110 changed files with 5660 additions and 0 deletions
+274
View File
@@ -0,0 +1,274 @@
# 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:
```json
{
"source": "storage1/path/file.txt",
"destination": "storage2/path/file_copy.txt"
}
```
Response (202):
```json
{
"task_id": "<uuid>",
"status": "queued"
}
```
#### POST /api/files/move
Request:
```json
{
"source": "storage1/path/file.txt",
"destination": "storage2/path/file_moved.txt"
}
```
Response (202):
```json
{
"task_id": "<uuid>",
"status": "queued"
}
```
#### GET /api/tasks/{task_id}
Response (200):
```json
{
"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):
```json
{
"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_id`s (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.