Přeskočit obsah

Synchronizace & Zálohy — Specifikace

Přehled

Každá firma má vyhrazený prostor na S3 s limitem velikosti. Synchronizace zajišťuje konzistenci dat mezi desktopem a webem. Zálohy jsou implicitně zapnuty — aplikace používají S3 komunikaci implementovanou v tomto modulu. Klientská data (mzdové listy, doklady, účetnictví) podléhají zákonné archivační povinnosti, nikoli GDPR výmazu — ta se vztahuje pouze na identifikační data uživatelů uložená odděleně.


Úložný prostor — limity per firma

Výchozí limit:  1 GB per firma
Soft limit:     5 GB  → kritické upozornění (zálohy stále běží)
Hard limit:     dle zakoupeného balíčku

Zakoupení většího prostoru: firma si dokoupí v portálu nebo přes support.
Předpoklad: 1 GB bude pro většinu firem dostatečné.

Chování při překročení limitů

0–1 GB    → normální provoz
1 GB      → SOFT LIMIT PŘEKROČEN
            • Upozornění company adminovi: e-mail + in-app notifikace
            • Zálohy pokračují beze změny
            • Portál zobrazí varování v detailu firmy

1–5 GB    → zálohy stále běží, každý týden opakované upozornění

5 GB      → KRITICKÉ PŘEKROČENÍ
            • Okamžitý e-mail + in-app notifikace company adminovi
            • Notifikace Avaxis (portál — detail firmy)
            • Zálohy nových dat se ZASTAVÍ (sync čte, nezapisuje nová data)
            • Stávající zálohy zůstanou dostupné
            • Firma musí dokoupit prostor nebo smazat staré zálohy

> 5 GB    → pouze po ručním navýšení limitu super adminem

Zobrazení velikosti v portálu

Správa portálu → detail firmy → záložka Přehled:

  Úložiště S3:
  ████████░░░░░░░░░░░░  780 MB / 1 000 MB (78 %)
  [Dokoupit prostor]

  Rozpad dle aplikace:
  Fakturace Pro    420 MB  (zálohy dat + přílohy)
  Účetnictví       280 MB
  Nastavení         80 MB

Datový model — sledování velikosti

company_storage (
  company_id      UUID REFERENCES companies(id) PRIMARY KEY,
  used_bytes      BIGINT DEFAULT 0,
  limit_bytes     BIGINT DEFAULT 1073741824,  -- 1 GB
  soft_limit_bytes BIGINT DEFAULT 5368709120, -- 5 GB (kritické)
  last_calculated_at TIMESTAMPTZ
)

-- Denní agregace per aplikace pro graf v portálu
company_storage_history (
  id          UUID PRIMARY KEY,
  company_id  UUID REFERENCES companies(id),
  app_id      UUID REFERENCES apps(id),
  date        DATE,
  used_bytes  BIGINT,
  PRIMARY KEY (company_id, app_id, date)
)

Velikost se přepočítává při každém uploadu/smazání zálohy a denně jako batch job (S3 ListObjects pro přesné číslo).


S3 struktura — firemní prostor

Data jsou organizována per firma, ne per uživatel — zálohy patří firmě.

s3://avaxis/companies/{company-id}/

├── users/
│   └── {user-id}/
│       ├── license.jwt                     ← offline licence cache
│       └── identity.json                   ← GDPR data (viz sekce GDPR)
├── settings/
│   └── {user-id}/
│       └── {app-slug}/
│           ├── current.snapshot
│           └── history/{ISO-timestamp}.snapshot
└── data/
    └── {app-slug}/
        ├── manifest.json                   ← metadata aktuálního stavu
        ├── latest/                         ← aktuální data (web přístup)
        │   ├── db.sqlite                   ← nebo jiný formát
        │   └── files/                      ← přílohy, doklady
        └── history/
            └── {ISO-timestamp}/            ← point-in-time zálohy
                ├── db.sqlite
                └── files/

GDPR a zákonná archivace — oddělení dat

KLÍČOVÉ ROZHODNUTÍ:
  Klientská data (mzdové listy, faktury, účetnictví) NEJSOU GDPR data
  ve smyslu práva na výmaz — jsou to zákonně archivovaná obchodní data.
  Firmy je musí uchovávat dle zákona (5–10 let dle typu dokladu).

  GDPR se vztahuje POUZE na identifikační data uživatele platformy:
    • Jméno uživatele
    • E-mailová adresa
    • Případně telefon

Tato data jsou uložena ODDĚLENĚ v identity.json:

identity.json — izolovaná GDPR data

{
  "user_id": "usr_abc123",
  "name": "Jan Novák",
  "email": "jan.novak@firma.cz",
  "phone": "+420601234567",
  "created_at": "2024-01-15T08:00:00Z"
}
Uložen v: companies/{company-id}/users/{user-id}/identity.json

