Přeskočit obsah

Peer sdílení adresářů — Specifikace

Navazuje na: s3backup.md (sync dirs, devices), user-data-sharing.md (ACL model)

Tato funkce umožňuje uživateli sdílet vlastní sync adresář přímo s jinými uživateli (peer-to-peer) — nezávisle na company_admin.


1. Přehled funkce

Vlastník vybere svůj synchronizovaný adresář a sdílí ho s jedním nebo více uživateli. Příjemce dostane oznámení, přijme sdílení a nastaví lokální cestu, kam se obsah bude synchronizovat.

Oprávnění jsou dvojí: - Číst — příjemce stahuje soubory; lokální úpravy se nikam neposílají - Zapisovat — příjemce může stahovat i nahrávat; majitel při svém syncu stáhne nové/změněné soubory příjemce

Data fyzicky leží v S3 bucketu vlastníka. Příjemce přistupuje přes presigned URL generované backendem — nekopíruje data do vlastního bucketu.


2. Datový model

-- Sdílení sync adresáře (peer-to-peer)
CREATE TABLE sync_dir_shares (
  id                UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  sync_dir_id       UUID REFERENCES user_sync_dirs(id) ON DELETE CASCADE,
  owner_id          UUID REFERENCES users(id) NOT NULL,
  recipient_id      UUID REFERENCES users(id) NOT NULL,
  permission        VARCHAR(10) NOT NULL CHECK (permission IN ('read', 'write')),
  status            VARCHAR(10) NOT NULL DEFAULT 'pending'
                      CHECK (status IN ('pending', 'accepted', 'declined')),
  cross_company_ack BOOLEAN NOT NULL DEFAULT false,  -- vlastník potvrdil warning
  local_path        TEXT,                             -- cesta na disku příjemce
  created_at        TIMESTAMPTZ NOT NULL DEFAULT now(),
  accepted_at       TIMESTAMPTZ,
  UNIQUE (sync_dir_id, recipient_id)
);

CREATE INDEX idx_shares_owner     ON sync_dir_shares(owner_id);
CREATE INDEX idx_shares_recipient ON sync_dir_shares(recipient_id);
CREATE INDEX idx_shares_dir       ON sync_dir_shares(sync_dir_id);

3. API endpointy

3.1 Vlastník — správa sdílení

POST   /storage/shares
  body: { sync_dir_id, recipient_email, permission: "read"|"write",
          cross_company_ack: bool }
  → 201 ShareResponse
  Vytvoří záznam status=pending, odešle notifikaci příjemci.
  Vrátí 409 pokud share pro (sync_dir_id, recipient_id) již existuje.
  Vrátí 422 pokud vlastník sdílí mimo firmu a cross_company_ack=false.

GET    /storage/shares/sent
  → [ ShareResponse ]
  Všechna sdílení která vlastník vytvořil (všechny statusy).

DELETE /storage/shares/{share_id}
  → 204
  Vlastník odebere sdílení (jakýkoliv status). Příjemce ztratí přístup.

PUT    /storage/shares/{share_id}
  body: { permission: "read"|"write" }
  → ShareResponse
  Vlastník změní oprávnění existujícího sdílení.

3.2 Příjemce — přijmutí / odmítnutí

GET    /storage/shares/received
  → [ ShareResponse ]
  Sdílení určená příjemci (pending i accepted).

POST   /storage/shares/{share_id}/accept
  body: { local_path: "/cesta/na/disku" }
  → ShareResponse (status=accepted)
  Příjemce přijme sdílení a nastaví lokální cestu.

POST   /storage/shares/{share_id}/decline
  → 204
  Příjemce odmítne nebo odebere přijaté sdílení.

3.3 Prohlížení a sync

GET    /storage/shares/{share_id}/files?path=/subdir
  → { path, entries: [{ name, type: "file"|"dir", size, modified }] }
  Výpis obsahu sdíleného adresáře (S3 listing přes backend).

GET    /storage/shares/{share_id}/presigned
  query: path=/subdir/soubor.txt, action=get|put
  → { url: "https://s3.avaxis.cz/...", expires_in: 3600 }
  Backend ověří oprávnění a vrátí presigned URL přímo na S3 vlastníka.
  action=put: vrátí 403 pokud permission=read.

3.4 ShareResponse schema

{
  "id": "uuid",
  "sync_dir_id": "uuid",
  "sync_dir_path": "/lokální/cesta/vlastníka",
  "owner_id": "uuid",
  "owner_name": "Jan Novák",
  "owner_company": "Demo s.r.o.",
  "owner_email": "jan@demo.cz",
  "recipient_id": "uuid",
  "recipient_name": "...",
  "recipient_email": "...",
  "permission": "read",
  "status": "pending",
  "local_path": null,
  "created_at": "2026-05-01T10:00:00Z",
  "accepted_at": null
}

4. S3 přístupový model

Sdílená data leží v cestě vlastníka:

s3://claudeai/users/{owner_id}/sync/{sync_dir_id}/podadresar/soubor.txt

