feat(domains): миграция на food-market.kz / admin.food-market.kz
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 58s
CI / Web (React + Vite) (push) Successful in 42s
Docker API / Build + push API (push) Successful in 1m38s
Docker Public / Build + push Public (push) Successful in 49s
Docker Web / Build + push Web (push) Successful in 37s
Docker API / Deploy API on stage (push) Successful in 18s
Docker Public / Deploy Public on stage (push) Successful in 9s
Docker Web / Deploy Web on stage (push) Successful in 11s

- Заменил все хардкоды URL в src/** и deploy/:
  food-market.zat.kz       → food-market.kz       (публичный сайт)
  app.food-market.zat.kz   → admin.food-market.kz (админ-API + SPA)
- public/SignupForm и Header: дефолт PUBLIC_APP_URL теперь
  https://admin.food-market.kz (раньше указывал на сам публичный домен,
  что было багом — фронт стучался не туда после переезда зон).
- public/Dockerfile ARG PUBLIC_APP_URL → admin.food-market.kz.
- API appsettings.json CORS — оставил только два прода-origin (localhost
  для dev живёт там же).
- Program.cs: добавил opts.SetIssuer(uri) если задан OpenIddict:Issuer
  в конфиге — иначе iss вычислялся из текущего HTTP-запроса и ломался
  при nginx-прокси без X-Forwarded-Proto.
- docker-compose стейджа: env OpenIddict__Issuer=https://admin.food-market.kz/
  + Cors__AllowedOrigins[0,1].

Nginx (на сервере, не в репе):
- /etc/nginx/conf.d/food-market.kz.conf, admin.food-market.kz.conf —
  новые конфиги с certbot-выданными сертификатами на оба домена
  (LetsEncrypt --webroot, действителен до 2026-07-29).
- Старые food-market.zat.kz / app.food-market.zat.kz переведены в
  301-редирект на новые домены (HTTP+HTTPS), серты zat.kz пока
  оставлены чтобы handshake шёл нормально.
This commit is contained in:
nns 2026-04-30 13:56:51 +05:00
parent 3849cb3547
commit 58df887f1c
11 changed files with 20 additions and 14 deletions

View file

@ -9,7 +9,7 @@ Config (/etc/food-market/telegram.env or env vars):
TELEGRAM_BOT_TOKEN bot token (required) TELEGRAM_BOT_TOKEN bot token (required)
TELEGRAM_CHAT_ID single whitelisted chat id (required) TELEGRAM_CHAT_ID single whitelisted chat id (required)
TELEGRAM_WEBHOOK_URL public URL Telegram should POST to TELEGRAM_WEBHOOK_URL public URL Telegram should POST to
(default: https://food-market.zat.kz/tg-webhook) (default: https://food-market.kz/tg-webhook)
TELEGRAM_WEBHOOK_SECRET random secret; bridge validates the TELEGRAM_WEBHOOK_SECRET random secret; bridge validates the
X-Telegram-Bot-Api-Secret-Token header on every X-Telegram-Bot-Api-Secret-Token header on every
incoming request and Telegram sends it back so incoming request and Telegram sends it back so
@ -143,7 +143,7 @@ def main() -> int:
token = env.get("TELEGRAM_BOT_TOKEN", "").strip() token = env.get("TELEGRAM_BOT_TOKEN", "").strip()
chat_id_raw = env.get("TELEGRAM_CHAT_ID", "").strip() chat_id_raw = env.get("TELEGRAM_CHAT_ID", "").strip()
secret = env.get("TELEGRAM_WEBHOOK_SECRET", "").strip() secret = env.get("TELEGRAM_WEBHOOK_SECRET", "").strip()
webhook_url = env.get("TELEGRAM_WEBHOOK_URL", "https://food-market.zat.kz/tg-webhook").strip() webhook_url = env.get("TELEGRAM_WEBHOOK_URL", "https://food-market.kz/tg-webhook").strip()
if not token or not chat_id_raw: if not token or not chat_id_raw:
print("ERROR: TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID required", file=sys.stderr) print("ERROR: TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID required", file=sys.stderr)
return 78 return 78

View file

@ -96,6 +96,14 @@
builder.Configuration["OpenIddict:AccessTokenLifetime"] ?? "01:00:00")); builder.Configuration["OpenIddict:AccessTokenLifetime"] ?? "01:00:00"));
opts.SetRefreshTokenLifetime(TimeSpan.Parse( opts.SetRefreshTokenLifetime(TimeSpan.Parse(
builder.Configuration["OpenIddict:RefreshTokenLifetime"] ?? "30.00:00:00")); builder.Configuration["OpenIddict:RefreshTokenLifetime"] ?? "30.00:00:00"));
// Явный Issuer = публичный URL админки. Без него OpenIddict
// вычисляет issuer из текущего HTTP-запроса, что ломается за
// nginx-прокси если Host/X-Forwarded-Proto не доходят. Берём
// из конфигурации (OpenIddict:Issuer / docker compose env).
if (builder.Configuration["OpenIddict:Issuer"] is { Length: > 0 } issuer)
{
opts.SetIssuer(new Uri(issuer));
}
}) })
.AddValidation(opts => .AddValidation(opts =>
{ {

View file

@ -31,10 +31,8 @@
"AllowedOrigins": [ "AllowedOrigins": [
"http://localhost:5173", "http://localhost:5173",
"http://localhost:4173", "http://localhost:4173",
"https://food-market.zat.kz",
"https://app.food-market.zat.kz",
"https://food-market.kz", "https://food-market.kz",
"https://app.food-market.kz" "https://admin.food-market.kz"
] ]
}, },
"AllowedHosts": "*" "AllowedHosts": "*"

View file

@ -11,7 +11,7 @@ COPY pnpm-lock.yaml* ./
RUN pnpm install --frozen-lockfile || pnpm install RUN pnpm install --frozen-lockfile || pnpm install
COPY . . COPY . .
ARG PUBLIC_SITE_URL=https://food-market.kz ARG PUBLIC_SITE_URL=https://food-market.kz
ARG PUBLIC_APP_URL=https://food-market.zat.kz ARG PUBLIC_APP_URL=https://food-market.kz
ENV PUBLIC_SITE_URL=$PUBLIC_SITE_URL ENV PUBLIC_SITE_URL=$PUBLIC_SITE_URL
ENV PUBLIC_APP_URL=$PUBLIC_APP_URL ENV PUBLIC_APP_URL=$PUBLIC_APP_URL
RUN pnpm build RUN pnpm build

View file

@ -1,3 +1,3 @@
User-agent: * User-agent: *
Allow: / Allow: /
Sitemap: https://food-market.zat.kz/sitemap-index.xml Sitemap: https://food-market.kz/sitemap-index.xml

View file

@ -83,7 +83,7 @@ const tmpl = ({ title, sub }) => ({
props: { props: {
style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between' }, style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between' },
children: [ children: [
{ type: 'div', props: { style: { color: '#0F172A', fontSize: '20px', fontWeight: 700 }, children: 'food-market.zat.kz' } }, { type: 'div', props: { style: { color: '#0F172A', fontSize: '20px', fontWeight: 700 }, children: 'food-market.kz' } },
{ {
type: 'div', type: 'div',
props: { props: {

View file

@ -5,7 +5,7 @@ import { fileURLToPath } from 'node:url'
const __dirname = path.dirname(fileURLToPath(import.meta.url)) const __dirname = path.dirname(fileURLToPath(import.meta.url))
const OUT = path.resolve(__dirname, '..', 'public', 'screenshots') const OUT = path.resolve(__dirname, '..', 'public', 'screenshots')
const APP = 'https://app.food-market.zat.kz' const APP = 'https://admin.food-market.kz'
const EMAIL = 'admin@food-market.local' const EMAIL = 'admin@food-market.local'
const PASSWORD = 'Admin12345!' const PASSWORD = 'Admin12345!'

View file

@ -1,6 +1,6 @@
--- ---
import Logo from './Logo.astro' import Logo from './Logo.astro'
const APP_URL = import.meta.env.PUBLIC_APP_URL || 'https://food-market.zat.kz' const APP_URL = import.meta.env.PUBLIC_APP_URL || 'https://food-market.kz'
--- ---
<header class="sticky top-0 z-40 bg-white/95 backdrop-blur border-b border-slate-200"> <header class="sticky top-0 z-40 bg-white/95 backdrop-blur border-b border-slate-200">
<div class="max-w-7xl mx-auto px-4 sm:px-6 h-14 flex items-center justify-between gap-4"> <div class="max-w-7xl mx-auto px-4 sm:px-6 h-14 flex items-center justify-between gap-4">

View file

@ -2,8 +2,8 @@ import { useState } from 'react'
import { validateEmail, validatePassword, validatePhone } from '@/lib/validation' import { validateEmail, validatePassword, validatePhone } from '@/lib/validation'
// Админский / API endpoint — переключается через PUBLIC_APP_URL на этапе // Админский / API endpoint — переключается через PUBLIC_APP_URL на этапе
// билда. Дефолт пока — текущий рабочий food-market.zat.kz. // билда. Дефолт — admin.food-market.kz (там и API и админ-SPA).
const APP_URL = (import.meta.env.PUBLIC_APP_URL as string | undefined) ?? 'https://food-market.zat.kz' const APP_URL = (import.meta.env.PUBLIC_APP_URL as string | undefined) ?? 'https://admin.food-market.kz'
const API_URL = APP_URL const API_URL = APP_URL
interface Props { interface Props {

View file

@ -1,7 +1,7 @@
--- ---
import BaseLayout from '@/layouts/BaseLayout.astro' import BaseLayout from '@/layouts/BaseLayout.astro'
const releases = [ const releases = [
{ date: '2026-04', title: 'Phase 6 — Публичный сайт', items: ['Маркетинговый сайт food-market.zat.kz','Регистрация без админа платформы','Тарифы Старт / Бизнес-конструктор / Сеть'] }, { date: '2026-04', title: 'Phase 6 — Публичный сайт', items: ['Маркетинговый сайт food-market.kz','Регистрация без админа платформы','Тарифы Старт / Бизнес-конструктор / Сеть'] },
{ date: '2026-04', title: 'Phase 4 — SuperAdmin консоль', items: ['Управление организациями','Журнал действий','Read-only «открыть как…»','Edit-mode с reason + audit-trail','Настраиваемый retention period'] }, { date: '2026-04', title: 'Phase 4 — SuperAdmin консоль', items: ['Управление организациями','Журнал действий','Read-only «открыть как…»','Edit-mode с reason + audit-trail','Настраиваемый retention period'] },
{ date: '2026-04', title: 'Phase 3 — Цены и роли', items: ['Расширенная модель цен','Сотрудники и роли','Системные роли (Администратор, Кассир)'] }, { date: '2026-04', title: 'Phase 3 — Цены и роли', items: ['Расширенная модель цен','Сотрудники и роли','Системные роли (Администратор, Кассир)'] },
{ date: '2026-04', title: 'Phase 2 — Закупки и приёмки', items: ['Документ приёмки со сканером','Скользящее среднее себестоимости','Inline quick-add позиций'] }, { date: '2026-04', title: 'Phase 2 — Закупки и приёмки', items: ['Документ приёмки со сканером','Скользящее среднее себестоимости','Inline quick-add позиций'] },

View file

@ -2,7 +2,7 @@ import { Logo } from '@/components/Logo'
import { logout } from '@/lib/auth' import { logout } from '@/lib/auth'
import { useMe } from '@/lib/useMe' import { useMe } from '@/lib/useMe'
const PUBLIC_SITE = (import.meta.env.VITE_PUBLIC_SITE_URL as string | undefined) ?? 'https://food-market.zat.kz' const PUBLIC_SITE = (import.meta.env.VITE_PUBLIC_SITE_URL as string | undefined) ?? 'https://food-market.kz'
/** Fallback-экран для AppUser без активного Employee в живой Organization. /** Fallback-экран для AppUser без активного Employee в живой Organization.
* Возможные причины: его org удалена SuperAdmin'ом, его Employee * Возможные причины: его org удалена SuperAdmin'ом, его Employee