15 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
55191e089d |
feat(deploy): Phase 6 — публичный сайт на food-market.zat.kz, админка на app.
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 45s
CI / Web (React + Vite) (push) Successful in 36s
Docker API / Build + push API (push) Successful in 50s
Docker Public / Build + push Public (push) Successful in 45s
Docker Web / Build + push Web (push) Successful in 6s
Docker API / Deploy API on stage (push) Successful in 17s
Docker Public / Deploy Public on stage (push) Successful in 11s
Docker Web / Deploy Web on stage (push) Successful in 11s
Доменная схема (по решению юзера): food-market.zat.kz → новый Astro public-сайт (порт 8082, контейнер food-market-public) app.food-market.zat.kz → существующая админка (food-market.web, порт 8081) API остаётся на app.* под /api/*. Изменения: - docker-compose: добавлен сервис public (image food-market-public:latest, 127.0.0.1:8082:80). На стенде .env дополнен PUBLIC_TAG=latest, контейнер поднят, smoke на / и /pricing проходит. - Forgejo workflow .forgejo/workflows/docker-public.yml — отдельный билд при изменениях в src/food-market.public/**: docker build с --build-arg PUBLIC_SITE_URL=https://food-market.zat.kz и --build-arg PUBLIC_APP_URL=https://app.food-market.zat.kz, push в локальный registry, deploy через docker compose pull+up. TG-пинг. - Nginx (на стенде вручную, не через репо): - Новый блок food-market-app.conf для app.food-market.zat.kz — проксирует на :8081 (web), вместе с /api/admin/import/ и /tg-webhook путями. Certbot --nginx выпустил SSL. - Старый food-market-stage.conf переписан на public — проксирует на :8082, использует существующий SSL для food-market.zat.kz. - API CORS: добавлены food-market.zat.kz, app.food-market.zat.kz, food-market.kz, app.food-market.kz в AllowedOrigins (publicу нужен food-market.zat.kz для signup-запросов, админке нужен app.*). - JWT cookie domain не настраиваем — проект использует localStorage, cross-domain auth-bridge через URL fragment (см. AuthBridgePage), что безопаснее cookie с .food-market.zat.kz. - Хардкодов food-market.zat.kz в food-market.web/src не нашлось — всё через относительные URL. Существующие админ-сессии: токены в localStorage привязаны к food-market.zat.kz origin. После переезда юзеры увидят на этом домене публичный сайт без своих токенов — нужно перелогиниться на app.food-market.zat.kz. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
a4cbb06bb3 |
feat(public): Phase 6 — публичный маркетинговый сайт food-market.public на Astro
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 48s
CI / Web (React + Vite) (push) Successful in 38s
Docker API / Build + push API (push) Successful in 50s
Docker Web / Build + push Web (push) Successful in 40s
Docker API / Deploy API on stage (push) Successful in 18s
Docker Web / Deploy Web on stage (push) Successful in 11s
Новый пакет src/food-market.public/ — отдельный фронт для маркетинга и
самостоятельной регистрации магазинов-клиентов в SaaS Food Market.
Существующая админка food-market.web НЕ затронута.
Стек: Astro 4 + React 19 islands + Tailwind v3 (палитра идентична
food-market.web — единый бренд), TypeScript 6, content collections для
юр.документов. Static-сайт через nginx, gzip + immutable cache на assets.
Карта страниц (23):
- / — главная (Hero + 3 выгоды + скриншот +
6 вертикалей + 6 модулей + Касса +
Интеграции + 3 тарифа + соцпруф +
FAQ + финальный CTA)
- /features — модули по сценариям
- /pricing — тарифы + интерактивный конструктор
«Бизнес» (per-unit: 10000 база +
2000/магазин + 500/касса + 500/склад,
слайдеры, передача params в /signup)
- /pos — УТП лендинг кассы для Windows + весов
- /migration-from-moysklad — УТП лендинг миграции с МойСклад
(сравнительная таблица + 3 шага)
- /integrations — список интеграций
- /for-grocery|pharmacy|cafe|alcohol|clothing|household — 6 вертикалей
с уникальными фишками (весовой,
серии/сроки, модификаторы и комбо,
акцизные марки, размерные сетки,
гарантийные сроки)
- /signup — регистрация (React-island форма)
- /about /contacts /kb /blog /status /changelog — компания + ресурсы
- /legal/{offer,privacy,consent,requisites} — реальные юр.документы
из /tmp/legal/ как Astro content
collection (markdown с frontmatter,
динамический [slug].astro template,
720px max-width, line-height 1.7,
prose-legal стили)
- /sitemap.xml — ручной генератор (sitemap-плагин
конфликтует с Astro 4.16, заменён
на простой APIRoute)
React-острова (3):
- BusinessTariffBuilder — слайдеры + расчёт total + ссылка на signup
- SignupForm — email/password/orgName/phone/plan + валидация + agree
- FAQ — accordion 7 вопросов
API: новый POST /api/auth/signup создаёт Organization + AppUser
(Identity Admin role) + Owner Employee + полный bootstrap через
DevDataSeeder.SeedTenantReferencesAsync (units, price-types, store,
cassa, 6 ролей). Токены НЕ выпускает — фронт сразу делает обычный
/connect/token (password grant) и получает access/refresh без
дублирования OpenIddict-логики. На signup-форме — auth-bridge:
токены передаются через URL fragment в админку
APP_URL/auth-bridge#access=...&refresh=...&welcome=1, AuthBridgePage
кладёт в localStorage и редиректит на /?welcome=signup.
URL-домены через env-переменные (юзер ещё выбирает финальный):
- PUBLIC_SITE_URL — canonical/OG/sitemap (default https://food-market.kz)
- PUBLIC_APP_URL — admin/API endpoint (default https://food-market.zat.kz)
Nginx-конфиг для деплоя сайта — заготовка-template в
deploy/nginx/food-market-public.conf.template, не применён —
ждёт решения по домену.
Dockerfile multi-stage (node:20-alpine build → nginx:1.27 runtime),
build-args PUBLIC_SITE_URL/PUBLIC_APP_URL, deploy/nginx.conf gzip +
immutable cache + try_files для pretty URLs.
SEO: OG-теги, twitter-card, canonical, JSON-LD SoftwareApplication
схема, robots.txt → sitemap-index, lang=ru-KZ.
Admin-side: /auth-bridge route в food-market.web — принимает токены
из URL fragment, кладёт в localStorage (fm.access_token / fm.refresh_token),
редиректит на /. Fragment чтобы access_token не попадал в Referer.
23 страницы билдятся без ошибок. Контейнер собирается. Деплой на
конкретный домен — отдельным шагом после решения юзера.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
4acb51c270 |
feat(bridge): /quiet и /loud команды для управления PreToolUse прогресс-лентой
- /quiet → создаёт /tmp/cc-tg-quiet, pretool-hook сразу выходит, Stop hook продолжает работать (финальные ответы летят). - /loud → удаляет flag, прогресс-лента возобновляется. - /ping без изменений. На стенде bridge перезапущен, setWebhook 200. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
982141598a |
feat(infra): PreToolUse hook for Telegram progress feed + rate-limited batching
Hook /usr/local/bin/cc-tg-notify-pretool читает JSON через stdin (tool_name + tool_input) и шлёт короткую строку прогресса в Telegram перед каждым tool-вызовом — чтобы юзер видел «он работает» а не просто ждал финального ответа от Stop hook. Форматы (по требованию ТЗ): - Bash → 🔨 ${description} либо 🔨 Bash: ${command[:80]} - Edit/Write/Read → ✏️/📝/📖 + basename(file_path) - Grep/Glob → 🔍/🌐 + pattern[:30/50] - WebFetch/Search → 🌍/🔎 + url|query[:60] - Task → 🎯 + description[:60] - TodoWrite → skip (шумно) - прочие → 🔧 ${tool_name} Дебаунс через flock + фоновый sleep: - Каждый вызов append'ит строку в /tmp/cc-tg-pretool-buffer.txt и бампит /tmp/cc-tg-pretool-last (epoch ms) под flock'ом. - Спавнит фоновый sleep 1.5s; после пробуждения проверяет, чей timestamp в LAST — если не его, выходит молча. Только «последний» hook реально шлёт батч одним сообщением. - Это даёт пачке tool-вызовов один Telegram-апдейт, а не 5–10. - Если буфер длиннее 20 строк — режется хвостом (свежие важнее). Off-switch: touch /tmp/cc-tg-quiet — pretool-hook сразу выходит. Stop hook продолжает работать. Hook регистрируется в ~/.claude/settings.json под PreToolUse с matcher="" (все tools без фильтра); фильтр TodoWrite — внутри скрипта. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
7c40c11595 |
feat(infra): event-driven Telegram bridge — webhook + Stop hook
Полный отказ от 2-секундного polling tmux'а в пользу реактивной схемы:
OUTBOUND (server-Claude → Telegram) через Stop hook:
- /usr/local/bin/cc-tg-notify-stop (Bash) читает transcript из stdin
(Claude Code передаёт {transcript_path}), достаёт последнюю
assistant-запись с непустым text-блоком (jq), чанкует ≤4000 символов
с префиксом «🤖 [food-market]», POST'ит в Telegram через curl.
Логи /var/log/cc-tg-notify.log. Если turn без текстового ответа
(только tool calls) — выходит молча.
- Зарегистрирован в ~/.claude/settings.json под Stop event с пустым
matcher (все turns).
INBOUND (Telegram → bridge → tmux) через webhook:
- bridge.py переписан с run_polling на run_webhook listening
127.0.0.1:8765 на /tg-webhook. python-telegram-bot[webhooks]
(tornado) ставится через pip.
- При старте сам делает setWebhook к Telegram API с secret_token
из TELEGRAM_WEBHOOK_SECRET (osprandom 24 hex), Telegram присылает
его обратно в X-Telegram-Bot-Api-Secret-Token — PTB валидирует
до вызова handler'ов.
- Сохранены: whitelist по chat_id, paste-в-tmux через
send-keys -l + Enter, /ping команда. Удалён poll_and_forward,
diff/clean логика, recently_sent_lines дедуп — больше не нужны.
Nginx: новый location = /tg-webhook на food-market-stage.conf,
проксирует на 127.0.0.1:8765 с прокидыванием X-Telegram-Bot-Api-
Secret-Token. Smoke-test: curl с неверным секретом → 403.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
e4cba50ab6 |
feat(product-images): загрузка на диск сервера + галерея с лайтбоксом
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 31s
CI / Web (React + Vite) (push) Successful in 23s
Docker Images / API image (push) Successful in 42s
Docker Images / Web image (push) Successful in 27s
Docker Images / Deploy stage (push) Successful in 18s
Backend:
- ProductImagesController: GET list / POST multipart upload /
DELETE / POST set-main.
- Файлы лежат в $ContentRoot/uploads/products/{productId}/{guid}.{ext}
(volume /opt/food-market-data/uploads:/app/uploads в compose).
- В БД хранится относительный URL /uploads/products/{id}/{file}.
- UseStaticFiles на /uploads — публичная раздача (без auth).
- Допустимые расширения: jpg/jpeg/png/webp/gif, до 10 МБ.
- При первой загрузке картинка становится основной; Product.ImageUrl
синхронизируется с "основной".
- Удаление основной переводит "основной" флаг на следующую оставшуюся.
Web-nginx: /uploads/ проксируется на api:8080.
Web UI:
- Компонент <ProductImageGallery>: превьюшки 80×80 в грид,
при наведении — кнопки "сделать основным" и "удалить",
клик на превью → fullscreen lightbox с навигацией ←→ и счётчиком.
- В ProductEditPage убран инпут "URL изображения" (был технической
строкой для копипаста), вместо него блок "Изображения" с галереей.
Показывается только для уже сохранённого товара (есть id).
Docker compose: добавлен bind-mount /opt/food-market-data/uploads.
|
||
|
|
2fc6d207f3 |
feat(moysklad-import): async jobs с прогрессом + токен в настройках
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 36s
CI / Web (React + Vite) (push) Successful in 23s
Docker Images / API image (push) Successful in 42s
Docker Images / Web image (push) Successful in 25s
Docker Images / Deploy stage (push) Successful in 18s
Pain points:
1. Импорт на ~30k товарах проходит 15-30 мин, nginx рвал на 60s → 504.
2. При импорте/очистке ничего не видно — ни счётчика, ни прогресса.
3. Токен приходилось вводить каждый раз вручную.
Фиксы:
- Async-job pattern: POST /api/admin/moysklad/import-products и
/api/admin/cleanup/all/async возвращают jobId, реальная работа
в Task.Run. GET /api/admin/jobs/{id} — статус +
Total/Created/Updated/Skipped/Deleted/Stage/Message.
- ImportJobRegistry (singleton, in-memory) — хранит job-progress.
- MoySkladImportService обновляет progress по мере пейджинга
(в т.ч. счётчик Created/Updated/Skipped).
- Cleanup разбит на именованные шаги, Stage меняется по мере
"Товары…" → "Группы…" → "Контрагенты…".
- Токен per-organization: Organization.MoySkladToken + миграция
Phase3_OrganizationMoySkladToken. Endpoints:
GET/PUT /api/admin/moysklad/settings.
- Импорт-endpoints больше не требуют token в теле — берут из org.
- HttpContextTenantContext.UseOverride(orgId) — AsyncLocal-scope
для background tasks (HttpContext там нет, а query-filter'у нужен
orgId — ставим через override).
Nginx (host + web-container) получил 60-минутный timeout на
/api/admin/import/ чтобы старый sync-путь тоже не ронять (на
случай если кто-то вернёт sync call).
Web:
- MoySkladImportPage переработан: блок "Токен API" (save/test
mask), блок импорта с кнопками без поля токена.
- JobCard с polling каждые 1.5s отображает живые счётчики и stage.
- DangerZone тоже теперь async с live-прогрессом.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
9891280bfd |
deploy: mirror all base images into local registry — builds no longer need internet
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 35s
CI / Web (React + Vite) (push) Successful in 24s
Docker Images / API image (push) Successful in 6s
Docker Images / Web image (push) Successful in 5s
Docker Images / Deploy stage (push) Successful in 29s
Any block on mcr.microsoft.com or docker.io from KZ would stall our
builds. Mirror docker base images into 127.0.0.1:5001 under mirror/*
via daily systemd timer, and point Dockerfiles + compose + CI at the
local copies.
Mirror:
node:20-alpine → 127.0.0.1:5001/mirror/node:20-alpine
nginx:1.27-alpine → 127.0.0.1:5001/mirror/nginx:1.27-alpine
postgres:16-alpine → 127.0.0.1:5001/mirror/postgres:16-alpine
mcr.microsoft.com/dotnet/sdk:8.0 → 127.0.0.1:5001/mirror/dotnet-sdk:8.0
mcr.microsoft.com/dotnet/aspnet:8.0 → 127.0.0.1:5001/mirror/dotnet-aspnet:8.0
Infra (committed for reproducibility):
- deploy/mirror-base-images.sh — pull/tag/push (idempotent)
- deploy/food-market-mirror-base-images.{service,timer} — daily refresh,
installed on stage server
Usage in build-time:
- Dockerfile.api/web take ARG LOCAL_REGISTRY=127.0.0.1:5001 with the local
copy as default, so the same Dockerfile still builds from docker.io if
you pass --build-arg LOCAL_REGISTRY=docker.io (well, almost).
- docker-compose.yml postgres: image via ${REGISTRY}/mirror/postgres.
- ci.yml postgres service container: local mirror.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
e408647b4b |
ci(forgejo): trigger Docker Images workflow for first Forgejo run
Some checks failed
|
||
|
|
afbf01304a |
ops: Forgejo on git.zat.kz as primary, GitHub as mirror
Pushing straight to GitHub from KZ is a lottery — TCP to github.com times out often enough that git push becomes a flake. Fix: Forgejo runs on the stage server (sqlite, single container), all pushes go there first (local network, always reliable), a systemd timer mirrors the whole repo into GitHub every 10 minutes so GitHub stays up-to-date as a backup + CI source. What's committed here is the infra-as-code side: - deploy/forgejo/docker-compose.yml — Forgejo 7 on :3000 (HTTP) and :2222 (SSH) - deploy/forgejo/food-market-forgejo.service — systemd unit that drives compose - deploy/forgejo/mirror-to-github.sh + mirror timer/service — push to GH every 10 min - deploy/forgejo/nginx.conf — vhost for git.zat.kz (certbot to be run once DNS is set) - docs/forgejo.md — how to clone/push, operations, what's left for the user (DNS + certbot) GitHub Actions CI is untouched: commits land on GitHub via the mirror and the self-hosted runner picks them up as before. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
d455087bc8 |
feat(ops): Telegram <-> tmux bridge + local docker-registry unit
Telegram bridge lets me drive the local Claude Code tmux session from my phone — inbound messages are typed into the 'claude' session, pane diffs are streamed back as plain Telegram messages (TUI noise, tool-call blocks, echoed user input and already-sent lines are filtered so only the assistant's actual reply reaches the chat). Deployed as food-market-telegram-bridge.service, reads creds from /etc/food-market/telegram.env (not committed). Also committing the local docker-registry unit for reproducibility — registry:2 on 127.0.0.1:5001, data persisted in /opt/food-market-data/docker-registry. Setup docs in docs/telegram-bridge.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
a5f7060fb1 |
deploy: local docker registry at 127.0.0.1:5001 (primary), ghcr as backup
Stage's external pulls from ghcr.io flap on KZ network — the self-hosted runner pushes images into a local registry:2 (systemd-managed, /opt/food-market-data/docker-registry) and docker-compose now pulls from localhost:5001 via \$REGISTRY. ghcr.io is still tagged and pushed as off-site backup, but ghcr push failure no longer fails the build. Setup done on the host (not in workflow): - systemd unit food-market-registry.service (enabled, restart on failure) - /etc/docker/daemon.json: \"insecure-registries\": [\"127.0.0.1:5001\"] Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
75d73b9dcd |
ci/deploy: stage deploy workflow + notifications + server plan
.github/workflows/deploy-stage.yml: - Triggers on successful "Docker Images" workflow or manual dispatch. - SSHes to stage server via STAGE_SSH_KEY, copies deploy/docker-compose.yml and nginx.conf, writes .env with current SHA + POSTGRES_PASSWORD. - `docker compose pull && up -d --remove-orphans`. - Smoke-tests /health with 5 retries (5s each). - Pings Telegram on success/failure with commit SHA + stage URL. .github/workflows/notify.yml: - Separate workflow_run listener for CI/Docker failures, sends Telegram message with link to the failed run. deploy/docker-compose.yml port remap (stage server already uses 80/443/5000/5432): - API: 8080 (was 8080, confirmed free) - Web: 8081 (was 80 — taken by legacy nginx) - Postgres: 127.0.0.1:5434 (was 5433 — and now localhost-only, safer) docs/stage-setup.md — one-time server setup runbook: - Verified specs: Ubuntu 24.04, 4 CPU, 15 GB RAM, 4 GB free disk (tight). - Step 1: `sudo usermod -aG docker nns` so deploy doesn't need sudo. - Step 2: generate STAGE_POSTGRES_PASSWORD secret via `openssl rand`. - Step 3: port-conflict check. - Step 4: first manual deploy via gh workflow run. - Disk-usage monitoring via cron → Telegram when >85%. Secrets now in repo: TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID, STAGE_SSH_HOST, STAGE_SSH_PORT, STAGE_SSH_USER, STAGE_SSH_KEY Still needed from user: STAGE_POSTGRES_PASSWORD (one openssl command). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
5bcbff66de |
ci/deploy: GitHub Actions + Docker images + DB backup + 24x7 plan
.github/workflows/ci.yml — on push/PR:
- backend job: dotnet restore/build/test with a live postgres service
- web job: pnpm install + vite build + tsc, uploads dist artifact
- pos job: windows-latest, dotnet publish self-contained win-x64
single-file exe as artifact
.github/workflows/docker.yml — on push to main (if src changed) or manual:
- api image → ghcr.io/nurdotnet/food-market-api:{latest,sha}
- web image → ghcr.io/nurdotnet/food-market-web:{latest,sha}
- uses buildx + GHA cache
deploy/Dockerfile.api — multi-stage (.NET 8 sdk → aspnet runtime),
healthcheck on /health, App_Data + logs volumes mounted.
deploy/Dockerfile.web — node20 build → nginx 1.27 runtime; ships the
Vite dist + nginx.conf that proxies /api, /connect, /health to api
service and serves the SPA with fallback to index.html.
deploy/nginx.conf — SPA + API reverse-proxy configuration.
deploy/docker-compose.yml — production-shape stack: postgres 16 +
api (from ghcr image) + web (from ghcr image), named volumes, env-
driven tags so stage/prod can pin specific SHAs.
deploy/backup.sh — pg_dump wrapper with 3 modes: local (brew
postgres), --docker (compose container), --remote HOST:PORT. Writes
gzipped dumps to ~/food-market-backups, 30-day retention.
docs/24x7.md — explains where Claude/CI/stage live, which pieces
depend on the Mac, and the exact steps to hand off secrets via
~/.food-market-secrets/ so I can push them into GitHub Secrets.
Next, once user supplies Proxmox + FTP + Telegram creds: stage deploy
workflow, notification workflow, and (optional) claude-runner VM so
I no longer depend on the Mac being awake.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
fd2f5ae4f3 |
Phase 0: project scaffolding and end-to-end auth
- .NET 8 LTS solution with 7 projects (domain/application/infrastructure/api/shared/pos.core/pos[WPF]) - Central package management (Directory.Packages.props), .editorconfig, global.json pin to 8.0.417 - PostgreSQL 14 dev DB via existing brew service; food_market database created - ASP.NET Identity + OpenIddict 5 (password + refresh token flows) with ephemeral dev keys - EF Core 8 + Npgsql; multi-tenant query filter via reflection over ITenantEntity - Initial migration: 13 tables (Identity + OpenIddict + organizations) - AuthorizationController implements /connect/token; seeders create demo org + admin - Protected /api/me endpoint returns current user + org claims - React 19 + Vite 8 + Tailwind v4 SPA with TanStack Query, React Router 7 - Login flow with dev-admin placeholder, bearer interceptor + refresh token fallback - docs/architecture.md, CLAUDE.md, README.md Verified end-to-end: health check, password grant issues JWT with org_id, web app builds successfully (310 kB gzipped). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |