Ten dokument opisuje lokalne czynności operacyjne PhotoMap: diagnostykę danych, backup i podstawowy restore test. Nie jest roadmapą produktu.
Każda odpowiedź backendu ma nagłówek X-Request-ID. Jeśli klient przyśle własny poprawny identyfikator, backend go zachowa; w przeciwnym razie wygeneruje nowy. Odpowiedzi błędów JSON mają kształt:
{
"detail": "Human readable error",
"request_id": "request-id"
}Błędy walidacji zachowują listę FastAPI w detail, ale również dodają request_id. Nieobsłużone wyjątki zwracają bezpieczne Internal server error bez szczegółów implementacji.
Listy moderacji w admin API mają limit wyników:
GET /api/admin/photos?limit=100,GET /api/admin/memories?limit=100,GET /api/admin/reports?limit=100.
Domyślny limit to 100, a maksymalny dozwolony limit to 200. Parametr status można łączyć z limit, np. GET /api/admin/photos?limit=50&status=pending.
Te endpointy są kolejkami pracy, a nie źródłem pełnych galerii miejsc. Panel zdjęć konkretnego miejsca używa GET /api/admin/places/{place_id}/photos, żeby pobrać wszystkie zdjęcia tego miejsca niezależnie od paginacji i aktualnego filtra kolejki moderacji.
Pełne liczniki badge'y moderacji nie pochodzą z paginowanych list. Admin UI używa GET /api/admin/moderation/counts, które zwraca pełne totals z bazy dla zdjęć, pamiątek i zgłoszeń z rozbiciem po statusach. Dzięki temu badge Zdjęcia, Wszystkie, Zatwierdzone itd. nie zatrzymuje się na wartości 100, gdy pierwsza strona kolejki ma domyślny limit.
Publiczne uploady pamiątek są blokowane, jeśli kolejka materiałów pending przekracza limit rekordów albo limit bajtów w storage. Do limitu bajtów wlicza się obraz, miniatura, publiczna kopia i opcjonalne audio. Zdjęcia miejsc są dodawane przez adminowy upload redakcyjny i nie podlegają tym limitom.
Konfiguracja env:
PHOTOMAP_PUBLIC_PENDING_MEDIA_MAX_RECORDS=100
PHOTOMAP_PUBLIC_PENDING_MEDIA_MAX_BYTES=536870912
PHOTOMAP_PUBLIC_MEMORY_UPLOAD_RATE_LIMIT=10
PHOTOMAP_PUBLIC_MEMORY_UPLOAD_RATE_WINDOW_SECONDS=3600
PHOTOMAP_PUBLIC_MEMORY_UPLOAD_MAX_CONCURRENCY=1
PHOTOMAP_ADMIN_MEDIA_UPLOAD_MAX_CONCURRENCY=1
PHOTOMAP_PUBLIC_REPORT_RATE_LIMIT=20
PHOTOMAP_PUBLIC_REPORT_RATE_WINDOW_SECONDS=3600
PHOTOMAP_PUBLIC_MEMORY_OWNER_RATE_LIMIT=30
PHOTOMAP_PUBLIC_MEMORY_OWNER_RATE_WINDOW_SECONDS=3600
PHOTOMAP_TRUSTED_PROXY_NETWORKS=127.0.0.1/32,::1/128Limity częstotliwości są prowadzone w pamięci procesu osobno dla uploadów pamiątek i zgłoszeń.
Identyfikator CF-Connecting-IP jest używany wyłącznie wtedy, gdy bezpośrednie połączenie pochodzi
z sieci wskazanej w PHOTOMAP_TRUSTED_PROXY_NETWORKS; nagłówek od innego klienta jest ignorowany.
Publiczny runtime za lokalnym cloudflared powinien pozostać dostępny tylko na loopback.
Limit całego requestu jest sprawdzany przed parserem multipart zarówno z Content-Length, jak i podczas
strumieniowego odbierania body, więc upload bez tego nagłówka nie może ominąć ograniczenia rozmiaru.
Domyślnie tylko jeden publiczny upload pamiątki może być jednocześnie parsowany i przetwarzany; następne
żądanie dostaje 429 z Retry-After, co ogranicza szczytowe zużycie RAM podczas dekodowania dużych zdjęć.
Mutacje admina sprawdzają Bearer token przed odczytem body. Wszystkie mają limit całego requestu, a trzy
endpointy mediów współdzielą osobny limit jednej równoległej operacji, więc nieuwierzytelniony request nie
może wymusić wcześniejszego spoolowania dużego multipart do /tmp.
Publiczny runtime powinien dodatkowo ustawić:
PHOTOMAP_ENV=production
PHOTOMAP_PUBLIC_SITE_URL=https://photomap.pl
PHOTOMAP_ALLOWED_HOSTS=photomap.pl,www.photomap.pl,localhost,127.0.0.1
FRONTEND_ORIGINS=https://photomap.pl,https://www.photomap.plTryb production wyłącza /docs, /redoc i /openapi.json. Aplikacja odrzuca nieznane nagłówki
Host, dodaje CSP, ochronę przed osadzaniem, nosniff, polityki referrera/uprawnień oraz HSTS dla
żądań HTTPS. /admin i /api/admin/* mają Cache-Control: no-store. Token panelu admina jest trzymany
wyłącznie w pamięci bieżącej karty i po odświeżeniu trzeba wpisać go ponownie.
Diagnostyka porównuje SQLite z lokalnym storage:
backend/data/app.db,backend/storage/private,backend/storage/public.
Sprawdzane są rekordy photo i memory, brakujące oryginały prywatne, brakujące publiczne kopie i miniatury, osierocone pliki storage, statusy, liczniki miejsc, cover miejsca oraz publiczne serializery pod kątem prywatnych pól.
Audio jest opcjonalnym załącznikiem do photo albo memory. Oryginał trafia do prywatnego storage, publiczna kopia trafia pod /media/..., a publiczne payloady zwracają tylko obiekt audio z public_path, mime_type, size_bytes i duration_seconds. Publiczne listy, mapa i szczegóły pokazują audio tylko dla zatwierdzonych mediów; admin widzi metadane audio także w kolejce moderacji. Admin może dodać, podmienić albo usunąć audio dla istniejącego zdjęcia lub pamiątki; podmiana i usunięcie kasują stare pliki audio z private i public storage po udanym zapisie rekordu.
photo.caption pozostaje krótkim podpisem zdjęcia. photo.description_blocks jest opcjonalnym dłuższym opisem redakcyjnym/narracją przygotowaną pod tekst na ekranie i TTS; nie jest plikiem audio i nie zastępuje atrybucji źródła. Zasady stylu, struktury bloków i edutainment dla opisów zdjęć są w docs/create_tts.md.
TTS dla opisów zdjęć, miejsc i tras korzysta w przeglądarce z Web Speech API (speechSynthesis), a nie z backendowego generatora audio. Na Debianie/Raspberry Pi Brave lub Chromium mogą wystawiać API bez żadnych głosów, co daje niemą ikonę albo brak przycisku po poprawnej detekcji. Runtime powinien mieć lokalny silnik głosu:
sudo apt install speech-dispatcher espeak-ngPo instalacji zrestartuj Brave/Chromium i sprawdź, czy window.speechSynthesis.getVoices() zwraca co najmniej jeden głos. Aplikacja renderuje przycisk TTS tylko wtedy, gdy przeglądarka ma realnie dostępny głos.
python3 scripts/diagnose_local_data.py
python3 scripts/diagnose_local_data.py --json
python3 scripts/diagnose_local_data.py --output-json .dev/local-data-diagnostics.json
python3 scripts/diagnose_local_data.py --no-image-check
python3 scripts/diagnose_local_data.py --strictKody wyjścia:
0oznacza brak błędów,1oznacza błędy,--strictzwraca1także przy ostrzeżeniach.
Osierocone pliki storage usuwaj dopiero po backupie lokalnych danych. Skrypt korzysta z tej samej diagnostyki co scripts/diagnose_local_data.py i usuwa wyłącznie pliki zgłoszone jako orphan_private_file albo orphan_public_file. Jeśli diagnostyka wykryje błędy, tryb --apply nie usuwa plików.
./scripts/backup_local_data.sh --apply
python3 scripts/cleanup_orphan_media.py --dry-run
python3 scripts/cleanup_orphan_media.py --apply
python3 scripts/cleanup_orphan_media.py --apply --output-json .dev/orphan-media-cleanup.jsonPo migracji prywatności zdjęć usuń historyczne publiczne pochodne rekordów pending i rejected. Komenda bez --apply jest bezpiecznym dry-runem. Tryb --apply usuwa wyłącznie publiczną kopię, miniaturę i publiczne audio, a następnie zeruje ich ścieżki w bazie; prywatny oryginał pozostaje do podglądu moderatora. Skrypt jest idempotentny.
./scripts/backup_local_data.sh --apply
backend/.venv/bin/python scripts/unpublish_nonapproved_photos.py
backend/.venv/bin/python scripts/unpublish_nonapproved_photos.py --applyW bieżącym przepływie odrzucenie najpierw przenosi publiczne pliki przez atomowy rename do prywatnego quarantine, a następnie jednym commitem zapisuje status rejected i puste ścieżki publiczne. Publiczny server.py po migracjach automatycznie odzyskuje niedokończoną operację: przy nadal zatwierdzonym rekordzie przywraca pliki, a po zapisanym odrzuceniu usuwa quarantine. Niepusty quarantine bez poprawnego manifestu zatrzymuje start fail-closed i pozostawia prywatne pliki do ręcznej inspekcji.
Po --apply unieważnij w cache CDN ścieżki /media/... wypisane przez raport, żeby wcześniej zbuforowana odpowiedź nie pozostała dostępna na brzegu.
Runtime ustawia dla /media/* rewalidację przeglądarki i no-store dla CDN, aby zwykłe odrzucenie
zdjęcia nie pozostawiało publicznej kopii na brzegu. Po pierwszym wdrożeniu tej polityki wykonaj jeden
pełny purge istniejącego cache strefy Cloudflare; nowe nagłówki nie usuwają odpowiedzi zbuforowanych wcześniej.
Retencja prywatnych oryginałów działa jako ręczny skrypt operacyjny. Dla zatwierdzonych mediów po zadanym czasie prywatny oryginał jest zastępowany kopią publicznej pochodnej. Odrzucone media nie mają publicznych pochodnych; admin korzysta z chronionego podglądu prywatnego oryginału tylko do czasu usunięcia go przez retencję.
python3 scripts/retain_private_originals.py --dry-run
python3 scripts/retain_private_originals.py --dry-run --json
python3 scripts/retain_private_originals.py --apply
python3 scripts/retain_private_originals.py --apply --output-json .dev/private-original-retention.jsonDomyślnie zatwierdzone media są kwalifikowane po 30 dniach od approved_at, a odrzucone media od razu. Progi można zmienić:
python3 scripts/retain_private_originals.py --dry-run --approved-days 60 --rejected-days 7Adminowe kolejki zdjęć i pamiątek mają akcję Anonimizuj. Modal ładuje obraz, pozwala narysować obszar myszką, przesunąć zaznaczenie, złapać rogi, dopasować kształt, obrócić aktywny obszar i zapisać redakcję. Zapis wypala obszary w prywatnym oryginale, publicznej kopii i miniaturze.
CLI zostaje niższopoziomową ścieżką operacyjną. Współrzędne podawane są jako wartości z zakresu 0..1: prostokąt jako left,top,right,bottom, a poligon jako kolejne punkty x1,y1,x2,y2,x3,y3.
python3 scripts/redact_media_image.py --dry-run --kind photo --id <photo-id> --rect 0.1,0.1,0.4,0.3
python3 scripts/redact_media_image.py --apply --kind memory --id <memory-id> --rect 0.2,0.2,0.5,0.5
python3 scripts/redact_media_image.py --apply --kind photo --id <photo-id> --polygon 0.2,0.2,0.8,0.2,0.5,0.7
python3 scripts/redact_media_image.py --apply --kind photo --id <photo-id> --rect 0.1,0.1,0.4,0.3 --output-json .dev/redaction.jsonUżywaj tego do ręcznego ukrycia twarzy, tablic, przypadkowych osób albo prywatnych szczegółów. Skrypt nie zgaduje regionów automatycznie.
Eksport research tworzy tekstowe paczki ZIP z opisami miejsc: opis miejsca, article_blocks,
local_comment, podpisy i description_blocks istniejących zdjęć, czytelny review.md,
prompt PROMPT.md, tts-guidelines.md z aktualnym standardem TTS z docs/create_tts.md
i requested_changes.template.json do późniejszego zwrotu poprawek tekstowych.
Paczki nie zawierają zdjęć, prywatnych oryginałów ani EXIF.
Prompt wymaga porównania obecnej i proponowanej wersji oraz zwrotu finalnego
requested_changes.json z tekstami gotowymi do późniejszego zapisu w bazie. description_blocks
są traktowane jako tekst widoczny w aplikacji i materiał do TTS, więc audyt nie powinien
skracać ich mechanicznie do streszczeń. Wynik audytu ma być wklejony bezpośrednio w czacie,
bez linków i plików do pobrania.
make export-place-research
make export-place-research QUERY="Rynek"
make export-place-research CITY="Wrocław" PLACE="Rynek"
make export-city-research CITY="Wrocław"
make export-all-research ARGS="--yes"Wyszukiwanie miasta i miejsca jest case-insensitive oraz ignoruje polskie znaki. Jeśli jest kilka wyników albo tylko podobne nazwy, skrypt pokazuje listę wyboru.
Eksport trafia do czytelnej struktury katalogów:
research-exports/
miejsca/{place-slug}.zip
miasta/{city-slug}.zip
wszystkie/wszystkie.zipPaczka pojedynczego miejsca, np. miejsca/rynek-wroclaw.zip, zawiera pliki opisu bez dodatkowych katalogów.
Paczka miasta, np. miasta/walbrzych.zip, zawiera jeden wspólny PROMPT.md w głównym katalogu ZIP
i osobne katalogi miejsc w środku. Paczka wszystkie/wszystkie.zip zawiera katalogi miast, a w nich katalogi miejsc.
Każdy eksport odświeża też research-exports/prompt.txt z krótką instrukcją do skopiowania
do czatu razem z załączonym ZIP-em.
research-exports/ jest ignorowane przez Git. Paczki są lokalnym artefaktem roboczym i nie powinny
trafiać do commita.
Skrypty operacyjne, które tylko raportują stan, obsługują --json, --output-json i --strict. Skrypty, które mogą zmieniać dane albo pliki, obsługują dodatkowo --dry-run i --apply.
Importer manifestów również działa w tym trybie:
python3 scripts/content/import_city.py --dry-run content/cities/wroclaw/manifest.json
python3 scripts/content/import_city.py --apply content/cities/wroclaw/manifest.jsonBackup lokalny uruchamia diagnostykę przed kopiowaniem danych. Jeśli diagnostyka wykryje błędy, backup jest blokowany, a raport JSON zostaje zapisany w katalogu niedoszłego backupu.
./scripts/backup_local_data.sh --dry-run
./scripts/backup_local_data.sh --apply
./scripts/backup_local_data.sh --dry-run --json
./scripts/backup_local_data.sh --apply --output-json .dev/backup-report.json
./scripts/backup_local_data.sh --apply --keep-backups 2
./scripts/backup_local_data.sh --apply --no-pruneUdany backup trafia do:
backups/local-{timestamp}Zakres backupu:
backend/data/app.db,backend/storage,local-data-diagnostics.json.
Katalog backups/ jest lokalny i nie powinien trafiać do Git.
Po udanym --apply skrypt domyślnie zostawia tylko najnowszy katalog backups/local-* i usuwa starsze lokalne backupy. Czyszczenie działa dopiero po poprawnym utworzeniu nowej kopii. Liczbę zachowanych kopii można zmienić przez --keep-backups N albo PHOTOMAP_BACKUP_KEEP=N; jednorazowo można je wyłączyć przez --no-prune.
Próbne odtworzenie wykonuj do katalogu tymczasowego, bez nadpisywania aktywnego backend/data ani backend/storage.
mkdir -p /tmp/photomap-restore-test
cp -R backups/local-{timestamp}/backend /tmp/photomap-restore-test/backend
PHOTOMAP_DATA_DIR=/tmp/photomap-restore-test/backend/data \
PHOTOMAP_STORAGE_DIR=/tmp/photomap-restore-test/backend/storage \
python3 scripts/diagnose_local_data.pyJeśli diagnostyka po restore zwraca błędy, nie używaj tego backupu jako źródła odtworzenia bez ręcznej weryfikacji.
Raport architektury jest narzędziem read-only do szybkiej orientacji w kodzie. Pokazuje największe pliki, najdłuższe funkcje, znalezione stringi endpointów, zależności między grupami modułów, cykle importów Pythona, proste ryzykowne wzorce i dostępność lokalnych narzędzi.
python3 scripts/diagnose_architecture.py
python3 scripts/diagnose_architecture.py --json
python3 scripts/diagnose_architecture.py --output-json .dev/architecture-diagnostics.jsonRaport nie modyfikuje danych ani kodu. Traktuj go jako wsparcie przy planowaniu porządkowania modułów, nie jako automatyczną listę zadań produktowych.
Reset lokalnej bazy i storage:
./scripts/reset_dev_data.shPo resecie uruchom migracje przez backend albo pełny check:
make start
make checkLokalne procesy dev zapisują logi w:
.dev/backend.log
.dev/frontend.logPodgląd:
make logs
make logs -f