Два окна редактора статей pacod-cms, светлое и тёмное, наложенные друг на друга, в каждом статья Starward Forty-eight hours on Halcyon Station с панелями публикации и авторов рядом с текстом.
Редактор статей в студии днём и ночью: текст, а рядом статус, дата, авторы и рубрики статьи. Сама студия говорит на английском, болгарском, немецком и русском.

pacod-cms — система управления контентом с открытым исходным кодом для редакционных сайтов. Одна инсталляция обслуживает множество сайтов, у каждого из которых своя база данных, файлы и кэш; редакторы работают в многоязычной студии, а читатели получают быстрые публичные сайты с маршрутизацией по хосту.

Управление контентом

  • Лицензия: Apache-2.0
  • Версия: 0.0.1 · Предварительная
  • Языки: Java
  • Технологии: Java 25, Spring Boot 4.1, Thymeleaf, MongoDB, Maven, Caddy, Gotenberg, Docker
  • Открыть на GitHub · Создать issue

Возможности

Редакционное ядро

Статьи, рубрики, авторы, теги и постоянные страницы, а также общая библиотека изображений для всего сайта.

Мультисайтовость изначально

У каждого сайта своя база данных MongoDB, файлы и кэш, и он отвечает на тех хостах, которые за ним закреплены.

Реклама и статистика

Размещение рекламы с конструктором HTML5-баннеров, страница link-in-bio для профилей в соцсетях и статистика по читателям, рекламе и краулерам, чтобы редакторы видели, что читают и что индексируют. Размещение по отдельным рубрикам и иконка GitHub для страницы ссылок на подходе (ещё не выпущено, в ветке blog-site).

Многоязычность

Контент на многих языках с настраиваемым языком оригинала и AI-перевод статьи или целого сайта в виде задачи, где любой ответ отклоняется, если в нём не сохранена в точности разметка статьи; сама студия — на английском, болгарском, немецком и русском. Русский язык для собственных надписей публичного сайта на подходе (ещё не выпущено, в ветке blog-site).

AI-помощь в написании

Заголовки, целая статья по заданию, фрагмент в позиции курсора и текст, свёрстанный стилизованными блоками без потери хотя бы одного слова автора.

AI-дизайн

Тема, макет страницы или дизайн всего сайта по нескольким словам, как доработка текущего черновика; только черновики, с одной отменой.

Организации и роли

Организации, сайты и приглашения, с ролями на уровне организации и сайта, которые хранятся в pacod-id.

PDF и выпуски журнала

PDF статей в виде фоновой задачи, в печатном оформлении сайта или в научном формате в две колонки, с аннотацией и ключевыми словами из статьи или написанными AI; выпуск журнала собирает статьи в один PDF на каждый язык с обложкой, редакционной статьёй, содержанием и нумерацией страниц и публикуется в архиве сайта.

Экспорт и восстановление

Сайт или целая организация скачивается одним ZIP-файлом и восстанавливается как новый — в этой инсталляции или в другой.

Диаграмма платформы с выделенным pacod-cms и приглушёнными остальными проектами: линия от людей к pacod-cms, зелёная пунктирная связь с pacod-gw и дальше к моделям и золотая пунктирная связь с pacod-id.
Место pacod-cms. Люди пользуются его студией и сайтами (серый). AI-функции студии обращаются к модели только через pacod-gw (зелёные точки), а вход в студию идёт через pacod-id (золотые точки); читатели сайтов нигде не входят. pacod-demo запускает его вместе с другими проектами.

Скриншоты

