Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions docs/export/v1/act.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "EvilCase export v1, !ukon.json of an act",
"type": "object",
"additionalProperties": false,
"required": [
"type", "actId", "actNumber", "externalActNumber", "date", "title", "description", "direction", "caseId",
"contact", "labels", "comments", "files", "created", "updated", "path"
],
"properties": {
"type": { "enum": ["act"] },
"actId": { "$ref": "common.schema.json#/definitions/id" },
"actNumber": { "type": "string" },
"externalActNumber": { "$ref": "common.schema.json#/definitions/optionalText" },
"date": { "type": "string", "format": "date" },
"title": { "type": "string" },
"description": { "description": "Markdown.", "$ref": "common.schema.json#/definitions/optionalText" },
"direction": { "description": "From the case owner's point of view; null exactly where contact is.", "enum": ["Incoming", "Outgoing", null] },
"caseId": { "$ref": "common.schema.json#/definitions/id" },
"contact": {
"oneOf": [{ "type": "null" }, { "$ref": "common.schema.json#/definitions/contactReference" }]
},
"labels": { "type": "array", "items": { "$ref": "common.schema.json#/definitions/labelReference" } },
"comments": { "type": "array", "items": { "$ref": "common.schema.json#/definitions/comment" } },
"files": { "type": "array", "items": { "$ref": "common.schema.json#/definitions/file" } },
"created": { "$ref": "common.schema.json#/definitions/moment" },
"updated": { "$ref": "common.schema.json#/definitions/optionalMoment" },
"path": { "description": "The act's folder in the archive.", "type": "string" }
}
}
31 changes: 31 additions & 0 deletions docs/export/v1/case.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "EvilCase export v1, !spis.json of a case",
"type": "object",
"additionalProperties": false,
"required": [
"type", "caseId", "caseNumber", "externalCaseNumber", "date", "title", "description", "status", "parentCaseId",
"contact", "labels", "comments", "files", "created", "updated", "path"
],
"properties": {
"type": { "enum": ["case"] },
"caseId": { "$ref": "common.schema.json#/definitions/id" },
"caseNumber": { "type": "string" },
"externalCaseNumber": { "$ref": "common.schema.json#/definitions/optionalText" },
"date": { "type": "string", "format": "date" },
"title": { "type": "string" },
"description": { "description": "Markdown.", "$ref": "common.schema.json#/definitions/optionalText" },
"status": { "enum": ["Active", "WaitingOnAuthority", "Closed"] },
"parentCaseId": { "description": "Null for a root case.", "type": ["string", "null"], "format": "uuid" },
"contact": {
"description": "The counterpart.",
"oneOf": [{ "type": "null" }, { "$ref": "common.schema.json#/definitions/contactReference" }]
},
"labels": { "type": "array", "items": { "$ref": "common.schema.json#/definitions/labelReference" } },
"comments": { "type": "array", "items": { "$ref": "common.schema.json#/definitions/comment" } },
"files": { "description": "The case's own files, not those of its sub-cases or acts.", "type": "array", "items": { "$ref": "common.schema.json#/definitions/file" } },
"created": { "$ref": "common.schema.json#/definitions/moment" },
"updated": { "$ref": "common.schema.json#/definitions/optionalMoment" },
"path": { "description": "The case's folder in the archive.", "type": "string" }
}
}
83 changes: 83 additions & 0 deletions docs/export/v1/common.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "EvilCase export v1, shared definitions",
"definitions": {
"id": {
"type": "string",
"format": "uuid"
},
"moment": {
"description": "ISO 8601 date and time in UTC.",
"type": "string",
"format": "date-time"
},
"optionalMoment": {
"description": "ISO 8601 date and time in UTC; null where the record was never changed.",
"type": ["string", "null"],
"format": "date-time"
},
"optionalText": {
"type": ["string", "null"]
},
"contactReference": {
"type": "object",
"additionalProperties": false,
"required": ["contactId", "name"],
"properties": {
"contactId": { "$ref": "#/definitions/id" },
"name": { "type": "string" }
}
},
"labelReference": {
"type": "object",
"additionalProperties": false,
"required": ["labelId", "name"],
"properties": {
"labelId": { "$ref": "#/definitions/id" },
"name": { "type": "string" }
}
},
"comment": {
"type": "object",
"additionalProperties": false,
"required": ["commentId", "body", "authorEmail", "created", "updated"],
"properties": {
"commentId": { "$ref": "#/definitions/id" },
"body": { "description": "Markdown.", "type": "string" },
"authorEmail": { "type": "string" },
"created": { "$ref": "#/definitions/moment" },
"updated": { "$ref": "#/definitions/optionalMoment" }
}
},
"file": {
"type": "object",
"additionalProperties": false,
"required": ["fileId", "fileName", "versions"],
"properties": {
"fileId": { "$ref": "#/definitions/id" },
"fileName": { "description": "The name the file carries in the application.", "type": "string" },
"versions": {
"description": "By number; the last is the current version.",
"type": "array",
"minItems": 1,
"items": { "$ref": "#/definitions/fileVersion" }
}
}
},
"fileVersion": {
"type": "object",
"additionalProperties": false,
"required": ["number", "sizeBytes", "mediaType", "sha256", "created", "uploaderEmail", "path", "missing"],
"properties": {
"number": { "type": "integer", "minimum": 1 },
"sizeBytes": { "type": "integer", "minimum": 0 },
"mediaType": { "$ref": "#/definitions/optionalText" },
"sha256": { "description": "SHA-256 of the content, lower-case hex.", "type": "string", "pattern": "^[0-9a-f]{64}$" },
"created": { "description": "When the version was uploaded.", "$ref": "#/definitions/moment" },
"uploaderEmail": { "type": "string" },
"path": { "description": "The version's entry in the archive; null where the content was missing.", "$ref": "#/definitions/optionalText" },
"missing": { "description": "The content was not on disk and is not in the archive.", "type": "boolean" }
}
}
}
}
18 changes: 18 additions & 0 deletions docs/export/v1/contact.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "EvilCase export v1, a contact in Kontakty",
"type": "object",
"additionalProperties": false,
"required": ["type", "contactId", "kind", "name", "dataBoxId", "address", "created", "updated", "path"],
"properties": {
"type": { "enum": ["contact"] },
"contactId": { "$ref": "common.schema.json#/definitions/id" },
"kind": { "enum": ["Authority", "Official", "Person"] },
"name": { "type": "string" },
"dataBoxId": { "$ref": "common.schema.json#/definitions/optionalText" },
"address": { "$ref": "common.schema.json#/definitions/optionalText" },
"created": { "$ref": "common.schema.json#/definitions/moment" },
"updated": { "$ref": "common.schema.json#/definitions/optionalMoment" },
"path": { "description": "The contact's document in the archive.", "type": "string" }
}
}
15 changes: 15 additions & 0 deletions docs/export/v1/export.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "EvilCase export v1, export.json",
"type": "object",
"additionalProperties": false,
"required": ["type", "formatVersion", "exported", "applicationVersion", "tenantId", "tenantName"],
"properties": {
"type": { "enum": ["export"] },
"formatVersion": { "enum": [1] },
"exported": { "description": "When the export started; the archive holds the data as it stood then.", "$ref": "common.schema.json#/definitions/moment" },
"applicationVersion": { "type": "string" },
"tenantId": { "$ref": "common.schema.json#/definitions/id" },
"tenantName": { "type": "string" }
}
}
23 changes: 23 additions & 0 deletions docs/export/v1/labels.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "EvilCase export v1, Štítky/!stitky.json",
"type": "object",
"additionalProperties": false,
"required": ["type", "labels"],
"properties": {
"type": { "enum": ["labels"] },
"labels": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["labelId", "name", "color"],
"properties": {
"labelId": { "$ref": "common.schema.json#/definitions/id" },
"name": { "type": "string" },
"color": { "enum": ["Blue", "Azure", "Indigo", "Purple", "Pink", "Red", "Orange", "Yellow", "Lime", "Green", "Teal", "Cyan", "Gray"] }
}
}
}
}
}
3 changes: 2 additions & 1 deletion docs/sdd/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ SDD (software design document) závazně popisuje jednu oblast návrhu EvilCase.
## Pořadí čtení