Příjemce nikdy nepřistupuje přímo — vždy přes backend presigned URL: - Download (read + write): GET /storage/shares/{id}/presigned?action=get&path=... → presigned GET URL platná 1 hodinu - Upload (write only): GET /storage/shares/{id}/presigned?action=put&path=... → presigned PUT URL platná 1 hodinu

Backend před vygenerováním URL vždy ověří: 1. Příjemce je v záznamu sync_dir_shares 2. Status = accepted 3. Pro PUT: permission = write


5. Synchronizace — chování

Příjemce (read)

Pro každý soubor v S3 (rekurzivně):
  s3_mtime = metadata mtime (nebo S3 LastModified)
  man_s3   = manifest["s3_modified"] pro tento soubor

  if man_s3 is None OR s3_mtime > man_s3 + tolerance:
    stáhni soubor přes presigned GET URL
    obnov mtime na disku (os.utime)
    ulož manifest { mtime: local, s3_modified: s3_mtime }

Lokální změny: ignorovány, nikdy se nenahrají

Příjemce (write)

Download: stejný jako read výše

Upload — pro každý lokální soubor:
  local_mtime = os.path.getmtime(soubor)
  man_mtime   = manifest["mtime"]

  if local_mtime > man_mtime + tolerance:
    získej presigned PUT URL
    nahraj soubor na S3 vlastníka
    ulož do S3 metadata: { mtime: str(local_mtime) }
    aktualizuj manifest { mtime: local_mtime, s3_modified: time.time() }

Vlastník

Vlastník synchronizuje standardně — sdílená složka je jeho běžný sync dir. Pokud příjemce (write) nahrál nový soubor, vlastník ho stáhne protože s3_mtime > man_s3 + tolerance.

Priorita při konfliktu (write)

Pokud oba (vlastník i příjemce) změní stejný soubor → server wins: vyhraje ten, kdo uploadoval jako první. Druhý uvidí nový soubor při příštím stažení a jeho lokální verze bude přepsána.

Budoucí rozšíření: conflict dialog (v scope není).


6. Launcher UI

6.1 BackupScreen — nová sekce „Moje sdílení"

┌─────────────────────────────────────────────────────┐
│ Moje sdílení                          [+ Sdílení]   │
├─────────────────────────────────────────────────────┤
│ 📁 C:\Users\Jan\Faktury                              │
│    👤 marie@demo.cz       📖 Číst                   │
│    👤 petr@demo.cz        ✏️ Zapisovat  [Nastavení] │
├─────────────────────────────────────────────────────┤
│ 📁 C:\Users\Jan\Projekty                            │
│    👤 jana@demo.cz        📖 Číst       [Nastavení] │
└─────────────────────────────────────────────────────┘

Tlačítko [Nastavení] otevře dialog správy příjemců pro daný adresář.

6.2 Dialog „+ Sdílení" (vlastník vytváří)

┌─────────────────────────────────────────────────────┐
│ Nové sdílení                                    [×] │
├─────────────────────────────────────────────────────┤
│ Adresář:                                            │
│ [▼ C:\Users\Jan\Faktury              ]              │
│                                                     │
│ Přidat uživatele:                                   │
│ [marie@demo.cz        ] [🔍]  ○ Číst  ○ Zapisovat  │
│                               [+ Přidat]            │
│ ─────────────────────────────────────────────────── │
│ Přidaní uživatelé:                                  │
│  Marie Nováková (Demo s.r.o.)  📖 Číst       [✕]  │
│  Petr Svoboda   (Demo s.r.o.)  ✏️ Zapisovat  [✕]  │
│ ─────────────────────────────────────────────────── │
│              [Zrušit]      [Sdílet]                 │
└─────────────────────────────────────────────────────┘
  • Vyhledávání uživatele: autocomplete přes /auth/users/search?q=email
  • Výběr adresáře: pouze z vlastních sync dirs tohoto zařízení

6.3 Varování cross-company

Pokud příjemce patří do jiné firmy než vlastník:

┌─────────────────────────────────────────────────────┐
│ ⚠  Sdílení mimo firmu                               │
│                                                     │
│ Sdílíte adresář s uživatelem:                       │
│   Michal Plavecký (AVAXIS.CZ)                       │
│   plavecky.michal@gmail.com                         │
│                                                     │
│ Data budou přístupná mimo vaši firmu Demo s.r.o.    │
│                                                     │
│       [Zrušit]    [OK, beru na vědomí]              │
└─────────────────────────────────────────────────────┘

6.4 Sekce „Sdíleno se mnou" — rozšíření

Existující sekce se rozšíří o pending notifikace (zobrazeny nahoře, zvýrazněny) a detail přijatého sdílení.

Pending pozvánka:

┌─────────────────────────────────────────────────────┐
│ 🔔 Nové sdílení                                     │
│                                                     │
│   Jan Novák  (Demo s.r.o.)                          │
│   jan@demo.cz                                       │
│   sdílí s vámi adresář: „Faktury"                   │
│   Oprávnění: 📖 Pouze čtení                         │
│                                                     │
│       [Odmítnout]      [Přijmout]                   │
└─────────────────────────────────────────────────────┘