Главная страница Starward в окне браузера: шапка с семью рубриками и полем поиска, главный материал Why the stars do not twinkle in space на фоне звёздного поля с зелёной туманностью и реклама Aurelian Lines ниже.
Starward, демонстрационный сайт на наборе шаблонов magazine: главный материал на всю ширину поверх фотографии NASA, а под ним HTML5-баннер одного из его вымышленных рекламодателей.
Экран Websites в pacod-cms со списком Demo Journal, Demo Notes и Starward в одной демонстрационной организации, у каждого адрес, язык en и активный статус, с кнопками для нового сайта и восстановления сайта.
Одна инсталляция, много сайтов: список сайтов в студии, у каждого организация, адреса, язык содержимого и статус. У каждого своя база данных, папка с файлами и записи в кэше.
Экран «Дизайн» pacod-cms для Starward: набор шаблонов Magazine с описанием, таблица из шести типов страниц от главной до поиска, каждый отмечен как собственный макет сайта и с кнопкой Edit, и тема с 26 заданными значениями.
Экран «Дизайн»: набор шаблонов сайта, макет для каждого из шести типов страниц и тема, всё меняется в студии без кода.
Тёмный экран темы pacod-cms для Starward с двумя готовыми вариантами, Nightfall и Newsprint, над таблицей цветов, таких как страница, карточки, линии, текст и акцент, у каждого шестнадцатеричное значение для дня и ночи, и кнопки Preview и Publish.
Редактор темы ночью: начните с готового варианта набора, Nightfall или Newsprint, затем задайте каждому цвету дневное и ночное значение. Переменные общие для всех наборов шаблонов, поэтому тема переживает смену набора.
Экран рекламы pacod-cms для Starward со списком из шести активных размещений для Aurelian Lines, Kestrel & Vane, Rimstone Lodge, Nightglass Tours и Lumen Assurance, в боковой колонке или вверху, у каждого свои страницы и счётчики.
Экран рекламы: каждое размещение с типом, позицией, порядком, страницами, на которых оно идёт, периодом, показами и кликами. Пять вымышленных рекламодателей Starward показывают написанные вручную HTML5-креативы и одно статичное изображение.

Один стек, множество сайтов

pacod-cms обслуживает множество сайтов множества организаций из одного стека. У каждого сайта своя база данных MongoDB, своя папка с файлами и свои записи в кэше, и он отвечает на тех хостах, которые за ним закреплены. Любой зарегистрировавшийся может создать организацию, стать её администратором, создавать в ней сайты и приглашать людей для работы над ними; лимиты задаёт администратор платформы.

Архитектура исходит из того, что владелец сайта может быть враждебен ко всем остальным в инсталляции. Сайты выполняют скрипты, написанные их владельцами, — HTML5-баннеры, менеджеры тегов, тексты статей, — и платформа должна держать эти скрипты и эти данные там, где им место.

Как устроены части