Architektura 001 jako první. Průřezová 002–005 a 018 platí pro každou změnu ve své oblasti;
základ 006–008 (tenance, model, číslování), agendy 009–013 a 019, aplikace 015–017, 020 a 021.
základ 006–008 (tenance, model, číslování), agendy 009–013 a 019, aplikace 015–017, 020–022.

## Mapa SDD ↔ milník

Expand All @@ -40,3 +40,4 @@ základ 006–008 (tenance, model, číslování), agendy 009–013 a 019, aplik
| [019](sdd-019-stitky.md) | Štítky a nastavení | M8 |
| [020](sdd-020-vzhled.md) | Vzhled | M9 |
| [021](sdd-021-vyhledavani.md) | Vyhledávání | M9 |
| [022](sdd-022-export.md) | Export | — |
1 change: 1 addition & 0 deletions docs/sdd/sdd-016-navigace-a-vzhled.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ URL nesou UUID entit, nikdy jejich čísla:
| `/contacts` | kontakty |
| `/contacts/{id}` | detail kontaktu |
| `/settings` | nastavení (SDD-019) |
| `/settings/export` | export dat tenanta (SDD-019, SDD-022) |
| `/login` | přihlášení |

Neznámá routa vykreslí stav nenalezeno uvnitř aplikace, neznámé id v routě prázdný stav
Expand Down
10 changes: 8 additions & 2 deletions docs/sdd/sdd-019-stitky.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
- **Stav:** platí
- **Milníky:** M8
- **Související SDD:** [007](sdd-007-domenovy-model.md), [009](sdd-009-spisy.md),
[010](sdd-010-ukony.md), [016](sdd-016-navigace-a-vzhled.md)
[010](sdd-010-ukony.md), [016](sdd-016-navigace-a-vzhled.md), [022](sdd-022-export.md)