Při GDPR anonymizaci (právo na výmaz):
  → identity.json se přepíše na:
    { "user_id": "usr_abc123",
      "name": "Smazaný uživatel",
      "email": "deleted_usr_abc123@avaxis.deleted",
      "anonymized_at": "2026-04-21T10:00:00Z" }
  → Veškerá ostatní data (doklady, zálohy) zůstanou beze změny
    (jsou to firemní, ne osobní data)
  → V aplikaci se uživatelovo jméno nahradí "Smazaný uživatel"

Zákonná archivace klientských dat

Klientská data podléhají zákonné archivaci — firma si nastaví dobu
uchování dle svých zákonných povinností:

  Typ dat            Zákonná povinnost    Doporučené nastavení
  ──────────────────────────────────────────────────────────────
  Mzdové listy       30 let (ZP §305)     30 let
  Účetní doklady     5 let (ZÚ §31)       10 let (s rezervou)
  Daňové doklady     10 let (DŘ §148)     10 let
  Faktury            10 let               10 let

Nastavení per firma per aplikace:
  company_data_retention (
    company_id      UUID,
    app_id          UUID,
    retention_years INTEGER,    -- zákonná povinnost firmy
    updated_by      UUID,
    updated_at      TIMESTAMPTZ
  )

Expirování záloh: po uplynutí retention_years se zálohy NESMAŽOU automaticky.
Označí se jako expired=true a čekají na garbage collector (viz sekce níže).
Firma dostane upozornění 90 dní před expirací.

Backup konfigurace — implicitní záloha

Záloha je zapnuta implicitně pro všechny aplikace. Aplikace nemusí nic konfigurovat — používají S3 komunikaci implementovanou v tomto modulu.

Nastavení v launcheru (Nastavení → Zálohy):

  ☑ Automaticky zálohovat data na server (doporučeno)
    Frekvence: [Při každé změně ▼]

  Aplikace:
    ☑ Fakturace Pro     Poslední záloha: dnes 14:23 (45 MB)
    ☑ Účetnictví        Poslední záloha: dnes 09:01 (32 MB)
    ☐ Sklad             Záloha vypnuta uživatelem

  [Zálohovat nyní] [Správa záloh]

Uživatel NEMŮŽE mazat zálohy přímo — pouze žádat o výmaz přes portál/support. Fyzické mazání provádí výhradně Garbage Collector (viz sekce níže).


Manifest.json — synchronizační metadata

{
  "app": "fakturace-pro",
  "company_id": "comp_xyz",
  "user_id": "usr_abc123",
  "version": 47,
  "last_modified": "2026-04-21T10:30:00Z",
  "device_id": "desktop-win-xyz",
  "files": [
    {
      "path": "db.sqlite",
      "checksum": "sha256:a1b2c3...",
      "size": 2048576
    },
    {
      "path": "files/faktury/",
      "checksum": "sha256:d4e5f6...",
      "size": 10485760
    }
  ],
  "total_size": 12533152
}

Synchronizační protokol

Sync cyklus (desktop → S3 → web)

Aplikace oznámí launcheru změnu dat (app.sync.request() přes SDK):
Sync Agent (součást launcheru):
  1. Načte lokální manifest, vypočítá checksums změněných souborů
  2. Stáhne manifest.json ze S3
  3. Porovná:
     Lokální = Remote  → nic
     Lokální novější   → upload změněných souborů → nový manifest → snapshot
     Remote novější    → stáhni změněné soubory do lokálu
     KONFLIKT          → viz sekce Konflikt

Web aplikace čte z S3/latest/ — vždy aktuální po sync

Detekce a řešení konfliktu

Konflikt: obě strany změnily data od posledního sync

Řešení:
  SQLite DB   → SDK poskytuje merge hook (app implementuje)
                pokud hook chybí → kopie s suffixem .conflict
  Soubory     → zachová obě verze, uživatel rozhodne v aplikaci
  Nastavení   → Remote vítězí (méně kritické)

Upload flow (manifest-based, stejný princip jako app-catalog)

Sync Agent → API: které soubory se změnily?
API → Sync Agent: presigned PUT URL pro každý změněný soubor
Sync Agent → S3: upload přímo (nezatěžuje API)
Sync Agent → API: commit (nový manifest + snapshot)

Zálohy nastavení (zdarma, všechny plány)

Trigger: aplikace zapíše přes app.paths.settings (SDK)
  → Launcher detekuje změnu (file watcher)
  → Komprimuje → settings.snapshot (gzip)
  → Upload: companies/{company-id}/settings/{user-id}/{app-slug}/
  → Rotace: uchováváme posledních 10 snapshotů

Zálohy klientských dat

Automatické zálohy

Frekvence:
  Standard: denně ve 02:00 (lokální čas)
  Premium:  každých 6 hodin

Proces:
  1. SDK zavolá backup hook aplikace (app.sync.request())
  2. Aplikace dokončí neuložené operace
  3. Launcher zkopíruje data z app.paths.data → snapshot
  4. Upload do companies/{company-id}/data/{app-slug}/history/
  5. Zapíše metadata do backup_snapshots

Ruční záloha: uživatel může spustit kdykoli z launcheru

Retention (dle zákonné povinnosti firmy)

Nastaveno per firma per aplikace v company_data_retention
Default: 10 let pro všechna data (bezpečná hodnota)
Upozornění 90 dní před automatickým smazáním

Obnovení zálohy

GET  /backup/{app-slug}/history
     Vrátí: seznam snapshotů (timestamp, velikost, typ)

POST /backup/{app-slug}/restore/{snapshot-id}
     → presigned URL pro stažení
     → launcher zastaví aplikaci, přepíše data, spustí aplikaci
     → před obnovením automaticky záloha aktuálního stavu

Šifrování

Typ dat Šifrování
Přenos TLS 1.3 (HTTPS)
S3 uložení AES-256 SSE-S3 (serverové)
identity.json AES-256 SSE-KMS (vlastní klíč pro GDPR data)
Klientská data (Premium) E2E: klíč odvozený z hesla firmy (PBKDF2-SHA256)

Sync & Backup API

GET  /sync/{app-slug}/manifest
     Vrátí: aktuální manifest.json ze S3

POST /sync/{app-slug}/upload
     Body: { files: [{path, checksum, size}] }
     Vrátí: { presigned_urls: [{path, url, expires_at}] }

POST /sync/{app-slug}/commit
     Body: { manifest: {...} }
     → zapíše manifest, vytvoří snapshot, aktualizuje company_storage

GET  /sync/{app-slug}/download
     Body: { files: [{path}] }
     Vrátí: { presigned_urls: [{path, url}] }

GET  /backup/{app-slug}/history
POST /backup/{app-slug}/restore/{snapshot-id}
POST /backup/{app-slug}/manual
     → spustí ruční zálohu okamžitě

GET  /storage/usage
     Vrátí: { used_bytes, limit_bytes, breakdown_by_app: [...] }

Datový model

backup_snapshots (
  id           UUID PRIMARY KEY,
  company_id   UUID REFERENCES companies(id),
  user_id      UUID REFERENCES users(id),
  app_id       UUID REFERENCES apps(id),
  s3_key       TEXT NOT NULL,
  type         VARCHAR(20),    -- 'settings' | 'data'
  trigger      VARCHAR(20),    -- 'auto' | 'manual' | 'pre-restore'
  size_bytes   BIGINT,
  checksum     VARCHAR(64),
  is_encrypted BOOLEAN,
  created_at   TIMESTAMPTZ
)

sync_state (
  company_id       UUID REFERENCES companies(id),
  user_id          UUID REFERENCES users(id),
  app_id           UUID REFERENCES apps(id),
  device_id        VARCHAR(255),
  manifest_version INTEGER,
  last_synced_at   TIMESTAMPTZ,
  PRIMARY KEY (user_id, app_id, device_id)
)

company_storage (
  company_id       UUID REFERENCES companies(id) PRIMARY KEY,
  used_bytes       BIGINT DEFAULT 0,
  limit_bytes      BIGINT DEFAULT 1073741824,   -- 1 GB
  soft_limit_bytes BIGINT DEFAULT 5368709120,   -- 5 GB
  last_calculated_at TIMESTAMPTZ
)

company_storage_history (
  company_id UUID REFERENCES companies(id),
  app_id     UUID REFERENCES apps(id),
  date       DATE,
  used_bytes BIGINT,
  PRIMARY KEY (company_id, app_id, date)
)

company_data_retention (
  company_id      UUID REFERENCES companies(id),
  app_id          UUID REFERENCES apps(id),
  retention_years INTEGER DEFAULT 10,
  updated_by      UUID REFERENCES users(id),
  updated_at      TIMESTAMPTZ,
  PRIMARY KEY (company_id, app_id)
)

Garbage Collector — bezpečné mazání záloh

Žádná záloha se nikdy nesmaže automaticky bez explicitního schválení. GC je samostatný backend job který pracuje s frontou označených souborů.

Životní cyklus zálohy

active    → záloha platná, přístupná
expired   → retention_years uplynul, označeno systémem
            → záloha stále přístupná, čeká na GC
            → firma upozorněna 90 dní před expirací
pending_delete → super admin nebo firma schválila smazání
               → GC ji zpracuje v nejbližším běhu
deleted   → fyzicky smazáno ze S3, záznam v DB zachován
            (s3_key = NULL, deleted_at = timestamp)

Kdo může označit zálohu ke smazání

