Архитектура и состав монорепы
Репозиторий — npm workspaces + Turborepo. Node 20+ (витрина требует 22.13+), npm 10.
Приложения
| Каталог | Пакет | Что это |
|---|---|---|
apps/backend | @dtc/backend | MedusaJS v2 (2.17.2): Store/Admin API, админка на /app, фоновые задачи |
apps/web | @auto-paint-store/web | Витрина, Next.js App Router + Tailwind |
apps/cms | @auto-paint-store/cms | Strapi: содержимое главной страницы, баннеры, вкладки |
apps/e2e | @auto-paint-store/e2e | Playwright |
apps/docs | apps-docs | Эта документация (Docusaurus) |
Инфраструктура
docker-compose.yml поднимает postgres, redis, meilisearch, minio, backend, cms, web
и одноразовые сервисы migrate, backend-init, minio-init, cms-db-init, cms-minio-init.
- Redis обязателен: на нём кэш, шина событий и движок workflow (
medusa-config.ts). - PostgreSQL — источник истины. Meilisearch — только публичный поисковый индекс каталога,
он синхронизируется по событиям (
subscribers/catalog-index-sync.ts). Любое расхождение решается в пользу данных Medusa: карточка товара дополнительно проверяет доступность через backend, чтобы снятый с публикации товар давал 404 даже при устаревшем индексе. - MinIO / S3 — изображения товаров и загрузки Strapi.
Как данные ходят между частями
Каталог и заказы. Витрина ходит в backend: стандартные Store API — для корзины и аккаунтов,
но не для оформления и не для чтения истории заказов покупателем — эти два кастомные: оформление
идёт через POST /catalog/checkout (placeCatalogOrderWorkflow), список/карточка/отмена
заказов покупателя — через store/account/orders/*. Плюс кастомные catalog/* там, где
стандартного API не хватает вовсе (поиск, карточка товара, валидация корзины). См. Кастомные
API.
Контент. Редактор пишет в Strapi → Strapi зовёт вебхук backend → backend перечитывает снимок. См. Контент из Strapi на витрину.
Остатки и заказы склада. Модуль moysklad-integration тянет остатки по расписанию и
отправляет заказы покупателей. См. Модуль МойСклад.
Фоновые задачи
apps/backend/src/jobs — расписание в config.schedule каждого файла:
| Задача | Расписание | Что делает |
|---|---|---|
cms-snapshot-refresh | каждую минуту | подстраховка вебхука Strapi |
moysklad-order-sync-retry | каждую минуту | повтор неудачной отправки заказа |
moysklad-stock-sync | каждые 15 минут | остатки из МойСклад |
expire-catalog-payment-carts | ежечасно | сброс корзин с брошенной оплатой |
poll-apiship-tracking-status | ежечасно | статусы отправлений ApiShip |
abandoned-cart-reminder | 03:00 | письмо о брошенной корзине |
sync-cdek-pickup-points-daily | 03:00 | справочник ПВЗ СДЭК |
Детали устройства платёжных/складских/доставочных заданий — в соответствующих страницах (Оплата, Модуль МойСклад, Доставка). Что стоит знать о самих job'ах отдельно:
expire-catalog-payment-cartsчитает платёжные сессии страницами по 100 записей до полного обхода (jobs/expire-catalog-payment-carts.ts:14).(*)Обработка корзин не изолирована try/catch — исключение одного workflow прерывает весь текущий цикл и оставляет последующие просроченные корзины до следующего запуска (jobs/expire-catalog-payment-carts.ts:72) — в отличие отmoysklad-stock-sync, где ошибка одной позиции не мешает остальным.abandoned-cart-reminderвыбирает только зарегистрированного покупателя с непустой корзиной, простаивающей больше 7 дней (abandoned-cart-reminder.ts:43). Повторное напоминание по той же корзине разрешается только после нового изменения корзины (:113).(*)Если SMTP уже отправил письмо, но запись об этом не сохранилась, ошибка лишь логируется — следующий запуск может отправить письмо повторно (jobs/abandoned-cart-reminder.ts:99). Отписка (workflows/opt-out-abandoned-cart-reminders.ts) добавляет флагabandoned_cart_reminders_opt_outчерез merge в существующийcustomer.metadata, не перезаписывая объект целиком — прямая заменаmetadataв этом workflow стёрла бы любые другие кастомные поля покупателя.cms-snapshot-refreshделает то же, что вебхук Strapi — ошибка одного прогона изолируется и только логируется, не роняет остальные задачи. Подробнее — Контент из Strapi.
Кастомные модули
apps/backend/src/modules: moysklad-integration, cms-snapshot, meilisearch,
site-content, payment-provider-config, shipping-provider-registry,
abandoned-cart-reminder, wishlist, notification-smtp.
Правило проекта: сначала стандартное решение Medusa, кастомный модуль — только там, где стандартного API действительно нет.