Klik [Přijmout] → dialog výběru lokální složky:

┌─────────────────────────────────────────────────────┐
│ Kam uložit sdílenou složku?                    [×]  │
│                                                     │
│ Lokální cesta:                                      │
│ [C:\Users\Marie\Sdilene\Faktury     ] [📂 Vybrat]  │
│                                                     │
│                   [Zrušit]  [Přijmout a synchron.] │
└─────────────────────────────────────────────────────┘

Přijatá sdílení (detail položky):

┌─────────────────────────────────────────────────────┐
│ 📁 Faktury (Jan Novák, Demo s.r.o.)   📖 Číst       │
│    Lokální: C:\Users\Marie\Sdilene\Faktury          │
│                                                     │
│  [🔍 Procházet]  [↓ Synchronizovat]  [⚙ Nastavení] │
└─────────────────────────────────────────────────────┘

6.5 Prohlížeč souborů (browse)

Klik [🔍 Procházet] otevře panel s adresářovou strukturou:

📁 Faktury/
  ├─ 📁 2025/
  │    ├─ 📄 faktura_001.pdf   (128 KB, 15.3.2025)
  │    └─ 📄 faktura_002.pdf   ( 95 KB, 20.3.2025)
  └─ 📄 prehled_2024.xlsx      (256 KB,  2.1.2025)
  • Klik na soubor → download (presigned GET URL)
  • Klik na složku → rozbalí podadresář
  • Breadcrumb navigace

6.6 Sync nastavení pro příjemce

Pod detailem sdílení přes [⚙ Nastavení]: - Lokální cesta (změna) - Plán synchronizace: ○ Pouze ručně ○ V určené časy [09:00] [12:00] [17:00] - Tlačítko „Synchronizovat nyní" (dostupné i přímo v listu) - Tlačítko „Odebrat sdílení" (příjemce odmítne/odpojí)


7. Notifikace

7.1 In-app

Launcher zobrazí badge na „Zálohy" v menu pokud jsou pending pozvánky. Sekce „Sdíleno se mnou" je automaticky první při otevření BackupScreen, pokud jsou pending pozvánky.

7.2 E-mail (backend)

Při vytvoření sdílení backend pošle e-mail příjemci:

Předmět: Jan Novák sdílí s vámi složku „Faktury"

Jan Novák (Demo s.r.o.) sdílí s vámi adresář prostřednictvím AVAX.
Oprávnění: Pouze čtení

Přijmout sdílení: otevřete AVAX Launcher → Zálohy → Sdíleno se mnou.


8. Bezpečnostní pravidla

Akce Podmínka
Vytvořit share Vlastník musí být majitel sync_dir_id
Presigned GET status=accepted, příjemce je recipient_id
Presigned PUT status=accepted, permission=write, příjemce je recipient_id
Smazat share owner_id = current_user NEBO recipient_id = current_user
Změnit permission owner_id = current_user
Browse files status=accepted, recipient_id = current_user

9. Fáze implementace

Fáze Obsah
FS1 DB migrace, backend CRUD endpointy, e-mail notifikace
FS2 Launcher: sekce „Moje sdílení" + dialog vytvoření
FS3 Launcher: pending notifikace + accept dialog
FS4 Launcher: sync agent rozšíření (read + write pro příjemce)
FS5 Launcher: prohlížeč souborů, sync nastavení příjemce

10. Rozhodnutá nastavení

# Otázka Rozhodnutí
1 Hledání uživatele Zobraz seznam uživatelů vlastní firmy s fulltext hledáním. Pokud zadáš přesný e-mail mimo firmu, backend ověří zda uživatel existuje na platformě (cross-platform check).
2 Zaniklý sync dir Sdílení a sync dir jsou nezávislé — odebrání sdílení neodstraní sync dir ze záloh vlastníka. Při odebírání sync diru který má aktivní shares: varování „Sdílíte tento adresář s X uživateli — odebráním ztratí přístup" + potvrzení. Poté se shares smažou (CASCADE).
3 Kvóta Počítá se pouze vlastníkovi — data leží v jeho bucketu, příjemce kvótu nespotřebovává.
4 Max příjemců Neomezeno.

11. Podadresáře a struktura

Záloha zachovává kompletní adresářovou strukturu včetně všech podadresářů. Toto platí pro vlastní zálohy i sdílené adresáře.

Implementace v sync_agent.py již toto řeší: - Lokální sken: os.walk(local_path) → rekurzivní průchod všemi podadresáři - S3 upload: klíč = {prefix}{rel_path} kde rel_path zachovává podadresáře (podadr/soubor.txt) - S3 download: list_objects_v2 bez Delimiter → vrátí všechny objekty rekurzivně - Prohlížeč souborů zobrazuje strom adresářů dle S3 prefixů

Pro sdílené adresáře platí totéž — příjemce vidí a synchronizuje kompletní strukturu.