Firma (company admin):
  → Může požádat o smazání expired záloh přes portál
  → Žádost jde ke schválení super adminovi Avaxis
  → Důvod: úspora místa, zákonná lhůta uplynula

Super admin (Avaxis):
  → Může schválit žádost firmy
  → Může označit zálohy ke smazání přímo (např. při ukončení smlouvy)
  → Vždy s poznámkou důvodu (audit log)

Systém (automaticky):
  → NIKDY nemaže bez schválení — pouze označí jako expired

GC job — průběh

Spouštění: denně ve 03:00 UTC (mimo pracovní dobu)

1. Načte všechny záznamy se stavem pending_delete
2. Pro každý záznam:
   a. Ověří že záznam má platný důvod a schválení v audit logu
   b. Smaže soubor ze S3 (S3 DeleteObject)
   c. Aktualizuje backup_snapshots: s3_key=NULL, status='deleted', deleted_at=now()
   d. Aktualizuje company_storage (odečte bytes)
   e. Zapíše do audit_log: { action: 'gc_deleted', snapshot_id, size_bytes }
3. Odešle report super adminovi (kolik souborů, kolik MB uvolněno)

Pokud S3 mazání selže:
  → Záznam zůstane pending_delete, zkusí se znovu příští den
  → Po 3 neúspěšných pokusech → alert super adminovi

Datový model — rozšíření backup_snapshots

backup_snapshots (
  id              UUID PRIMARY KEY,
  company_id      UUID REFERENCES companies(id),
  user_id         UUID REFERENCES users(id),
  app_id          UUID REFERENCES apps(id),
  s3_key          TEXT,              -- NULL po smazání
  type            VARCHAR(20),
  trigger         VARCHAR(20),
  size_bytes      BIGINT,
  checksum        VARCHAR(64),
  is_encrypted    BOOLEAN,
  status          VARCHAR(20) DEFAULT 'active',
  -- 'active' | 'expired' | 'pending_delete' | 'deleted'
  expired_at      TIMESTAMPTZ,       -- kdy přešlo do expired
  delete_requested_by UUID REFERENCES users(id),
  delete_approved_by  UUID REFERENCES users(id),
  delete_reason   TEXT,
  deleted_at      TIMESTAMPTZ,
  gc_attempts     INTEGER DEFAULT 0,
  created_at      TIMESTAMPTZ
)

S3 bezpečnostní model — uživatelé nemají delete práva

Uživatelé NIKDY nezískají S3 přístup s právem mazat soubory. Veškerý přístup k S3 probíhá přes presigned URL generované API serverem.

Presigned URL typy které API vydává klientům:
  GET  (čtení)  → stažení zálohy, sync download      ✓
  PUT  (zápis)  → upload zálohy, sync upload          ✓
  DELETE        → NIKDY nevydáváme klientům           ✗

IAM politika pro S3 bucket avaxis:
  Klienti (presigned URL): s3:GetObject, s3:PutObject
  API server:              s3:GetObject, s3:PutObject, s3:ListBucket
  GC job (backend only):   s3:GetObject, s3:PutObject, s3:DeleteObject
  Avaxis admini (konzole): plný přístup (jen interní, ne přes aplikaci)

Výsledek:
  → Uživatel nemůže smazat zálohu ani při kompromitaci svého účtu
  → Zálohy jsou chráněny i před company adminem
  → Jediný způsob mazání je přes schválený GC job

Rozhodnuté otázky

Otázka Rozhodnutí
Úložný limit 1 GB per firma default, dokoupení dostupné
Soft limit 5 GB — zálohy běží, kritické upozornění
Hard limit > 5 GB — zálohy zastaveny, nutné navýšení
Velikost v portálu Zobrazena v detailu firmy + rozpad per aplikace
Backup konfigurace Implicitně zapnuto, nastavení v launcheru per aplikace
Backup API S3 komunikace implementována v sync-backup, sdílena všemi apps
GDPR data Pouze name + email → identity.json, anonymizovatelný odděleně
Klientská data Nejsou GDPR — zákonná archivace (mzdy 30 let, účetnictví 10 let)
Retention zálohy Per firma per aplikace, default 10 let, konfigurovatelné
Automatické mazání ZAKÁZÁNO — zálohy se pouze označí jako expired
Garbage Collector Backend job, denně 03:00 UTC, maže jen pending_delete
Schválení mazání Firma žádá → super admin schvaluje → GC provede
S3 delete práva Klienti NIKDY — pouze GC job (IAM politika)
Presigned URL Pouze GET + PUT — žádné DELETE pro klienty
Upozornění před expirací 90 dní předem
Šifrování identity.json SSE-KMS (vlastní klíč) — odděleno od ostatních dat

Otevřené otázky

Všechny otázky k sync-backup.md jsou vyřešeny.


Poslední aktualizace: 2026-04-21