# Návrh webové platformy — Martin Reiner, zbraně a střelivo > Pracovní návrh vývojové a produkční platformy. Stav: před scaffoldem. > Datum: 2026-07-30 > Revize: databáze změněna z PostgreSQL na SQLite po upřesnění rozsahu (viz [2. Volba stacku](#2-volba-stacku)). > Revize: deploy přepsán na Gitea Actions + Gitea registry a sjednocen se zavedeným postupem z [Dashboardu](../Dashboard/) — pojmenování repa, rozdělení compose souborů i podoba CI (viz [4.](#4-pojmenování-repozitáře) a [7.](#7-deploy)). > **Stav: naskafoldováno.** Projekt existuje, lint/testy/build i produkční image ověřeny. Produkční větev je `release` (výchozí větev repa na Giteji), ne `main`. ## Obsah - [1. Zadání](#1-zadání) - [2. Volba stacku](#2-volba-stacku) - [3. Topologie](#3-topologie) - [4. Pojmenování repozitáře](#4-pojmenování-repozitáře) - [5. Struktura repa](#5-struktura-repa) - [6. Konfigurační soubory](#6-konfigurační-soubory) - [7. Deploy](#7-deploy) - [8. Zálohy](#8-zálohy) - [9. Cesta dál](#9-cesta-dál) - [10. Otevřené otázky](#10-otevřené-otázky) --- ## 1. Zadání | Parametr | Hodnota | |---|---| | Typ webu | Prezentace + jednoduchá administrace obsahu (CMS) | | Obsah | Položky zboží: obrázek/obrázky, pár parametrů, cena. Nic víc. | | Administrace | Přihlášení heslem přímo na webu, přidat / upravit / odebrat položku. Žádné zákaznické účty, žádný košík. | | Produkce | Stávající server s Dockerem v domácí síti (tentýž, kde běží Dashboard) | | HTTPS | Stávající domácí Caddy, veřejná IP, Let's Encrypt | | Jazyk | TypeScript / JavaScript | | K dispozici | Gitea (vlastní), Gitea Actions, server s Dockerem, Caddy | | Požadavek | Oddělené dev a prod prostředí, vývoj na notebooku, nasazení na jiném stroji | --- ## 2. Volba stacku | Volba | Důvod | |---|---| | **Payload CMS 3** | Admin rozhraní i veřejný web jsou **jedna Next.js aplikace**, ne dvě služby. Schéma obsahu se píše v TypeScriptu → typy pro frontend zdarma. Odpadá oddělený headless CMS. | | **Next.js** | Součást Payloadu 3, není to volba navíc. | | **SQLite** (`@payloadcms/db-sqlite`) | Payload bez databáze neběží, ale na tenhle rozsah stačí soubor. Jeden editor, žádné souběžné zápisy, desítky až stovky položek — Postgres by tu neřešil nic, jen přidal službu, heslo, healthcheck a druhý bod selhání. | | **Docker Compose** | `docker-compose.yml` pro vývoj, `docker-compose.prod.yml` pro nasazení — stejné rozdělení jako u Dashboardu. Prostředí (prod / staging) se liší jen `.env`. | | **Gitea Actions** | CI už je k dispozici, takže image staví runner, ne produkční server. Server pak jen `docker compose pull` — deploy trvá sekundy místo minut a produkce nikdy nekompiluje. | | **Gitea container registry** | Vestavěná součást Gitey, netřeba Harbor ani GHCR. Běží za stejným Caddy, takže má platný certifikát a odpadá otravování s `insecure-registries`. | Zvažované a zamítnuté varianty: - **PostgreSQL** — v první verzi návrhu. Zamítnut po upřesnění rozsahu: pro obrázek, pár parametrů a cenu je to služba navíc bez přínosu. Payload adaptér se mění za jeden řádek, takže případný pozdější přechod je export/import pár set řádků, ne přestavba. - **Astro + Decap CMS** — jednodušší provoz (obsah v gitu, žádná DB), ale slabší admin UX, nutný GitHub OAuth a rebuild po každé editaci. Horší cesta k budoucímu e-shopu. - **Astro + soubory v gitu** — nejjednodušší provoz, ale položky by musel přidávat vývojář, ne majitel. Neplní zadání. - **Astro + Directus** — dvě samostatné služby místo jedné, víc pohyblivých dílů bez odpovídajícího přínosu. --- ## 3. Topologie ``` ┌─ notebook (vývoj) ─────┐ │ pnpm dev → :3000 │ │ DB = soubor v ./data │ └───────────┬────────────┘ │ git push ▼ ┌─ gitea.doubynet.eu ────────────────────────┐ │ repo release → produkce, devel → staging│ │ Actions: lint → test → build image │ │ registry: honza/zbrane-reiner-web: │ └───────────┬────────────────────────────────┘ │ docker compose pull (server si sáhne pro image) ▼ ┌─ server s Dockerem ────────┐ │ prod: app :3000 │ │ staging: app :3001 │ │ data + media volumes │ └───────────▲────────────────┘ │ reverse_proxy ┌───────────┴─────────────────┐ │ Caddy │ │ reiner-zbrane.cz → 3000│ │ dev.reiner-zbrane.cz → 3001│ │ gitea.doubynet.eu → Gitea│ └─────────────────────────────┘ ``` Prod i staging běží na **jednom serveru** jako dva izolované Compose projekty — jiný `name:`, jiné porty, jiná volumes, jiný databázový soubor. Stačí stroj, který už máte, a na něm dva kontejnery navíc. --- ## 4. Pojmenování repozitáře ### Rozhodnutí: zůstat u `Zbrane-Reiner-Web` První verze tohoto dokumentu navrhovala `web-reiner-zbrane` (malá písmena, prefix podle typu). **Zamítnuto** — v `~/Dokumenty/Repo/` už je zavedená jiná konvence a ta má přednost před teorií: ``` Curator Dashboard Dokumentace GOGUpdater PlanetaryTime RWCalc SQLmem Trainyard Zbrane-Reiner-Web ``` TitleCase, žádný prefix podle typu, název = co to je. Zavést pro jeden projekt jiné pravidlo je horší než jakákoli nedokonalost toho stávajícího — konvence má cenu jen když platí všude. **Velká písmena Dockeru nevadí**, protože je CI srovná — přesně jak to dělá Dashboard: ```yaml echo "IMAGE=gitea.doubynet.eu/$(echo '${{ github.repository }}' \ | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_ENV" ``` `Honza/Zbrane-Reiner-Web` → `honza/zbrane-reiner-web`. Bez ručního zásahu. ### Propis do konfigurace ``` Gitea repo gitea.doubynet.eu/Honza/Zbrane-Reiner-Web (private) adresář ~/Dokumenty/Repo/Zbrane-Reiner-Web větve release (produkce), devel (staging) doména reiner-zbrane.cz → Caddy → server:3000 staging dev.reiner-zbrane.cz → Caddy → server:3001 na serveru /srv/reiner-zbrane (release) /srv/reiner-zbrane-staging (devel) compose name reiner-zbrane / reiner-zbrane-staging volumes reiner-zbrane_data, reiner-zbrane_media databáze /app/data/reiner.db (uvnitř volume data) image gitea.doubynet.eu/honza/zbrane-reiner-web: ``` Jméno repa (`Zbrane-Reiner-Web`) a jméno nasazení (`reiner-zbrane`) se **záměrně liší**. Repo je katalogová položka, nasazení je runtime — sufix `-Web` v cestách na serveru a ve jménech volumes nenese žádnou informaci, tam je webem všechno. ### Doplňky k pojmenování - **Repo držet private** — skončí v něm struktura `.env`, migrace a časem integrace na dodavatele. - **Přejmenovat před nasazením, ne po něm.** Název se propisuje do jmen Docker volumes; změna po nasazení znamená ruční migraci dat. - **Přibude-li druhý projekt téhož klienta**, vznikne `Zbrane-Reiner-Strelnice` — stejný vzor, řadí se vedle sebe. - **Monorepo** — pokud by víc webů sdílelo komponenty nebo design, ne N repozitářů, ale jedno repo s `apps/*` a `packages/ui`. Tento návrh do monorepa přejde bez přestavby. --- ## 5. Struktura repa ``` Zbrane-Reiner-Web/ ├─ src/ │ ├─ collections/ # Products.ts, Media.ts, Pages.ts, Users.ts ← schéma obsahu │ ├─ app/(frontend)/ # veřejný web │ │ ├─ page.tsx # výpis nabídky │ │ ├─ nabidka/[slug]/ # detail položky │ │ ├─ [slug]/ # statické stránky z kolekce Pages │ │ └─ api/health/ # endpoint pro Docker HEALTHCHECK │ ├─ app/(payload)/ # admin, generuje Payload │ ├─ lib/format.ts # formátování ceny a stavu │ └─ payload.config.ts ├─ migrations/ # verzované DB migrace, commitují se ├─ .gitea/workflows/ci.yml # lint → test → build → push do registry ├─ Dockerfile ├─ .dockerignore ├─ docker-compose.yml # lokální vývoj (build z Dockerfile) ├─ docker-compose.prod.yml # produkce (image z registry) ├─ data/ # databázový soubor, NIKDY v gitu ├─ .env.example # v gitu ├─ .env # NIKDY v gitu ├─ PROJECT.md # cíle a fáze projektu ├─ CHANGELOG.md └─ README.md ``` Rozdělení na `docker-compose.yml` a `docker-compose.prod.yml` (místo jednoho souboru s overridy) je převzaté z [Dashboardu](../Dashboard/) — prod soubor je samostatný, čitelný a na serveru je jediné, co tam z repa musí být. ### Obsahový model a přístup | Kolekce | Obsah | |---|---| | `Products` | název, obrázky (vazba na Media), parametry (ráže, délka hlavně, stav…), cena, popis, příznak „prodáno" | | `Media` | nahrané obrázky, Payload k nim sám generuje zmenšeniny | | `Pages` | statické stránky — o nás, kontakt, otevírací doba | | `Users` | **jeden účet pro majitele.** Registrace vypnutá, účet se založí jednou při prvním spuštění. | Administrace je na `/admin` — přihlášení e-mailem a heslem, za ním rovnou seznam položek s tlačítky přidat / upravit / smazat. Žádná další role, žádné zákaznické účty. Veřejná část položky jen vypisuje. > **Přihlašování ven z internetu.** Admin je dostupný na veřejné doméně, takže: silné heslo, zapnout Payloadu `maxLoginAttempts` + `lockTime` a zvážit two-factor. Alternativa, pokud stačí správa z domácí sítě — pustit `/admin` v Caddy jen z LAN. --- ## 6. Konfigurační soubory ### `Dockerfile` Záměrně **bez** Next `standalone` outputu: image je o pár set MB větší, ale Payload CLI (migrace) v něm funguje bez ladění file-tracingu. Na tento objem provozu je to správný kompromis. Skutečná podoba je v [Dockerfile](Dockerfile); podstatné jsou tři věci, které nejsou zřejmé: ```dockerfile # `corepack enable` jen vytvoří shim; bez `prepare` by si kontejner stahoval # pnpm z npmjs.com až při startu — a bez internetu by vůbec nenaběhl. RUN corepack enable && corepack prepare pnpm@11.18.0 --activate # Placeholder inline, ne přes ENV — jinak zůstane secret ve vrstvě image # (a `docker build` na to právem nadává). RUN PAYLOAD_SECRET=build-time-placeholder-nahrazen-za-behu pnpm build # sqlite jen kvůli zálohám (`.dump`), ~1,5 MB RUN apk add --no-cache sqlite HEALTHCHECK --interval=30s --timeout=3s --start-period=30s --retries=3 \ CMD wget -qO- http://127.0.0.1:3000/api/health || exit 1 # Migrace proběhnou při startu, teprve pak naskočí server. CMD ["sh", "-c", "pnpm payload migrate && pnpm start"] ``` `HEALTHCHECK` je převzatý ze vzoru [Dashboardu](../Dashboard/Dockerfile) — `docker ps` pak rovnou ukazuje, jestli aplikace opravdu odpovídá, ne jen jestli proces běží. Míří na [`/api/health`](src/app/(frontend)/api/health/route.ts), který **záměrně nesahá do databáze**: kdyby ji kontroloval, výpadek databáze by Docker „léčil“ restartem aplikace, což nepomůže. Ověřeno: image naběhne i s `--network none`, tedy bez internetu. ### `docker-compose.yml` (lokální vývoj) ```yaml # Lokální vývoj: build z Dockerfile. Databáze je soubor v ./data/. name: reiner-zbrane-dev services: app: build: . ports: - "3000:3000" env_file: .env volumes: - ./src:/app/src # změny bez rebuildu - ./data:/app/data - ./media:/app/media command: pnpm dev restart: unless-stopped ``` Na notebooku se ale běžně **ani tohle nepoužije** — Payload se pouští nativně přes `pnpm dev` kvůli rychlému hot-reloadu. Tenhle soubor slouží k ověření, že se obraz vůbec postaví a rozjede. ### `docker-compose.prod.yml` (produkce i staging) ```yaml # Produkční nasazení: image se STAHUJE z Gitea registry (nestaví se lokálně). # Na serveru (jednorázově): docker login gitea.doubynet.eu # Nasazení / aktualizace: # docker compose -f docker-compose.prod.yml pull # docker compose -f docker-compose.prod.yml up -d name: ${PROJECT:-reiner-zbrane} services: app: image: gitea.doubynet.eu/honza/zbrane-reiner-web:${IMAGE_TAG:-latest} ports: - "${APP_PORT:-3000}:3000" # viz poznámka o Caddy níže env_file: .env volumes: - data:/app/data # databázový soubor - media:/app/media # nahrané obrázky přežijí redeploy restart: unless-stopped volumes: data: media: ``` Jedna služba, dva volumes, žádné heslo k databázi. Celý stav webu = tyhle dva volumes. ### `.env.example` ```dotenv # Vzor konfigurace. Zkopíruj do `.env` a vyplň. `.env` NIKDY necommituj. PROJECT=reiner-zbrane APP_PORT=3000 IMAGE_TAG=latest DATABASE_URI=file:./data/reiner.db # Klíč pro podpis session cookie – v produkci náhodný řetězec min. 32 znaků! PAYLOAD_SECRET=zmen-me-na-nahodny-retezec NEXT_PUBLIC_SERVER_URL=https://reiner-zbrane.cz ``` Staging má `PROJECT=reiner-zbrane-staging`, `APP_PORT=3001`, `IMAGE_TAG=devel`, vlastní `PAYLOAD_SECRET` a `NEXT_PUBLIC_SERVER_URL=https://dev.reiner-zbrane.cz`. `DATABASE_URI` zůstává stejné — soubor je v jiném volume, takže se nemají jak potkat. `IMAGE_TAG` je zároveň páka na rollback: přepsat na předchozí SHA, `pull`, `up -d`. ### Caddyfile (na stávajícím domácím Caddy) ```caddyfile reiner-zbrane.cz, www.reiner-zbrane.cz { reverse_proxy 192.168.1.50:3000 } dev.reiner-zbrane.cz { basic_auth { honza } # staging schovaný před světem i roboty reverse_proxy 192.168.1.50:3001 } ``` Hash hesla: `caddy hash-password`. Caddy už tímhle způsobem obsluhuje Gitea i Dashboard, takže přibývají jen dva bloky. > **Síťová poznámka.** Pokud Caddy běží na **jiném stroji** než Docker, musí porty poslouchat na LAN, ne na `127.0.0.1`, a přístup se omezí firewallem: > > ```bash > ufw allow from to any port 3000,3001 proto tcp > ufw default deny incoming > ``` > > Pokud Caddy běží na **tomtéž stroji** (jak to vypadá u Dashboardu), je to jednodušší a bezpečnější — v `docker-compose.prod.yml` stačí publikovat port jen na loopback: `"127.0.0.1:${APP_PORT}:3000"`. Ven se pak nedostane nic mimo Caddy a žádná `ufw` pravidla netřeba. --- ## 7. Deploy Postup je záměrně **stejný jako u [Dashboardu](../Dashboard/)** — CI postaví a nahraje image, server si ho stáhne. Žádný nový vzor k naučení. ### Pipeline | Krok | Kde běží | |---|---| | 1. `git push` do `devel` / `release` | notebook | | 2. lint + typy + testy | Gitea Actions runner | | 3. build image, tag `` + `devel`/`latest`, push do registry | Gitea Actions runner | | 4. `docker compose pull && up -d`, migrace v entrypointu | server (ručně) | | 5. Caddy posílá provoz beze změny | Caddy | ### `.gitea/workflows/ci.yml` ```yaml name: CI # Spustí se při pushi do devel/release a jde i ručně spustit z GUI. on: push: branches: [devel, release] workflow_dispatch: jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: "22" - name: Instalace pnpm run: corepack enable && corepack prepare pnpm@latest --activate - name: Instalace závislostí run: pnpm install --frozen-lockfile - name: ESLint run: pnpm lint - name: Typová kontrola run: pnpm exec tsc --noEmit - name: Build (ověření, že projde) run: pnpm build build-and-push: needs: test runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 # Docker jméno musí být malými písmeny (Honza/Zbrane-Reiner-Web -> honza/zbrane-reiner-web) - name: Název image run: | echo "IMAGE=gitea.doubynet.eu/$(echo '${{ github.repository }}' \ | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_ENV" - name: Značka podle větve run: | if [ "${{ github.ref_name }}" = "release" ]; then echo "MOVING_TAG=latest" >> "$GITHUB_ENV" else echo "MOVING_TAG=devel" >> "$GITHUB_ENV" fi - name: Přihlášení do Gitea registry run: | echo '${{ secrets.REGISTRY_TOKEN }}' \ | docker login gitea.doubynet.eu -u '${{ github.actor }}' --password-stdin - name: Build image run: docker build -t "$IMAGE:$MOVING_TAG" -t "$IMAGE:${{ github.sha }}" . - name: Push image run: | docker push "$IMAGE:$MOVING_TAG" docker push "$IMAGE:${{ github.sha }}" ``` Oproti Dashboardu přibyl jen **plovoucí tag podle větve** — `release` → `latest`, `devel` → `devel`. Bez toho by staging a produkce ukazovaly na tentýž `latest` a nešly by oddělit. Záměrně **bez** `docker/build-push-action` — Gitea si actions tahá z github.com, takže každá závislost navíc je další věc, která se může rozbít. Holé `docker` funguje vždycky. Stejný důvod, proč to tak má Dashboard. ### Nasazení na serveru ```bash cd /srv/reiner-zbrane-staging # nebo /srv/reiner-zbrane docker compose -f docker-compose.prod.yml pull docker compose -f docker-compose.prod.yml up -d ``` Migrace se pustí samy v entrypointu kontejneru. Server **nic nekompiluje** a nemá zdrojové kódy — jen `docker-compose.prod.yml`, `.env` a dva volumes. ### Co je potřeba nastavit jednou **Na Giteji** — repo → Settings → Secrets: `REGISTRY_TOKEN` (osobní token s oprávněním `write:package`). Stejný secret, jaký už používá Dashboard. **Runner** — `act_runner` už běží kvůli Dashboardu, další projekt nepotřebuje nic navíc. **Na serveru** — jednorázově: ```bash mkdir -p /srv/reiner-zbrane /srv/reiner-zbrane-staging # do každého: docker-compose.prod.yml + .env docker login gitea.doubynet.eu # aby šlo image stáhnout ``` ### Dva adresáře na serveru | Cesta | Větev | Tag | Port | Doména | |---|---|---|---|---| | `/srv/reiner-zbrane` | `release` | `latest` | 3000 | `reiner-zbrane.cz` | | `/srv/reiner-zbrane-staging` | `devel` | `devel` | 3001 | `dev.reiner-zbrane.cz` | Každý má vlastní `.env`. Liší se `PROJECT`, `APP_PORT`, `IMAGE_TAG` a `PAYLOAD_SECRET`. ### Denní workflow ```bash # vývoj git switch -c feature/kontaktni-formular pnpm dev # localhost:3000 # změna schématu obsahu → migrace do gitu pnpm payload migrate:create pridani-formulare git add . && git commit && git push # staging git switch devel && git merge feature/kontaktni-formular && git push ssh server 'cd /srv/reiner-zbrane-staging && docker compose -f docker-compose.prod.yml pull && docker compose -f docker-compose.prod.yml up -d' # produkce, až to na dev.reiner-zbrane.cz sedí git switch release && git merge devel && git push ssh server 'cd /srv/reiner-zbrane && docker compose -f docker-compose.prod.yml pull && docker compose -f docker-compose.prod.yml up -d' ``` ### Rollback ```bash sed -i 's/^IMAGE_TAG=.*/IMAGE_TAG=/' .env docker compose -f docker-compose.prod.yml pull docker compose -f docker-compose.prod.yml up -d ``` Funguje, protože každý build je otagovaný svým SHA a v registry zůstává. Migrace jsou ale jednosměrné — pokud verze mezitím změnila schéma databáze, patří k rollbacku i obnova zálohy. --- ## 8. Zálohy S CMS je databáze **jediná kopie obsahu** — na rozdíl od statického webu ji nemáte v gitu. Nepodceňovat. Cron na serveru, denně: ```bash cd /srv/reiner-zbrane docker compose -f docker-compose.prod.yml exec -T app \ sqlite3 /app/data/reiner.db .dump | gzip > /backup/db-$(date +%F).sql.gz docker run --rm -v reiner-zbrane_media:/m -v /backup:/b alpine \ tar czf /b/media-$(date +%F).tgz -C /m . find /backup -mtime +14 -delete ``` `.dump` běží v transakci, takže je konzistentní i za provozu — soubor **nekopírovat** obyčejným `cp`, ten za běhu s WAL zachytí rozpracovaný zápis. Obnova: `zcat db-2026-07-30.sql.gz | sqlite3 reiner.db`. Zálohy odvézt i mimo server (rsync/restic na NAS nebo do cloudu) — záloha na stejném stroji neřeší selhání disku. **Obnovu jednou za čas opravdu vyzkoušet na stagingu.** Záloha, kterou jste nikdy nenačetli, není záloha. --- ## 9. Cesta dál Nic z toho neřešit teď — ruční `pull && up -d` vydrží hodně dlouho. 1. **Automatický deploy stagingu** — buď [Watchtower](https://containrrr.dev/watchtower/) sledující tag `devel`, nebo krok navíc v CI, který se přes SSH přihlásí na server a spustí `pull && up -d`. Ušetří jeden příkaz po každém pushi do `devel`; u produkce je ruční krok spíš výhoda. 2. **Buildx cache v CI** — první build instaluje závislosti od nuly. S `--cache-from` proti registry se sestavení zkrátí na desítky sekund. 3. **E-shop** — Payload má oficiální plugin pro platby; přechod nevyžaduje změnu infrastruktury. 4. **Postgres**, ale až kdyby přišly souběžné zápisy (objednávky, víc editorů současně). Mění se jeden adaptér v `payload.config.ts` a přidává jedna služba v `docker-compose.prod.yml`; data se převedou exportem a importem. Do té doby by to byla údržba navíc bez užitku. --- ## 10. Otevřené otázky - [ ] **Příjmení: „Reirenr" vs. „Reiner"** — vypadá to jako překlep. Celý dokument počítá s `reiner`. Potvrdit před scaffoldem, protože se to propíše do jmen Docker volumes a přejmenování po nasazení znamená ruční migraci dat. - [ ] **Skutečná doména** — do `NEXT_PUBLIC_SERVER_URL` a Caddyfile. Název repa už na ní nezávisí, ten je daný konvencí. - [ ] **Běží Caddy na tomtéž stroji jako Docker?** — pokud ano, publikovat porty jen na `127.0.0.1` a vynechat `ufw` pravidla. Pokud ne, doplnit IP serveru (v návrhu placeholder `192.168.1.50`). - [ ] **Porty 3000/3001 volné?** — na serveru už běží Dashboard na `:8000`, kolize nehrozí, ale ověřit. - [ ] **Smazat prázdný adresář `Web-ZaS`.** --- ## Další krok Po potvrzení otevřených otázek: 1. `git init` v tomto repu, remote na `gitea.doubynet.eu/Honza/Zbrane-Reiner-Web`, větve `release` + `devel` 2. vygenerovat Payload 3 + Next.js projekt se SQLite adaptérem 3. napsat `Dockerfile`, `.dockerignore`, `docker-compose.yml`, `docker-compose.prod.yml`, `.env.example`, `.gitea/workflows/ci.yml`, `README.md`, `PROJECT.md`, `CHANGELOG.md` 4. přidat kolekce `Products`, `Media`, `Pages`, `Users`, vypnout veřejnou registraci a založit jediný účet pro majitele 5. postavit veřejný výpis položek a detail položky