Этап 1a: фундамент

- Next 16.4 и Payload 3.91 в одном приложении, PostgreSQL с UUID,
  TypeScript 6, ESLint 9, Prettier, Vitest
- Payload: администраторы с ролями, multi-tenant по сайтам, локализация
  en/ru/de/fr/es, интерфейс админки ru/en, языки сайта (sites.locales)
- Коллекции sites, domains, currencies, countries, locations, tags,
  tariffs, payment-systems (manual), media и начальные данные (pnpm seed)
- Proxy: хост → домен, язык и валюта, внутренние маршруты по хосту;
  /admin и /api закрыты на доменах брендов
- Токены и темы брендов, оболочки сайта и кабинета, переключатели языка,
  валюты и темы, заглушки страниц
- /svc/health, юнит systemd veilix-dev.service
- Документация: решения D-022…D-024, команды в CLAUDE.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
andreypr207andClaude Opus 5.5 committed 2026-10-21 05:08:05 +02:00
1 parent 3400f58a5a
commit 7e331c2c9d
140 files changed
+15885 -43

No files matched your search

+54
View File
@@ -0,0 +1,54 @@
import config from '@payload-config'
import { getPayload } from 'payload'
import { cache } from 'react'
import {
type DomainApp,
type DomainConfig,
DOMAINS_TTL_MS,
type SiteCode,
toDomainConfig,
} from './types'
/** Активные домены из Payload */
export const listActiveDomains = async (): Promise<DomainConfig[]> => {
const payload = await getPayload({ config })
const { docs } = await payload.find({
collection: 'domains',
where: { active: { equals: true } },
depth: 1,
pagination: false,
overrideAccess: true,
})
return docs.map(toDomainConfig).filter((domain) => domain !== null)
}
let cached: { at: number; domains: Promise<DomainConfig[]> } | null = null
const activeDomains = (): Promise<DomainConfig[]> => {
if (!cached || Date.now() - cached.at > DOMAINS_TTL_MS) {
const domains = listActiveDomains()
cached = { at: Date.now(), domains }
domains.catch(() => {
cached = null
})
}
return cached.domains
}
/** Домен по хосту для страниц; null — домена нет или он выключен */
export const getDomain = cache(async (hostname: string): Promise<DomainConfig | null> => {
const domains = await activeDomains()
return domains.find((domain) => domain.hostname === hostname) ?? null
})
/** Основной домен сайта нужного типа: на него ведут ссылки между сайтом и кабинетом */
export const getPrimaryDomain = cache(
async (site: SiteCode, app: DomainApp): Promise<DomainConfig | null> => {
const domains = await activeDomains()
return (
domains.find((domain) => domain.site === site && domain.app === app && domain.isPrimary) ??
null
)
},
)