Implementační plán — referenční dokument pro Claude¶
Tento soubor je hustý přehled co, kde a v jakém pořadí implementovat.
Načti ho místo všech spec souborů když potřebuješ kontext pro implementaci.
Detaily vždy v příslušném docs/spec/*.md.
Repozitářová struktura¶
avax-platform/
├── CLAUDE.md ← vždy načíst jako první
├── docs/spec/ ← specifikace (načíst jen relevantní)
├── backend/ ← FastAPI (Python 3.12)
│ ├── app/
│ │ ├── main.py
│ │ ├── core/ (config, db, redis, s3, security)
│ │ ├── models/ (SQLAlchemy ORM modely)
│ │ ├── schemas/ (Pydantic schémata)
│ │ ├── routers/ (auth, org, catalog, sync, chat, support, admin)
│ │ ├── services/ (business logika)
│ │ ├── workers/ (Celery tasky)
│ │ └── migrations/ (Alembic)
│ ├── tests/
│ └── pyproject.toml
├── frontend/ ← Next.js 15 (web + admin portál)
├── desktop/
│ └── launcher/ ← egui 0.31 launcher (Rust, nativní GPU)
├── sdk/
│ ├── python/ ← avaxis-sdk-python
│ ├── dotnet/ ← avaxis-sdk-dotnet
│ └── java/ ← avaxis-sdk-java
├── ai-service/ ← FastAPI mikroslužba na vm-ai
└── infrastructure/
├── nginx/ ← nginx.conf per subdoména
├── docker/ ← docker-compose.dev.yml
└── scripts/ ← VM setup skripty
Databázové tabulky — pořadí vytvoření (migrace)¶
Migrace 001 — základ:
companies, users, audit_log, gdpr_consents
Migrace 002 — auth:
company_roles, user_company_roles, avaxis_user_roles
tfa_qr_sessions, tfa_backup_codes, contact_tokens, contact_requests, contacts
Migrace 003 — katalog:
apps, app_versions, app_version_files
company_subscriptions, subscription_history, company_channel_assignments
app_installs
Migrace 004 — zálohy a storage:
backup_snapshots, sync_state, company_storage, company_storage_history
company_data_retention, company_trial_config
Migrace 005 — chat:
conversations, conversation_members, messages
message_reads, message_reactions, message_attachments
Migrace 006 — support:
support_tickets, support_agents, ticket_notes, ticket_transfers
kb_articles (s pgvector column: embedding vector(768))
Migrace 007 — portál:
company_registrations, platform_settings, portal_notifications
gdpr_requests
Migrace 008 — login history (partitioned):
login_history PARTITION BY RANGE (created_at)
API endpointy — přehled per modul¶
/auth¶
POST /auth/login → { access_token, refresh_token, offline_jwt }
POST /auth/refresh → { access_token, refresh_token }
POST /auth/logout
POST /auth/register/company → vytvoří firmu + company_admin
POST /auth/register/user → přidá uživatele do existující firmy (dle IČO)
POST /auth/password-reset/request
POST /auth/password-reset/confirm
GET /auth/license → offline JWT pro aktuální device
POST /auth/2fa/setup → { qr_uri, backup_codes }
POST /auth/2fa/verify → ověří code před aktivací
POST /auth/2fa/disable → pouze support_agent nebo super_admin
/org¶
GET /org/companies/{id}
PUT /org/companies/{id}
GET /org/companies/{id}/users
POST /org/companies/{id}/users
PUT /org/users/{id}
DELETE /org/users/{id} → soft delete (anonymizace)
GET /org/roles
POST /org/roles
POST /org/roles/{id}/assign
POST /org/apps/{app_id}/assign → přidělení aplikace roli nebo uživateli
DELETE /org/apps/{app_id}/assign/{target_id}
/catalog¶
GET /catalog/apps → launcher endpoint (verze + size_mb per app) ← PŘIDÁNO F2
GET /catalog ?company_id, ?user_id
GET /catalog/{slug}
GET /catalog/{slug}/versions
GET /catalog/{slug}/check-update ?installed_version, ?platform
POST /apps/{slug}/file-urls → presigned GET URLs pro změněné soubory
POST /apps/{slug}/install-report
POST /apps/{slug}/log-upload-url
/sync + /backup¶
GET /sync/{slug}/manifest
POST /sync/{slug}/upload → presigned PUT URLs
POST /sync/{slug}/commit
GET /sync/{slug}/download
GET /backup/{slug}/history
POST /backup/{slug}/restore/{snapshot_id}
POST /backup/{slug}/manual
GET /storage/usage
/chat¶
GET /conversations
GET /conversations/{id}/messages ?before, ?limit=50
POST /conversations
POST /conversations/{id}/invite
POST /conversations/{id}/leave
DELETE /conversations/{id}/members/{user_id}
POST /conversations/{id}/messages
PUT /conversations/{id}/messages/{msg_id}
DELETE /conversations/{id}/messages/{msg_id}
POST /conversations/{id}/messages/{msg_id}/reactions { emoji }
PUT /conversations/{id}/mode { mode: open|moderated|questions|announcement }
PUT /conversations/{id}/messages/{msg_id}/hide { hidden: bool }
PUT /conversations/{id}/messages/{msg_id}/approve
POST /conversations/{id}/export
POST /chat/upload-url → presigned PUT pro přílohu
PUT /conversations/{id}/members/me/notifications { pref }
POST /contacts/token
POST /contacts/connect { token }
PUT /contacts/requests/{id} { action: accept|reject }
/support¶
GET /support/queue
GET /support/tickets ?status, ?agent_id
GET /support/tickets/{id}
POST /support/tickets/{id}/assign
POST /support/tickets/{id}/priority
POST /support/tickets/{id}/status { status, message_to_customer }
POST /support/tickets/{id}/transfer { to_agent_id, note }
POST /support/tickets/{id}/notes { content }
GET /support/tickets/{id}/ai-draft
POST /support/tickets/{id}/csat { score: 1-5, comment }
PUT /support/agents/me/availability { is_available }
GET /support/stats
GET /kb/articles ?q, ?category, ?app
POST /kb/articles
PUT /kb/articles/{id}
POST /kb/articles/{id}/helpful { helpful: bool }
/admin (portal)¶
GET /admin/registrations ?status=pending
PUT /admin/registrations/{id} { action: approve|reject, note }
GET /admin/companies ?q, ?status, ?plan
GET /admin/companies/{id}
PUT /admin/companies/{id}/plan
POST /admin/companies/{id}/trial { days }
GET /admin/companies/{id}/storage
GET /admin/users ?q, ?company_id, ?status
POST /admin/users/{id}/impersonate
POST /admin/users/{id}/reset-password
PUT /admin/users/{id}/2fa-disable
GET /admin/apps
POST /admin/apps
POST /admin/apps/{slug}/versions
PUT /admin/apps/{slug}/versions/{ver}/promote { to_channel }
PUT /admin/apps/{slug}/rollback
GET /admin/apps/{slug}/logs
GET /admin/settings
PUT /admin/settings { key, value }
GET /admin/stats
/webhooks¶
WebSocket protokol¶
# Připojení
wss://ws.avaxis.cz?token={access_token}
# Klient → server
{ "type": "subscribe", "conversations": ["conv_id1", "conv_id2"] }
{ "type": "unsubscribe", "conversations": ["conv_id1"] }
{ "type": "typing", "conv_id": "...", "is_typing": true }
# Server → klient
{ "type": "message", "data": { ...message } }
{ "type": "reaction", "data": { message_id, emoji, action, count } }
{ "type": "typing", "data": { conv_id, user_id, is_typing } }
{ "type": "member", "data": { conv_id, action, user_id } }
{ "type": "hidden", "data": { conv_id, message_id } } ← moderace
{ "type": "approved", "data": { conv_id, message_id } } ← pre-moderace
{ "type": "update_available", "data": { app, version, changelog } }
{ "type": "support_online", "data": { is_online } }
Celery fronty a tasky¶
# Fronta: default
send_email(template, to, context)
send_push_notification(user_id, title, body)
calculate_company_storage(company_id)
embed_kb_article(article_id)
# Fronta: ai
generate_support_draft(ticket_id)
generate_offline_response(ticket_id)
embed_resolved_ticket(ticket_id)
# Fronta: gc
mark_expired_backups() # označí backup_snapshots expired
process_gc_queue() # physical delete pending_delete ze S3
archive_old_chat_messages() # starší než retention → expired
cleanup_app_logs() # starší než retention → smazat ze S3
# Celery Beat schedule (crontab):
"mark_expired_backups": crontab(hour=1, minute=0)
"process_gc_queue": crontab(hour=3, minute=0)
"calculate_storage_all": crontab(hour=4, minute=0)
"check_expiring_subs": crontab(minute="*/60")
"premium_backup": crontab(minute=0) # každou hodinu
"standard_backup": crontab(hour=2, minute=0)
"weekly_storage_alert": crontab(day_of_week=1, hour=9)
AI Service (vm-ai:9000)¶
POST /ai/support-draft
Vstup: { ticket_id, customer_message, priority }
Logika: embed → pgvector top-5 → if priority<=2 or conf<0.7: Claude else: Ollama GPU
Výstup: { draft, model_used, confidence, sources: [{type, id, title}] }
POST /ai/offline-response
Vstup: { ticket_id, customer_message }
Logika: embed → pgvector → Ollama GPU (rychlost) → Claude fallback
Výstup: { response, model_used }
POST /ai/embed
Vstup: { text }
Výstup: { embedding: [float * 768] } ← Ollama nomic-embed-text
POST /ai/search
Vstup: { query, limit, document_type }
Výstup: { results: [{ id, type, title, score }] }
AVAX App SDK — povinné rozhraní¶
# Minimální implementace (Python příklad)
from avaxis_sdk import AvaxApp
app = AvaxApp(slug="moje-aplikace")
app.connect() # připojí Named pipe / Unix socket k launcheru
app.ready() # launcher ví že app startovala
app.paths.data # %APPDATA%/Avaxis/apps/{slug}/data/
app.paths.settings # %APPDATA%/Avaxis/apps/{slug}/settings/
app.log.error(msg, exception=None, context={})
app.log.warn(msg)
app.log.info(msg)
app.sync.request() # požádá launcher o okamžitý sync
@app.on_shutdown
def handle_shutdown():
# uložit stav
app.shutdown_ok()
@app.on_update_pending
def handle_update(version):
app.update_ok() # nebo app.update_defer()
IPC zprávy launcher → app:
{ "type": "shutdown", "reason": "user_request"|"update_ready" }
{ "type": "update_pending", "version": "2.1.4" }
{ "type": "sync_complete", "status": "ok" }
IPC zprávy app → launcher:
{ "type": "ready", "version": "2.1.3", "pid": 1234 }
{ "type": "heartbeat" }
{ "type": "shutdown_ok" }
{ "type": "update_ok" }
{ "type": "update_defer" }
{ "type": "sync_request" }
{ "type": "log", "level": "ERROR", "message": "...", "stack": "..." }
Manifest soubory (S3)¶
App version manifest¶
{
"app": "fakturace-pro",
"version": "2.1.4",
"channel": "stable",
"released_at": "ISO timestamp",
"changelog": "Markdown text nebo null",
"files": [
{ "path": "app.exe", "checksum": "sha256:...", "size": 5242880, "platform": "win" },
{ "path": "assets/logo.png", "checksum": "sha256:...", "size": 51200, "platform": "any" }
]
}
Sync manifest (S3: companies/{id}/data/{slug}/manifest.json)¶
{
"app": "fakturace-pro",
"company_id": "uuid",
"user_id": "uuid",
"version": 47,
"last_modified": "ISO timestamp",
"device_id": "string",
"files": [
{ "path": "db.sqlite", "checksum": "sha256:...", "size": 2048576 }
]
}
Klíčové konstanty a konfigurace¶
# JWT
ACCESS_TOKEN_TTL = 15 * 60 # 15 minut
REFRESH_TOKEN_TTL = 30 * 24 * 3600 # 30 dní
OFFLINE_JWT_TTL = 30 * 24 * 3600 # 30 dní
OFFLINE_GRACE = 7 * 24 * 3600 # 7 dní grace po expiraci
# Storage
COMPANY_STORAGE_DEFAULT = 1 * 1024**3 # 1 GB
COMPANY_STORAGE_SOFT = 5 * 1024**3 # 5 GB (kritické upozornění)
# Update notifikace
UPDATE_TRAY_INTERVAL = 4 * 3600 # každé 4 hodiny
# Zálohy
SETTINGS_SNAPSHOT_COUNT = 10 # posledních 10 snapshotů nastavení
DEFAULT_TRIAL_DAYS = 7
DEFAULT_GRACE_DAYS = 7
DEFAULT_RETENTION_YEARS = 10
# Chat
LARGE_GROUP_THRESHOLD = 20 # >= 20 členů → seen_count místo per-user
CHAT_ATTACHMENT_MAX = 50 * 1024**2 # 50 MB
# Support SLA (minuty)
SLA_FIRST_RESPONSE = { 1: 15, 2: 60, 3: 240, 4: 1440 }
# AI
AI_CONFIDENCE_THRESHOLD = 0.7 # pod tímto → Claude místo Ollama
AI_LOCAL_HOST = "http://10.0.1.6:11434" # vm-gpu Ollama
AI_SERVICE_HOST = "http://10.0.1.5:9000" # vm-ai
F3 Desktop Launcher — implementační přehled¶
Stack¶
Jazyk: Rust (stable)
GUI: egui 0.31 + eframe (nativní GPU renderer, bez WebView)
Renderer: wgpu (DirectX 12 / Vulkan / Metal)
Async: tokio (runtime v separátním vláknu)
HTTP: reqwest (rustls-tls)
Keyring: keyring (bezpečné uložení refresh_token)
Build: cargo-xwin (cross-compile Windows exe na Linux)
Soubory¶
desktop/launcher/src/
├── main.rs — NativeOptions, perzistence nastavení (eframe storage), build_icon
├── app.rs — AvaxApp struct, draw(), všechny draw_* metody
├── theme.rs — AppTheme enum, Palette, FontSizes, AppSettings, apply()
├── types.rs — Screen enum, Session, AppInfo, AppEvent, InstallState
├── api.rs — api::get(), api::post() přes reqwest
└── installer.rs — install(), launch(), uninstall(), installed_version()
Screen enum (všechny obrazovky)¶
Login, Novinky, Blog,
Knihovna, Obchod,
Chat,
Podpora, Gdpr, Smlouvy, OSystemu, OKlientu,
Aktualizace, Nastaveni,
MujProfil, DetailUctu, Predvolby, MojePredplatne
Horní menu lišta (MENUBAR_H = 46px)¶
- Všechna tlačítka stejný flat styl (nav_btn) — konzistentní bez ohledu na dropdown
- Dropdown = popup_below_widget s ui.memory_mut(|m| m.toggle_popup(id))
- Window drag: Sense::click_and_drag() + ViewportCommand::StartDrag
Témata (AppTheme)¶
| Téma | Pozadí | Akcent |
|---|---|---|
| DarkBlue | #0d131f | #3d8bff |
| Dark | #111111 | #888888 |
| Green | #0a140a | #2ecc71 |
| Light | #d8d8e8 | #2050cc |
| Black | #000000 | #505050 |
| Pink | #1f0a14 | #ff3d9a |
Každé téma vrací Palette struct — barvy použity v painter calls přes self.palette.*.
Egui widgets získávají barvy z apply(ctx, theme, font_sizes) → ctx.set_style(style).
FontSizes (persistováno)¶
menubar: f32 // horní lišta
dropdown: f32 // dropdown položky (= TextStyle::Button)
subnav: f32 // lišta 2 (sub-navigace)
body: f32 // tělo textu
heading: f32 // nadpisy stránek
small: f32 // metadata, popisy
Persistence (eframe storage)¶
// main.rs — ukládání
fn save(&mut self, storage: &mut dyn eframe::Storage) {
eframe::set_value(storage, "avax_settings", self.inner.get_settings());
}
// Načtení při startu
let settings = cc.storage
.and_then(|s| eframe::get_value(s, "avax_settings"))
.unwrap_or_default();
%APPDATA%\avax launcher\data\ (Windows)
Login flow¶
POST /auth/login → { access_token, refresh_token, offline_jwt }
GET /auth/me → { email, name, company_name, system_role }
GET /catalog/apps (po login) → Vec<AppInfo>
WDAC poznámka (Windows Application Control)¶
Exe zkompilovaný bez code-signing je blokován WDAC na firemních strojích.
Spouštět přes Claude Code terminál (!) nebo zakoupit OV certifikát před produkcí.
Fáze implementace — závislosti¶
F0 Příprava infrastruktury
└─► F1 Backend základ (auth, org)
├─► F2 Katalog + distribuce
│ └─► F3 Desktop launcher
├─► F4 Chat systém
│ └─► F5 Support + AI (závisí na F4 pro support kanál)
├─► F6 Správa portálu (závisí na F1+F2)
└─► F7 Web + nasazení (závisí na F1–F6)
F3 a F4 lze vyvíjet paralelně po dokončení F1+F2. F5 a F6 lze vyvíjet paralelně po dokončení F4.
Env proměnné (přehled)¶
# Databáze
DATABASE_URL=postgresql+asyncpg://avaxis:pass@10.0.1.3/avaxis_prod
REDIS_URL=redis://10.0.1.3:6379/0
# S3
S3_ENDPOINT=https://s3.avaxis.cz
S3_BUCKET=avaxis
S3_API_ACCESS_KEY=... # GET + PUT + List
S3_API_SECRET_KEY=...
S3_GC_ACCESS_KEY=... # + DELETE (jen pro GC worker)
S3_GC_SECRET_KEY=...
# JWT (RS256)
JWT_PRIVATE_KEY_PATH=/etc/avaxis/jwt_private.pem
JWT_PUBLIC_KEY_PATH=/app/jwt_public.pem # bundlován v launcheru
# AI
ANTHROPIC_API_KEY=...
AI_SERVICE_URL=http://10.0.1.5:9000
OLLAMA_URL=http://10.0.1.6:11434
# E-mail
SMTP_HOST=mail.avaxis.cz
SMTP_PORT=587
SMTP_USER=noreply@avaxis.cz
SMTP_PASSWORD=...
# Seed (pouze při prvním nasazení)
AVAXIS_COMPANY_ICO=...
SUPERADMIN_EMAIL=...
SUPERADMIN_PASSWORD=...
Načti tento soubor na začátku implementační session místo všech spec souborů. Pro detaily konkrétní komponenty pak načti příslušný docs/spec/.md.*