## Rozsah

Expand All @@ -25,7 +25,13 @@ ničem. Tutéž dvojici nese nejvýše jednou. Spis ani úkon nemá počet ští

### Nastavení

`/settings` je agenda nastavení tenantu; zatím nese jedinou sekci, Štítky. Sekce ukazuje štítky
`/settings` je agenda nastavení tenantu se sekcemi Štítky (`/settings`) a Export
(`/settings/export`). Na desktopu je mezi sekcemi svislý panel, na mobilu řada pilulek.

Sekce Export popíše, co archiv nese a co ne, a nese tlačítko Exportovat vše s názvem souboru,
pod kterým se archiv stáhne. Export řídí [SDD-022](sdd-022-export.md).

Sekce Štítky ukazuje štítky
seřazené podle názvu, u každého počet spisů a počet úkonů, které ho nesou, a nechá štítek
založit, přejmenovat, přebarvit a smazat. Název, který už nese jiný štítek, se odmítne 409
(SDD-004). Smazání štítku ho sejme ze všech spisů a úkonů, které ho nesly; spis ani úkon tím
Expand Down
110 changes: 110 additions & 0 deletions docs/sdd/sdd-022-export.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# SDD-022 — Export

- **Stav:** platí
- **Milníky:** —
- **Související SDD:** [006](sdd-006-tenance-a-ucty.md), [007](sdd-007-domenovy-model.md),
[012](sdd-012-soubory.md), [019](sdd-019-stitky.md)

## Rozsah

Ruční export všech dat tenanta do jednoho archivu zip: spouštění, struktura archivu a formát
dokumentů JSON. Sekci nastavení, ze které se export spouští, drží SDD-019. Plánovaný export,
rotace, ukládání exportů, archiv s heslem a import do rozsahu nepatří.

## Popis

### Spuštění

Tlačítko Exportovat vše v sekci Export nastavení stáhne `GET /api/export` obyčejným stažením
prohlížečem, stejně jako Stáhnout vše (SDD-012). Exportovat smí každý přihlášený uživatel tenanta;
nepřihlášený dostane 401. Archiv nese jen data tenanta volajícího (SDD-006).

Archiv se jmenuje `EvilCase export 2026-10-30.zip` podle pražského data exportu. Posílá se
průběžně, jak se píše, bez přípravy předem a bez omezení velikosti, i nad 4 GB. Všechna data se
čtou z jednoho snímku databáze pořízeného na začátku exportu; změna během exportu v archivu není.
Přerušené stažení export zastaví. Chyba po prvním bajtu stažení přeruší; prohlížeč nikdy neuloží
useknutý archiv jako celý.

### Struktura

```
export.json
Spisy/
2026-08-21 Přestupek/
!spis.json
Smlouva (verze 1).pdf
Smlouva.pdf
2026-09-01 Odvolání/
!spis.json
2026-09-02 Podání/
!ukon.json
Podání.pdf
Kontakty/
Krajský úřad.json
Štítky/
!stitky.json
```

