{"id":50565634,"url":"https://github.com/4kulia/dutch-atlas","last_synced_at":"2026-06-04T14:30:30.544Z","repository":{"id":355031968,"uuid":"1226459418","full_name":"4kulia/dutch-atlas","owner":"4kulia","description":null,"archived":false,"fork":false,"pushed_at":"2026-05-01T14:44:44.000Z","size":238,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-01T15:26:54.300Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/4kulia.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-01T12:30:15.000Z","updated_at":"2026-05-01T13:31:15.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/4kulia/dutch-atlas","commit_stats":null,"previous_names":["4kulia/dutch-atlas"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/4kulia/dutch-atlas","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/4kulia%2Fdutch-atlas","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/4kulia%2Fdutch-atlas/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/4kulia%2Fdutch-atlas/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/4kulia%2Fdutch-atlas/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/4kulia","download_url":"https://codeload.github.com/4kulia/dutch-atlas/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/4kulia%2Fdutch-atlas/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33910136,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-04T02:00:06.755Z","response_time":64,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-04T14:30:29.910Z","updated_at":"2026-06-04T14:30:30.537Z","avatar_url":"https://github.com/4kulia.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Netherlands Attractions Map · Карта достопримечательностей Нидерландов\n\nИнтерактивная двуязычная (RU/EN) карта ~99 достопримечательностей Нидерландов\nиз видео https://youtu.be/8O8TIoHpKXQ. При клике на маркер открывается\nпанель с описанием и встроенным YouTube‑плеером, который автоматически\nиграет с нужного таймкода.\n\n## Стек\n\n- **Vite + React 18 + TypeScript**\n- **Tailwind CSS**\n- **`@vis.gl/react-google-maps`** + `@googlemaps/markerclusterer`\n- **PocketBase** (Go-бинарь в Docker) — auth + БД для избранного и заметок\n- YouTube IFrame API (без отдельной либы)\n\n## Локальный запуск\n\n```bash\n# 1. Установить зависимости\nnpm install\n\n# 2. Получить Google Maps API key — см. раздел ниже.\ncp .env.example .env.local\n# Отредактируйте .env.local — VITE_GOOGLE_MAPS_API_KEY и VITE_POCKETBASE_URL.\n\n# 3. Запустить PocketBase (бэкенд для auth + избранного + заметок)\ndocker compose up -d pb\n# → http://localhost:8090/_/   (админка)\n# → http://localhost:8090/api/ (REST API)\n\n# 4. Запустить dev-сервер\nnpm run dev\n# → http://localhost:5173\n```\n\n## Скрипты\n\n| Команда                     | Что делает                                                           |\n| --------------------------- | -------------------------------------------------------------------- |\n| `npm run dev`               | Vite dev server (HMR) на 5173                                        |\n| `npm run build`             | Production-сборка в `dist/`                                          |\n| `npm run preview`           | Preview production-сборки локально                                    |\n| `npm run typecheck`         | Только проверка типов (без сборки)                                   |\n| `node scripts/prepare-data.mjs` | Парсит docs/* и собирает базовый `data/attractions.base.json`     |\n| `node scripts/merge-data.mjs`   | Мёрджит base + enrichments → итоговый `data/attractions.json`     |\n\n## Google Maps API key\n\n1. https://console.cloud.google.com → создать или выбрать проект.\n2. **APIs \u0026 Services → Library** — включить **Maps JavaScript API**.\n3. **Credentials → Create credentials → API key**.\n4. На созданном ключе:\n   - **Application restrictions → HTTP referrers**:\n     - `http://localhost:5173/*`\n     - `http://localhost:8080/*`\n     - `http://localhost:4173/*`\n     - `https://your-domain.com/*` (продовый домен)\n   - **API restrictions → Selected APIs**: только **Maps JavaScript API**.\n5. **Billing**: привязать аккаунт с биллингом (без него Maps JS не работает,\n   но есть бесплатная квота $200/мес).\n6. Поставить **Budget alert** на $1–5/мес как страховку.\n7. Скопировать ключ в `.env.local`:\n   ```\n   VITE_GOOGLE_MAPS_API_KEY=AIza...\n   ```\n\n\u003e Ключ `VITE_*` попадает в клиентский бандл и виден в браузере — это нормально\n\u003e для Maps JS API. Защита — через HTTP-referrer ограничения, не через\n\u003e секретность ключа.\n\n### (Опционально) Map ID для AdvancedMarker\n\nЧтобы получить более чистый стиль карты и поддержку `AdvancedMarker` без\nпредупреждений в консоли:\n\n1. **Google Maps Platform → Map management → Create new Map ID**\n2. Тип: **JavaScript**\n3. Скопировать Map ID в `.env.local`:\n   ```\n   VITE_GOOGLE_MAPS_MAP_ID=ваш-map-id\n   ```\n\nБез указания Map ID используется дефолтный fallback.\n\n## Аутентификация (Google OAuth + PocketBase)\n\nПри первом запуске `docker compose up -d pb` PocketBase автоматически\nсоздаёт коллекции `users`, `favorites`, `notes` (см. `pb/pb_migrations/`).\n\n### Шаг 1. Создать superuser PocketBase\n\n1. Открыть **http://localhost:8090/_/** — на первом заходе появится форма\n   создания админ-аккаунта (нужен только разработчику для управления PB).\n2. Введите email + пароль, запомните.\n\n### Шаг 2. Создать OAuth Client в Google Cloud Console\n\n1. https://console.cloud.google.com → ваш проект.\n2. **APIs \u0026 Services → OAuth consent screen** — настроить External app:\n   - App name, support email, developer contact.\n   - В тестовом режиме добавьте свои Google-emails в **Test users**.\n3. **Credentials → Create credentials → OAuth client ID → Web application**:\n   - **Authorized JavaScript origins:**\n     - `http://localhost:5173`\n     - `http://localhost:8080`\n     - `https://your-domain.com` (прод)\n   - **Authorized redirect URIs:**\n     - `http://localhost:8090/api/oauth2-redirect`\n     - `https://your-domain.com/pb/api/oauth2-redirect` (прод)\n4. Скопировать **Client ID** и **Client Secret**.\n\n### Шаг 3. Подключить Google в PocketBase\n\n**Вариант А — через скрипт (рекомендуется).** Если у вас на руках\n`client_secret_\u003cid\u003e.json` из Google Cloud (Credentials → Download JSON),\nзапустите:\n\n```bash\nPOCKETBASE_ADMIN_EMAIL=you@example.com \\\nPOCKETBASE_ADMIN_PASSWORD='ваш-пароль-PB-админа' \\\nnode scripts/setup-pb-oauth.mjs path/to/client_secret_xxx.json\n```\n\nСкрипт прочитает файл локально, авторизуется в PB и проставит Client ID/Secret\n+ включит OAuth2 в коллекции users. Ничего не нужно копировать вручную.\n\n**Вариант Б — вручную через админку.** В **Settings → Auth providers → Google**\nвведите Client ID и Client Secret из Google Cloud Console. В\n**Collections → users → Edit → Options**: включите ✅ **OAuth2**.\n\n### Шаг 4. Тест\n\n`npm run dev` → http://localhost:5173 → клик «Войти через Google» в\nшапке → попап → выбор аккаунта → видите свой аватар вместо кнопки.\n\nПосле входа в drawer достопримечательности появятся:\n- Кнопка-сердечко «В избранное»\n- Секция «Мои заметки» (приватный текст, виден только вам)\n\nВ фильтре сверху появится чип «Избранное N» — фильтр по сохранённым.\n\n## Деплой через Docker\n\nОбраз собирается multi-stage (node → nginx) и слушает на `127.0.0.1:8080`.\nПеред ним должен стоять серверный nginx с TLS (см. пример ниже).\n\n### Сборка и запуск\n\n```bash\n# В .env рядом с docker-compose.yml:\necho \"VITE_GOOGLE_MAPS_API_KEY=AIza...\" \u003e .env\n\ndocker compose build\ndocker compose up -d\ndocker compose logs -f web\n```\n\nПосле запуска: `curl -I http://127.0.0.1:8080/` должен вернуть `200 OK`.\n\n### Полный шаг‑за‑шагом деплой на сервер (`dutch-atlas.com`)\n\nОдин раз настроить:\n\n```bash\n# 1. Установить Docker + compose plugin (если ещё нет)\ncurl -fsSL https://get.docker.com | sh\nsudo usermod -aG docker $USER  # перелогиньтесь\n\n# 2. Склонировать репо\ncd /opt\nsudo mkdir dutch-atlas \u0026\u0026 sudo chown $USER:$USER dutch-atlas\ngit clone https://github.com/4kulia/dutch-atlas.git dutch-atlas\ncd dutch-atlas\n\n# 3. Создать .env с production-ключом Google Maps\ncat \u003e .env \u003c\u003cEOF\nVITE_GOOGLE_MAPS_API_KEY=AIza...prod_key\nVITE_POCKETBASE_URL=/pb\nEOF\n\n# 4. Запустить контейнеры\ndocker compose up -d\n\n# 5. Создать PocketBase superuser\ndocker compose exec pb /pb/pocketbase superuser upsert your-email@example.com 'StrongPasswordHere'\n\n# 6. Подключить Google OAuth (см. Раздел \"Аутентификация\" выше — Шаг 3)\n#    Загрузите client_secret_*.json на сервер (scp), затем:\nPOCKETBASE_URL=http://localhost:8090 \\\nPOCKETBASE_ADMIN_EMAIL=your-email@example.com \\\nPOCKETBASE_ADMIN_PASSWORD='StrongPasswordHere' \\\nnode scripts/setup-pb-oauth.mjs /path/to/client_secret_xxx.json\n#    (или вручную в http://localhost:8090/_/ через SSH-туннель)\n\n# 7. Настроить host nginx (см. ниже) и certbot\nsudo certbot --nginx -d dutch-atlas.com\n```\n\nПосле каждого `git pull`:\n```bash\ndocker compose build \u0026\u0026 docker compose up -d\n```\n\n### Пример конфига host nginx (reverse proxy + TLS)\n\n```nginx\nserver {\n    listen 443 ssl http2;\n    server_name your-domain.com;\n\n    ssl_certificate     /etc/letsencrypt/live/your-domain.com/fullchain.pem;\n    ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;\n\n    # PocketBase (auth + БД)\n    location /pb/ {\n        proxy_pass         http://127.0.0.1:8090/;\n        proxy_http_version 1.1;\n        proxy_set_header   Host $host;\n        proxy_set_header   X-Real-IP $remote_addr;\n        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header   X-Forwarded-Proto https;\n        # PB realtime (server-sent events) и большие тела (uploads)\n        proxy_buffering off;\n        client_max_body_size 50M;\n    }\n\n    # Статика фронта\n    location / {\n        proxy_pass         http://127.0.0.1:8080;\n        proxy_http_version 1.1;\n        proxy_set_header   Host $host;\n        proxy_set_header   X-Real-IP $remote_addr;\n        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header   X-Forwarded-Proto https;\n    }\n}\n\nserver {\n    listen 80;\n    server_name your-domain.com;\n    return 301 https://$host$request_uri;\n}\n```\n\nПосле деплоя на прод:\n1. Добавить продовый домен в HTTP-referrer ограничения Google Maps API key.\n2. Добавить `https://your-domain.com` в **Authorized origins** OAuth Client.\n3. Добавить `https://your-domain.com/pb/api/oauth2-redirect` в **Authorized redirect URIs**.\n\n## Структура проекта\n\n```\nnl_attractions/\n├── docs/                       # исходные данные (timecodes.md, transcript.json)\n├── data/\n│   ├── attractions.base.json   # авто-сгенерировано, base RU данные\n│   └── attractions.json        # итоговый источник истины (RU+EN, координаты)\n├── scripts/\n│   ├── prepare-data.mjs        # парсер MD + транскрипта\n│   ├── enrichments.mjs         # координаты и EN-переводы (вручную)\n│   └── merge-data.mjs          # сборка финального JSON\n├── src/\n│   ├── App.tsx\n│   ├── main.tsx\n│   ├── components/             # MapView, Drawer, Header, Filter, Toggle, …\n│   ├── i18n/                   # LanguageProvider + строки UI\n│   ├── data/                   # типизированный импорт attractions.json\n│   ├── types.ts\n│   └── index.css\n├── docker/nginx.conf\n├── Dockerfile\n├── docker-compose.yml\n└── …\n```\n\n## Обновление данных\n\nЕсли поправили `docs/netherlands_attractions_timecodes.md` или\n`scripts/enrichments.mjs`:\n\n```bash\nnode scripts/prepare-data.mjs\nnode scripts/merge-data.mjs\n```\n\n`data/attractions.json` пересобирается из обоих источников.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F4kulia%2Fdutch-atlas","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F4kulia%2Fdutch-atlas","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F4kulia%2Fdutch-atlas/lists"}