Источник:
smarty-code/smarty-backend-stable/smarty-system-events/docs/API.md
smarty-system-events — API и контракты событийДата актуальности: 2026-06-13
Подсистема:smarty-backend-stable/smarty-system-events
Подход: contract-first. Для каждого события — вход (payload), продюсер/консьюмер,
выход/побочные эффекты. Архитектура и потоки — см.ARCHITECTURE.md.
system-events.msg.type) — имя события; по нему HandlersMap выбирает обработчик.user.systemEvent(type, data)smarty-db/models/system/user.js:269):| Поле | Тип | Назначение |
|---|---|---|
time |
Number |
Метка времени публикации (Date.now()). |
eventData |
Object |
Полезная нагрузка конкретного события (таблица §3, колонка «payload (eventData)»). |
userMeta |
Object |
Метаданные запроса инициатора: platform, ip, agent, city, deviceName, clientVersion и т.п. (из getMeta()). |
_user |
ObjectId |
Инициатор события. Используется обёрткой userEvent для загрузки User. |
employee.systemEventдополнительно подмешивает_wsIdвeventData.
События, опубликованные напрямую черезemitBgEvent.sendEvent(dialogHook,
feedbackCrash,individualRateAskPromo,deleteUser,rateConfirm), используют
свой форматdata(без_user/userMeta).
| Обёртка | Сигнатура, передаваемая в обработчик | Предусловие |
|---|---|---|
userEvent(fn) |
fn(user, { data }) |
User по data._user существует; иначе обработчик не вызывается (возврат null). |
adminEvent(fn) |
fn(adminUser, { data }), где data.userMeta перезаписан на мету админа |
Найден сотрудник-админ РП по data.eventData._wsId; иначе no-op. |
| без обёртки | fn({ data }) |
Нет. |
Зарегистрировано в events/index.js. 27 событий регистрируются безусловно; 28-е —
logCityChange — только при ENABLE_LOGCITYCHANGE. Колонка «Обёртка»: userEvent (U),
adminEvent (A), без обёртки (—).
| # | Событие | Обёртка | Продюсер (где эмитится) | Payload (eventData, если не указано иное) |
Эффект (выход / побочные эффекты) |
|---|---|---|---|---|---|
| 1 | dialogHook |
— | smarty-dialog/routes/hook.js; переэмитится обработчиками 5/11/18/19/20/28. Прямой data: { wsApiId?, token, body } |
wsApiId?, token, body |
Находит WsObjectToken по token; если модель Hookable и объект существует — object.hookExecute({ profile, body }). Иначе тихо выходит. |
| 2 | pbxIntegration |
U | api-rest/routes/workspace/workspace.js:129 |
{ _wsId } |
Письмо в support «Запрос интеграции с АТС». |
| 3 | accessRightCreated |
U | smarty-db/models/workspace/rights/access_right.js:730 |
{ _rightId } |
Письмо в support «Пользователь создал право доступа». |
| 4 | activateOffer |
U | smarty-db/models/system/promocode/promoCode.js:150 |
{ _offerId, _wsId } |
Грузит PromoCode; письмо о выбранной акции/промокоде (текст зависит от code). Нет промокода — no-op. |
| 5 | balanceFill |
U | api-rest/events/payApply.js:32 (employee.systemEvent) |
{ _wsId, paymentPack, platform } |
Чат-хук (dialogHook, токен <секрет>) с суммой платежа + письмо «Пользователь пополнил баланс РП». |
| 6 | individualRateAsk |
U | smarty-auth/routes/user.js:134 |
{ name, email, phone } |
Письмо «Запрос на индивидуальный тариф» с контактами. |
| 7 | individualRateAskPromo |
— | smarty-auth/routes/user.js:140 (emitBgEvent.sendEvent) |
eventData: { name, email, phone } |
Письмо «...условия тарифа Premium» по шаблону promo_event; Reply-To = email (если валиден), иначе no-reply@…. |
| 8 | inviteSent |
U | api-rest/routes/workspace/employees.js:154 |
{} |
Письмо «Пользователь пригласил своего сотрудника». |
| 9 | noPhoneConfirm |
U | smarty-db/.../AccountPlugin.js:119 (delaySystemEvent, +2 ч); снимается AccountWithConfirmation.js:87 |
{} |
Письмо «Зарегистрировался, но не смог подтвердить телефон» (если к дедлайну не снято). |
| 10 | noVisitForFiveDays |
U | smarty-db/.../AccountPlugin.js:393 (delaySystemEvent, +5 дн) |
{} |
Письмо «Пользователь не заходил больше 5 дней». |
| 11 | ocrPerformed |
U | smarty-media/lib/ocr/ocrHandler.js:29 |
{ _wsId, pagesLeft } |
Influx ocr_events; при pagesLeft<40 и отсутствии redis-флага OCR_SUPPORT_NOTIF — письмо о нехватке распознаваний (и ставит флаг), иначе снимает флаг; чат-хук (dialogHook, токен <секрет>). |
| 12 | rateChangeToFree |
U | api-rest/routes/workspace/workspace.js:79 |
{ _wsId, rateBefore, rateId } |
Письмо «...поменял тариф с платного на бесплатный». |
| 13 | rateChangeToPaid |
U | api-rest/routes/workspace/workspace.js:81 |
{ _wsId, rateBefore, rateId } |
Письмо «...перешёл на платный тариф rateId». |
| 14 | resendSms |
U | smarty-sms/routes.js:84 |
{ phone, provider, count } |
Письмо «Отправка смс на phone через provider в count+1-й раз». |
| 15 | resendCall |
U | Продюсер не найден (нет ни одного вызова в кодовой базе — обработчик-сирота). Ожидаемый payload — по аналогии с resendSms. |
{ phone, provider, count } |
Письмо «Отправка звонка на phone через provider в count+1-й раз». На 2026-06-13 не эмитится. |
| 16 | userAttemptChangeRate |
U | api-rest/routes/workspace/workspace.js:114 |
{ _wsId, rate } |
Письмо «Попытка смены тарифа... не хватило денег». |
| 17 | userPressFillBalanceButton |
U | api-rest/routes/workspace/workspace.js:122 (delaySystemEvent); снимается payApply.js:39 |
{} |
Письмо «Пользователь нажал Пополнить баланс» (если не снято успешной оплатой). |
| 18 | smsSend |
U | smarty-sms/lib/sendSms/index.js:23,40 |
{ count, providerName, phone, error } + userMeta.{ip,platform} |
Influx sms_events; чат-хук (dialogHook, токен <секрет>) со статусом УСПЕШНО/НЕУДАЧНО. |
| 19 | callSend |
U | smarty-sms/lib/sendSms/index.js:66,82 |
{ count, providerName, phone, error } + userMeta.{ip,platform} |
Influx call_events; чат-хук (dialogHook, токен <секрет>) со статусом. |
| 20 | phoneConfirmed |
U | smarty-db/.../AccountWithConfirmation.js:88 |
{} + userMeta.platform |
Чат-хук (dialogHook, токен <секрет>) о подтверждении; затем по инвайту — письмо: чистая регистрация / приглашение в диалог (грузит Invite,Employee) / приглашение сотрудником. |
| 21 | userSupport |
U | api-rest/routes/misc/report.js:37 |
{ message } |
Письмо «Feedback from <frontDomain>» с текстом обращения. |
| 22 | deletionReport |
U | api-rest/routes/misc/report.js:117 |
{ reason } |
Создаёт SupportReport(deletionReport); письмо «Запрос на удаление аккаунта» с профилем, страной (по номеру), причиной, ссылкой в админку. |
| 23 | deleteUser |
— | smarty-admin/routes/user.js:80 (emitBgEvent.sendEvent) |
data: { userId, reason } |
Каскад: по Employee-курсору — leaveWorkspace/deleteObject по правам; пометка связей isDeleted; отложенный userDeleted→auth-events; disconnect-other-sessions→socket-commands; Device.purgeDevices. |
| 24 | notEnoughBalance |
A | smarty-db/models/workspace/workspace.js:1386 |
{ _wsId } |
Адресуется админу РП: письмо «Недостаточно денег на балансе РП». |
| 25 | balanceEmptyInFiveDays |
A | smarty-db/models/workspace/workspace.js:905 |
{ _wsId } |
Адресуется админу РП: письмо «Заканчиваются деньги на счёте РП». |
| 26 | rateConfirm |
— | smarty-db/models/workspace/workspace.js:892 (emitBgEvent.sendEvent) |
data: { eventData: { rebillCount } } |
Influx rebill_events (rebill, value=rebillCount). Письма/чата нет. |
| 27 | feedbackCrash |
— | api-rest/routes/misc/report.js:15 (emitBgEvent.sendEvent) |
eventData: { subject, message, name, information, versionApp, device, os } |
Письмо «Crash Report <subject>» по шаблону crash_report. |
| 28 | logCityChange (флаг ENABLE_LOGCITYCHANGE) |
U | lib/Restify/handlers/AuthRequest.js:47, smarty-auth/lib/VariableAuthRequest.js:25 |
{} + userMeta.{platform,ip,city,agent} |
Если platform==='web' и сменились и город, и IP — чат-хук (dialogHook, токен <секрет>) и user.set({ipCity,lastIp}).save(). |
dialogHook(data) — мост в чат/хук{ wsApiId?, token, body }.WsObjectToken.findOne({value: token}) → если нет, return; Model = SmartySchema.model(token.tokenType) → если не Hookable, return;Model.findById(token.targetId) → если нет, return; иначеobject.hookExecute({ profile, body }).hookExecute (доставка в чат). Идемпотентность не гарантируется —phoneConfirmed(user, { data })userMeta.platform, user.{phone,auth_email,phoneConfirmedStatus,inviteToken}.dialogHook в служебный чат; (2) ветвление по инвайту —registrationClean / registrationInviteToDialog (доп. запрос Invite+Employee) /registrationInviteToWorkspace.inviteToken, но отсутствии Invite в БД — письма нет (только чат-хук).deleteUser({ data }) — оркестрация удаления{ userId, reason }.Employee: не-админ → leaveWorkspace; единственныйdeleteObject (РП удаляется); единственный админ при наличии другихdeleteObject; иначе → leaveWorkspace.updateMany(isDeleted:true); отложенный userDeleted (auth-events,now+deletedProfileWaitTime); disconnect-other-sessions (socket-commands);Device.purgeDevices.userEventSend(user, data, subject, emailContent?) — общий помощник писемuser, конверт data, тема, опц. HTML-контент тела.generateUserInfo → render('user_event', …) (с санацией) →sendEmailToSupport(user.auth_email, subject, html, data.time).Mail.Единого «кода ошибки» у событий нет — модель fire-and-forget. Ошибки обрабатываются
централизованно в HandlersMap.handle:
| Ситуация | Поведение | Видимость для продюсера |
|---|---|---|
| Обработчик бросил исключение | logger.error(...), затем acker.ack() (nack отключён). Сообщение не возвращается в очередь. |
Никакой — продюсер не узнаёт о сбое. |
NODE_ENV=test + исключение |
Исключение пробрасывается (после ack) — тесты падают. | n/a (тестовый режим). |
Неизвестный msg.type |
fn === undefined, тело не вызывается, ack(). |
Молчаливая потеря (опечатка в имени не диагностируется). |
userEvent: пользователь не найден |
Обработчик не вызывается (return null), ack(). |
Молчаливый пропуск. |
adminEvent: админ РП не найден |
if (employee) ложно → no-op, ack(). |
Молчаливый пропуск. |
| Отправка письма (Mailgun) | В test — заглушка ({message:'Queued...'}); статус в Mail = sended/error по ответу. Исключение запроса всплывёт в HandlersMap (см. строку 1). |
Через лог и коллекцию Mail. |
Семантические следствия (contract-first):
| Направление | Канал | Откуда |
|---|---|---|
Mailgun API (/v3/<domain>/messages) |
lib/mailgunSend.js; логируется в Mail. |
|
| Метрики | InfluxDB (influxWritePoints) |
ocrPerformed, smsSend, callSend, rateConfirm. |
| Служебные чаты | dialogHook → hookExecute (по токену) |
переэмит из обработчиков (токены — <секрет>). |
| Доменные очереди | auth-events (userDeleted), socket-commands (disconnect-other-sessions) |
deleteUser. |
| Кэш/флаги | Redis (OCR_SUPPORT_NOTIF) |
ocrPerformed. |
Контракты выведены из исходного кода по состоянию на 2026-06-13. Поведение, помеченное как
«недокументированное/наблюдаемое», в коде явно не специфицировано и может меняться.