- `Spisy/` nese kořenové spisy. Složka spisu nese `!spis.json`, soubory spisu s verzemi a jednu
složku za každý podspis a každý úkon, bez mezisložek. Podspisy se zanořují do libovolné
hloubky. Složka úkonu nese `!ukon.json` a soubory úkonu s verzemi.
- Složka spisu i úkonu se jmenuje `<datum> <název>`, datum `yyyy-MM-dd`. Název ztratí znaky,
které jméno souboru nesmí obsahovat, jako ve Stáhnout vše, a zkrátí se na 80 znaků. Název, ze
kterého nic nezbude, nahradí „Spis“ nebo „Úkon“.
- Soubory leží jako ve Stáhnout vše včetně verzí: aktuální verze pod jménem souboru, starší vedle
ní jako „Smlouva (verze 2).pdf“.
- `Kontakty/<jméno>.json` je jeden kontakt; jméno se upraví jako název spisu, prázdné nahradí
„Kontakt“.
- `Štítky/!stitky.json` nese všechny štítky tenanta; tenant bez štítků ho nemá.
- Jméno, které už ve složce je (bez ohledu na velikost písmen), dostane „ (2)“, „ (3)“ a další
volné číslo, u souboru před příponou. Jméno si zabírají v tomto pořadí: dokument spisu nebo
úkonu, složky podspisů, složky úkonů, soubory. Dokument JSON nese skutečný název i cestu
v archivu.
- Položky archivu nesou pražský čas, soubor okamžik nahrání své verze, dokument spisu, úkonu
a kontaktu okamžik poslední změny záznamu, ostatní začátek exportu. Jména položek jsou v UTF-8.
- Verze, jejíž obsah na disku chybí, export nezastaví: v archivu není, její dokument ji označí
`missing: true` a zaloguje se varování.
- Tenant bez dat vydá `export.json` a prázdné složky `Spisy`, `Kontakty` a `Štítky`.

### Dokumenty JSON

Formát je verzovaný; každá změna struktury zvýší verzi formátu i jejích schémat. Schéma JSON
každého typu dokumentu leží v [`docs/export/v1/`](../export/v1/): `export.schema.json`,
`case.schema.json`, `act.schema.json`, `contact.schema.json`, `labels.schema.json`
a společné definice `common.schema.json`.

- Klíče jsou anglicky a jmenují se jako v kontraktu API; datum je `yyyy-MM-dd`, okamžik ISO 8601
v UTC; dokumenty nesou id.
- Pole bez hodnoty je přítomné jako `null`.
- Každý dokument nese `type`: `export`, `case`, `act`, `contact` nebo `labels`.
- `export.json`: verze formátu, okamžik exportu, verze aplikace, id a název tenanta.
- `!spis.json`: všechna pole spisu, id nadřízeného spisu, protistrana jako id a jméno, štítky
jako id a název, komentáře, soubory, okamžik založení a poslední změny, cesta složky.
- `!ukon.json`: všechna pole úkonu, id spisu, kontakt jako id a jméno, štítky jako id a název,
komentáře, soubory, okamžik založení a poslední změny, cesta složky.
- Komentář: id, text, e-mail autora, okamžik založení a poslední změny.
- Soubor: id, jméno a u každé verze číslo, velikost, typ obsahu, SHA-256, okamžik nahrání, e-mail
toho, kdo ji nahrál, cesta v archivu a příznak `missing`.
- Kontakt: všechna pole, okamžik založení a poslední změny, cesta v archivu.
- Štítky: u každého id, název a barva.

Export nenese historii změn, smazané záznamy, uživatele, refresh tokeny ani data jiných tenantů.

### Logování

Začátek a konec exportu se logují s id tenanta, počty spisů, úkonů, kontaktů, štítků a verzí
souborů, počtem chybějících obsahů a velikostí archivu; obsah nikdy.

## Rozhodnutí

- Spuštění: příprava na pozadí a odkaz ke stažení / průběžné stažení. Platí průběžné stažení.
- Oprávnění: jen správce / každý přihlášený uživatel tenanta. Platí každý přihlášený uživatel.
- Struktura: plochý seznam dokumentů / složky podle stromu spisů. Platí složky podle stromu.
- Formát: kontrakt API / vlastní dokumenty exportu. Platí vlastní dokumenty, aby se kontrakt API
mohl měnit bez změny formátu.
- Chybějící obsah verze: přerušit export / označit a pokračovat. Platí označit a pokračovat.

## Dopady

Sekci Export v nastavení drží SDD-019, routu SDD-016. Jména souborů a verzí sdílí export se
Stáhnout vše (SDD-012).
Loading
Loading