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:
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.