Часть Что делает
pacod-cms-core Библиотека: документы, сервисы, мультиарендность, управляющий слой, начальное содержимое, модель дизайна и порт движка правил pacod-id на Java.
pacod-cms-site Публичный сайт сразу для всех сайтов: страницы, JSON только для чтения под /web/v1/**, медиафайлы, счётчики трафика.
pacod-cms-studio Студия: портал для организаций, сайтов и участников, экраны редактирования, фоновые задачи, AI.
Caddy Входная дверь, маршрутизирует по хосту: хост студии — в студию, хост pacod-id — в pacod-id, любой другой хост — на сайт.
Gotenberg Создаёт PDF статей и выпусков журнала из версий сайта для печати.

Два приложения никогда не обращаются друг к другу. Оба работают с MongoDB напрямую, поэтому правка, сделанная в студии, доходит до публичного сайта через базу данных; кэши сайта истекают через 60 секунд, а не сбрасываются, так что правка появляется на сайте в течение минуты.

От хоста до страницы

Каждая единица работы относится ровно к одному сайту, который привязан к ней на всё время её выполнения.

  1. Caddy передаёт запрос сайту. В продакшене Caddy запрашивает сертификат сайта при первом посещении, но только после того, как сайт подтвердит, что хост принадлежит активному сайту.
  2. Сайт ищет хост в управляющем слое раньше, чем выполняется что-либо ещё. Хост, который не закреплён ни за одним активным сайтом, получает простой 404, а не сайт по умолчанию.
  3. Когда сайт привязан, каждая операция с MongoDB идёт в базу данных этого сайта. Если сайт не привязан, слой доступа к данным выбрасывает ошибку, а не угадывает.
  4. Макет типа страницы — области с упорядоченными секциями — разрешается относительно набора шаблонов сайта, а данные каждой секции загружаются заранее. Шаблоны никогда не делают запросов; секция, данные которой не загрузились, пропускается и записывается в лог, а страница всё равно отображается.

В студии сайт определяется по пути (/sites/<id>/mgmt/…); фоновая работа привязывает тот сайт, для которого выполняется.

Где хранятся данные

Где Что
pacod_cms Управляющий слой: организации, сайты, домены, настройки платформы, очередь задач.
site_<id> Отдельная база данных для каждого сайта: статьи, страницы, изображения, баннеры, меню, настройки, макеты, тема, статистика.
sites/<id>/ Файлы сайта: изображения к статьям, рекламные креативы, PDF. PDF черновика выпуска лежат в закрытой папке, которую отдаёт только студия.
pacod-id Люди, сессии и назначенные роли. CMS не хранит учётных записей.

Идентичность сайта — название, логотипы, контактные данные, идентификаторы сторонних сервисов — это данные, а не разметка. Сторонний идентификатор выводится, только если он задан и имеет формат своего поставщика, поэтому новый сайт не загружает никакого отслеживания. Исходный язык содержимого сайта — последний запасной вариант при отсутствии перевода и юридически определяющий текст. Канонический адрес, hreflang и метаданные статьи в head каждой страницы для читателей, а также Atom-ленты для каждого сайта и каждой рубрики на подходе (ещё не выпущено, в ветке blog-site).

Дизайн без кода

В разделе Дизайн студии каждый сайт выбирает один из трёх наборов шаблонов — editorial, главная страница из сеток статей с колонкой рядом с содержимым; minimal, одна спокойная колонка; или magazine, построенный на изображениях, с главным материалом во всю ширину, фотографиями мозаикой и статьями, которые открываются своим снимком, — и составляет макет для каждого типа страницы: главная, статья, рубрика, последние, постоянная страница и поиск. Среди секций — списки статей (последние, из рубрики или самые читаемые), форматированный текст, призыв к действию, поле поиска и ссылки на соцсети; секция, показывающая текст постоянной страницы на языке читателя, на подходе (ещё не выпущено, в ветке blog-site). Тема задаёт цвета со значениями для ночного режима, шрифты и ширины.

Текст статьи может содержать стилизованные блоки из закрытого списка: вводный абзац, врезку с примечанием, вынесенную цитату, врезку с главными фактами, две колонки и иллюстрацию с подписью — обычную или прижатую влево или вправо. Сайты оформляют их по теме, в том числе в версиях для печати, а значит, и в PDF, а редактор показывает их так, как увидят читатели. Пять из них есть в меню Блоки редактора; AI-вёрстка может использовать все шесть. Примеры кода, вставляемые из редактора и подсвечиваемые на сайтах с помощью Prism, на подходе (ещё не выпущено, в ветке blog-site).

Смена набора сначала показывает, что будет потеряно, и ничего не удаляется: секции, которые новый набор не может показать, сохраняются и возвращаются при обратной смене. Макеты и тема остаются черновиками, пока их не опубликует тот, у кого есть на это право, — кто бы их ни написал, человек или AI. Помощник по дизайну набрасывает тему, макет или дизайн всего сайта по нескольким словам; подробности — в разделе об AI ниже. Предпросмотр показывает черновики с собственного адреса сайта, в ширине компьютера, планшета или телефона, по подписанному токену, действующему 30 минут, и ничего не учитывает в статистике.

Новый набор шаблонов — это код, который устанавливает оператор, а не загружаемый файл: шаблон Thymeleaf может всё, что может сам сайт.

PDF и выпуски журнала

Долгая работа выполняется в студии как задача, не более одной на организацию одновременно, с живым логом и кнопкой отмены. Экспорт в PDF — одна из таких задач: Gotenberg превращает каждую статью в отдельный PDF на каждом языке, на котором она написана, а статья ссылается на свой PDF, как только он появится.

  • Два формата. Стандартный — это версия сайта для печати. Научный — макет научной статьи: шапка на всю ширину с заголовком, авторами, аннотацией и ключевыми словами, затем текст в две колонки.
  • Два способа создания. Только код берёт аннотацию и ключевые слова из описания и тегов статьи. С AI поручает pacod-gw написать аннотацию, ключевые слова и короткий колонтитул для каждой статьи и каждого языка. Текст статьи никогда не меняется.
  • Настройки PDF хранят значения по умолчанию для каждого сайта: формат и способ, бумагу (A4, Letter или A5) и поля, название журнала на каждом языке и его ISSN, колонтитул и номера страниц. Любой запуск может выбрать другой формат и способ.

Выпуск журнала собирает статьи в один PDF на каждый язык. У него есть том, номер и месяц, заголовок и редакционная статья на каждом языке и, по желанию, изображение для обложки; статьи выбираются по одной или подбираются по его месяцу, а затем упорядочиваются. При генерации создаются обложка, редакционная статья, содержание с номерами страниц, а затем статьи, с закладкой для каждой статьи и сквозной нумерацией страниц. PDF черновика выпуска скачиваются только из студии. После публикации выпуск и его PDF появляются на сайте по адресу /issues и в карте сайта.

Модель безопасности

Кто что может

Учётные записи и роли принадлежат pacod-id. Токены не несут ролей: студия следит за потоком назначенных ролей pacod-id и сама решает каждое разрешение по манифесту CMS (authz/cms.yaml). Роли таковы: администратор платформы, провижнер, администратор организации, администратор сайта, дизайнер, редактор, автор и аналитик — на уровне платформы, организации или сайта. Автор пишет; редактор ещё и публикует.

  • Каждый обработчик студии объявляет нужное ему разрешение; обработчик, который ничего не объявил, отклоняется. Неявных ролей нет.
  • Если поток pacod-id не отвечал минуту, студия отвечает 503, а не принимает решение по ролям, которые, возможно, уже отозваны. Публичные сайты это не затрагивает.

Сайты изолированы друг от друга

  • Студия и pacod-id находятся на другом регистрируемом домене, чем сайты, иначе студия не запускается. В противном случае скрипт сайта мог бы устанавливать для них cookie и обойти их защиту от CSRF. Инсталляция, все сайты которой доверенные, например демо на одном домене, может принять этот риск с PACOD_SITE_DOMAIN_SHARED=true: студия тогда запускается с предупреждением, а сайт всё равно никогда не получит собственный хост студии или pacod-id.
  • Студия никогда не отдаёт HTML или скрипты сайта. Предпросмотр черновиков и HTML5-баннеров встраивается с собственного origin сайта.
  • Gotenberg изолирован: он может обращаться только к внутреннему адресу сайта и не выполняет JavaScript.
  • До читателя доходят только опубликованные статьи — на каждом маршруте, в каждом списке, карте сайта и PDF. Черновик выпуска журнала никогда не бывает публичным.

AI, который не трогает ваши ключи

Каждый вызов AI проходит через pacod-gw; студия никогда не хранит ключ поставщика. Для новых организаций AI по умолчанию выключен: администратор платформы включает его для организации, а администратор сайта может отключить его для своего сайта.

Тексты

В редакторе статей помощь в написании работает с текстом в его текущем виде, сохранён он или нет:

  • Переформулировать выделенный текст шестью способами: яснее, короче, подробнее, без ошибок, официальнее или дружелюбнее.
  • Заголовки: до трёх вариантов заголовка статьи.
  • Черновик целой статьи — заголовок, описание и текст — по заданию, короткий, средний или длинный (около 300, 600 или 1200 слов), на исходном языке сайта. Используются только факты из задания; там, где факта не хватает, ставится заполнитель в квадратных скобках, который заполнит автор, и ничего не выдумывается.
  • Написать в позиции курсора: фрагмент по заданию, на языке и в манере окружающего текста, по тому же правилу о фактах.
  • Сверстать выделенный фрагмент или весь текст: заголовки, списки, выделения и стилизованные блоки. Каждое слово автора должно остаться на своём месте по порядку, иначе ответ отклоняется. Текст с фреймом, видео, скриптом или формой отклоняется ещё до вызова, потому что вёрстка бы их потеряла.
  • Анонс и теги для статьи и альтернативный текст на каждом языке, который модель компьютерного зрения пишет по самому изображению.

Ответы очищаются до короткого списка тегов и схем ссылок (http, https, mailto, относительные). Класс сохраняется, только если он называет стилизованный блок на его собственном элементе; никаких style, скриптов, изображений, которых не было в тексте автора, и ссылок на адреса, которых не давали текст автора, задание или статья. Числа, имена и вынесенные цитаты, которые есть в ответе, но нет в его материале, перечисляются рядом с ним, чтобы автор их проверил, — это эвристика, а не доказательство. Переформулировка, фрагмент или вёрстка сначала показываются в предпросмотре и вставляются одним шагом отмены; черновик заполняет форму. Ничего не сохраняется, пока автор не сохранит.

Дизайн

  • Помощник по дизайну набрасывает тему или макет одного типа страницы по нескольким словам. По умолчанию он дорабатывает текущий черновик, который видит модель, и меняет только то, о чём просит задание; по запросу начинает заново. Каждый ответ проверяется так, будто его ввёл оператор, а один щелчок отменяет изменение или возвращает его.
  • Дизайн всего сайта — тема и макет каждого типа страницы — составляется по одному заданию в виде задачи. Каждый шаг самостоятелен, так что неудавшийся шаг не мешает остальным; задачу можно безопасно запустить заново, отменить, и она учитывает свою стоимость. У сайта одновременно бывает не больше одной задачи дизайна.

В обоих случаях меняются только черновики; публиковать по-прежнему должен тот, у кого есть право.

Перевод и PDF

  • Перевод статей выполняется как задача. Переведённый текст должен в точности сохранить разметку оригинала — меняться могут только alt, title, aria-label и placeholder, — иначе он отклоняется.
  • Перевод сайта охватывает постоянные страницы, названия разделов, меню, страницу ссылок и, если отмечено, теги, не перезаписывая правки, сделанные во время выполнения; не более 2000 вызовов за запуск.
  • Шапка PDF: для PDF, созданных с AI, аннотация, ключевые слова и колонтитул для каждой статьи и каждого языка, проверенные как обычный текст. Они используются повторно, пока статью не отредактируют.

Что должен пройти каждый ответ

  • Каждый ответ должен соответствовать строгой JSON-схеме. Оборванный ответ или отказ модели считается ошибкой, и ничего из него не записывается.
  • Текст человека передаётся как материал, и модели сказано, что он никогда не является инструкцией, даже если звучит как она.
  • Шлюз видит только непрозрачные идентификаторы, никогда не имена и не адреса электронной почты.

Три окружения, один Makefile

dev stage prod
Входная дверь HTTP, плюс HTTPS от локального CA Caddy HTTPS (Let's Encrypt) HTTPS (Let's Encrypt или собственная пара из сертификата и ключа)
Почта pacod-id перехватывает mailpit перехватывает mailpit ваш релей
AI мок-шлюз мок-шлюз ваш pacod-gw или выключен
Демонстрационные сайты да да нет

Stage — это конфигурация продакшена с перехваченной почтой и имитацией AI. make check не даёт запуститься, пока не хватает секрета или конфигурация небезопасна. Делайте резервные копии .env, баз данных MongoDB, файлов сайтов и PostgreSQL pacod-id. /mgmt-health студии проверяет только, что она жива: студия, которая не может достучаться до pacod-id, остаётся «здоровой», пока её экраны отвечают 503, и сообщает об этом в своём логе. Сайт и студия пишут логи в общем формате pacod, в виде JSON или текста (APP_LOG_FORMAT).

Экспорт и восстановление переносят сайт из одной инсталляции в другую. Сайт или целая организация скачивается из студии одним ZIP-файлом с содержимым и файлами, без статистики посещений, участников и хостов. Восстановление проверяет весь архив, прежде чем что-либо создать, и всегда создаёт новый сайт или новую организацию — в этой инсталляции или в другой; загрузки до 2 GiB принимаются только по этим двум путям. Резервные копии всей платформы — по-прежнему дампы MongoDB.

Новая инсталляция в dev или stage создаёт три демонстрационных сайта: Demo Journal (editorial), Demo Notes (minimal) и Starward (magazine) — журнал о космических путешествиях по вымышленной звёздной системе: 24 объёмные статьи, в которых использованы все стилизованные блоки, пять авторов, собственные страницы, меню, макеты, тема и два опубликованных выпуска журнала, 37 фотографий NASA с указанием авторства и пять вымышленных рекламодателей с HTML5-креативами. make site-create … DEMO=space или make seed SITE=<id> DEMO=space даёт другому сайту содержимое Starward. Наполнение сайта из собственной папки (SEED_DIR=<folder>), которая целиком проверяется до того, как что-либо будет записано, на подходе (ещё не выпущено, в ветке blog-site).

Каждое окружение работает в pacod-net, Docker-сети, общей для всех проектов pacod. Адреса, к которым обращаются только серверы, например внутренний адрес pacod-id, находятся во второй, внутренней сети pacod-admin-net, к которой подключается студия. Собственные сервисы CMS — сайт, студия, MongoDB и Gotenberg — описаны в одном compose-файле; её собственные копии общих компонентов — Caddy, pacod-id со своим PostgreSQL, шлюз для разработки и перехватчик почты — во втором, который Makefile загружает вместе с первым. На одной машине помещается только один стек с общими компонентами; работа за Caddy и pacod-id, которые уже запущены на хосте (PROXY_MODE=external), на подходе (ещё не выпущено, в ветке blog-site). pacod-demo загружает только первый файл и запускает CMS вместе с остальными проектами за одним Caddy, одним pacod-id, одним PostgreSQL и одним шлюзом; он доступен по адресу demo.pacod.io.

Что протестировано и каковы ограничения

  • Модульные тесты запускаются с болгарским в качестве языка оригинала, чтобы проверялась именно конфигурация не по умолчанию.
  • Интеграционные тесты запускают два сайта в одной настоящей MongoDB и проверяют, что они остаются изолированными: маршрутизация, определение хоста, кэши, хранилище и статистика.
  • make e2e проверяет вход и портал на запущенном стеке.
  • Новые обращения к AI — карточка черновика, список заголовков, доработка и отмена в помощнике по дизайну, задача дизайна и список фактов для проверки — на запущенном стеке ещё не проверены.

Ограничения и что почитать

Известные ограничения v1:

  • У идентичности сайта (siteSettings) пока нет экрана в студии; она редактируется в базе данных.
  • Демонстрационное содержимое написано на английском, поэтому его может получить только сайт, язык содержимого которого — английский.
  • Список участников не показывает роли уровня платформы.

Дорожная карта перечисляет, что v1 откладывает, в том числе API для записи, ключ шлюза и бюджет на AI для каждой организации, отдельный обработчик, хранилище S3 и импорт содержимого.

В репозитории:

Быстрый старт

Машина разработчика

make init
make up
open http://studio.localhost:8000/

Ещё один сайт из командной строки

make site-create SLUG=acme NAME="Acme News" HOSTS=acme.localhost DEMO=1

Работает с

  • pacod-id: Студия выполняет вход пользователей через pacod-id, где также хранятся их роли на уровне организации и сайта.
  • pacod-gw: AI-функции работают через шлюз — студия никогда не хранит ключ провайдера.
  • pacod-demo: Запускает CMS вместе с остальными проектами за одним Caddy, одним pacod-id и одним шлюзом — живая студия и три демонстрационных сайта.

Собственный код проекта распространяется под Apache-2.0. Студия включает редактор TinyMCE (GPL-2.0-or-later), поэтому собранная студия в целом распространяется на условиях GPL-3.0. Публичный сайт его не содержит.