Skip to content

Feat/eventhooks#7

Merged
m41denx merged 7 commits intomainfrom
feat/eventhooks
Dec 10, 2025
Merged

Feat/eventhooks#7
m41denx merged 7 commits intomainfrom
feat/eventhooks

Conversation

@TurboRigby
Copy link
Member

@TurboRigby TurboRigby commented Dec 10, 2025

Summary by CodeRabbit

Примечания к выпуску

  • Новые возможности
    • Добавлена интеграция с Discord для отправки уведомлений о рейтинге уровней с подробной информацией об уровне, сложности, монетах и сведениями о модераторе
    • Добавлена интеграция с Telegram для отправки уведомлений о рейтинге уровней с информацией об авторе, сложности, монетах и статусом функции

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link

vercel bot commented Dec 10, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
nitrocore Ready Ready Preview Comment Dec 10, 2025 11:55am

@TurboRigby TurboRigby requested a review from m41denx December 10, 2025 11:55
@coderabbitai
Copy link

coderabbitai bot commented Dec 10, 2025

Walkthrough

Добавлена асинхронная система событий для отслеживания действий пользователей. ActionController теперь эмитирует события через новый SDKEvents после вставки в базу данных. Введены типы AvailableActions и LikeItemType для публичного API, создан контекст на основе unctx, разработан EventFabric для управления эмиттерами и добавлены плагины Discord и Telegram для уведомлений о рейтинговании уровней.

Changes

Cohort / File(s) Описание изменений
Основной контроллер действий
controller/ActionController.ts
Добавлено асинхронное эмитирование события через useSDK().events.emitAction после вставки в БД; изменён тип параметра isItemLiked с ItemType на LikeItemType; экспортированы типы AvailableActions и LikeItemType
Система событий SDK
sdk/events/SDKEvents.ts, sdk/events/context.ts, sdk/events/types.ts
Введён класс SDKEvents с методами onAction и emitAction для регистрации и эмитирования слушателей; создан контекст ctx с типом Context (drizzle и config); определены типы ActionListener и ActionInvoker
Утилиты EventFabric
server/utils/useFabric.ts
Новый модуль с функциями useFabric и useTemporalFabric для управления именованными EventEmitter3 экземплярами и временными эмиттерами
Интеграция SDK
server/utils/useSDK.ts, server/utils/useServerConfig.ts
Добавлено свойство events в SDK с экземпляром SDKEvents; экспортирован useEventContext; экспортирован тип ServerConfig
Плагины уведомлений
server/plugins/plugin-discord-ratebot.ts, server/plugins/plugin-telegram-ratebot.ts
Введены плагины Nitro для слушания события level_rate и отправки уведомлений в Discord (с кастомизацией embed'ов) и Telegram (с деталями уровня)
Вспомогательные типы
server/utils/types.ts
Добавлен тип ArgumentTypes<F> для извлечения типов аргументов из функции

Sequence Diagram(s)

sequenceDiagram
    participant Controller as ActionController
    participant SDK as useSDK()
    participant Events as SDKEvents
    participant Fabric as EventFabric
    participant DiscordPlugin as Discord Plugin
    participant TelegramPlugin as Telegram Plugin
    participant Discord as Discord API
    participant Telegram as Telegram API

    Controller->>Controller: registerAction()
    Controller->>Controller: Insert action to DB
    Controller->>SDK: Get SDK instance
    SDK->>Events: emitAction(level_rate, uid, targetId, data, context)
    Events->>Fabric: useFabric('actions').emit(...)
    
    par Discord Webhook
        Fabric->>DiscordPlugin: onAction listener triggered
        DiscordPlugin->>DiscordPlugin: Validate & prepare payload
        DiscordPlugin->>Discord: POST webhook
        Discord-->>DiscordPlugin: Success
    and Telegram Message
        Fabric->>TelegramPlugin: onAction listener triggered
        TelegramPlugin->>TelegramPlugin: Validate & prepare message
        TelegramPlugin->>Telegram: Send message via Bot API
        Telegram-->>TelegramPlugin: Success
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Внимание на следующие области:
    • sdk/events/SDKEvents.ts — использование ctx.callAsync и обёртывание слушателей; проверить корректность асинхронного контекста и обработку ошибок
    • server/plugins/plugin-discord-ratebot.ts — сложная логика построения embed'ов, обработка различных типов рейтинговых действий и валидация конфигурации; требует тестирования всех сценариев фильтрации
    • server/utils/useFabric.ts — управление реестром эмиттеров и функция useTemporalFabric с уникальными UUID; проверить отсутствие утечек памяти при создании временных эмиттеров
    • Интеграция контекста в ActionController.registerAction — убедиться, что контекст корректно передаётся в emitAction и доступен в плагинах
    • Экспортирование типов LikeItemType и AvailableActions — проверить совместимость с существующим кодом и отсутствие конфликтов типов

Possibly related PRs

  • Feat/ratebot #4 — Оба PR модифицируют ActionController.registerAction для эмитирования событий после вставки (этот PR добавляет useSDK().events.emitAction, предыдущий использовал Nitro hooks)
  • feat: Add Discord and Telegram ratebot plugins to send level rating n… #6 — Оба PR добавляют те же плагины plugin-discord-ratebot.ts и plugin-telegram-ratebot.ts, которые слушают события level_rate и отправляют уведомления
  • unemployed #3 — Оба PR модифицируют файлы server/plugins/plugin-discord-ratebot.ts и server/plugins/plugin-telegram-ratebot.ts (этот PR их добавляет, предыдущий локализует строки)

Suggested reviewers

  • m41denx

Poem

🐰 Прыг-скок, событья летят в полёт,
ActionController действа творит,
Discord и Telegram весть получат,
EventFabric все нити вяжет в ночь,
Система нова — чудо-мощь!

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Feat/eventhooks' is vague and uses generic terminology that doesn't clearly convey what the pull request implements. Use a more descriptive title that clearly summarizes the main feature, such as 'Add SDK event hooks and Discord/Telegram rate notifications' or 'Implement event handling system for action tracking'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/eventhooks

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (5)
sdk/events/types.ts (1)

4-8: Рассмотрите использование существующего типа MaybePromise.

Возвращаемый тип void | Promise<void> можно заменить на MaybePromise<void>, который уже определен в server/utils/types.ts. Это улучшит согласованность типов в кодовой базе.

Примените этот diff:

+import {MaybePromise} from "~~/server/utils/types";
+
 export type ActionListener = (
     uid: number,
     targetId: number,
     data: ActionData
-) => void | Promise<void>
+) => MaybePromise<void>
server/plugins/plugin-telegram-ratebot.ts (1)

29-74: Избегайте использования as any для HTTP-методов.

На строке 68 используется method: "POST" as any, что ослабляет типобезопасность. Проверьте типы $fetch — возможно, можно использовать корректный тип без приведения, или использовать более конкретное приведение типа.

Рассмотрите замену:

-        method: "POST" as any,
+        method: "POST",

Если возникает ошибка типизации, используйте более явное приведение или проверьте версию и типы библиотеки $fetch.

sdk/events/context.ts (1)

1-9: Неиспользуемый импорт.

На строке 3 импортируется H3EventContext из h3, но этот тип нигде не используется в файле. Рассмотрите его удаление для чистоты кода.

 import {createContext} from "unctx";
 import {AsyncLocalStorage} from "node:async_hooks";
-import {H3EventContext} from "h3";
server/plugins/plugin-discord-ratebot.ts (2)

57-58: Слабая валидация URL.

Проверка startsWith("http") пропустит невалидные URL вроде "httpxyz". Рекомендую использовать конструктор URL для более надёжной валидации:

-    if (!moduleConfig.webhookUrl.startsWith("http"))
-        throw new Error("Webhook URL must be absolute")
+    try {
+        new URL(moduleConfig.webhookUrl)
+    } catch {
+        throw new Error("Webhook URL must be a valid absolute URL")
+    }

75-82: Излишнее приведение типа as any.

$fetch из Nitro принимает method: "POST" без необходимости приведения типа:

     try {
         await $fetch(moduleConfig.webhookUrl, {
-            method: "POST" as any,
+            method: "POST",
             body: webhookBody
         })
     } catch (error) {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fc704c0 and c6b873e.

📒 Files selected for processing (10)
  • controller/ActionController.ts (3 hunks)
  • sdk/events/SDKEvents.ts (1 hunks)
  • sdk/events/context.ts (1 hunks)
  • sdk/events/types.ts (1 hunks)
  • server/plugins/plugin-discord-ratebot.ts (1 hunks)
  • server/plugins/plugin-telegram-ratebot.ts (1 hunks)
  • server/utils/types.ts (1 hunks)
  • server/utils/useFabric.ts (1 hunks)
  • server/utils/useSDK.ts (1 hunks)
  • server/utils/useServerConfig.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (7)
sdk/events/SDKEvents.ts (5)
controller/ActionController.ts (1)
  • AvailableActions (168-171)
sdk/events/types.ts (2)
  • ActionListener (4-8)
  • ActionInvoker (10-10)
sdk/events/context.ts (2)
  • Context (13-16)
  • ctx (6-9)
server/utils/types.ts (1)
  • ArgumentTypes (8-8)
drizzle/actions.ts (1)
  • ActionData (55-57)
sdk/events/types.ts (3)
drizzle/actions.ts (1)
  • ActionData (55-57)
sdk/events/context.ts (1)
  • Context (13-16)
server/utils/types.ts (1)
  • ArgumentTypes (8-8)
server/plugins/plugin-telegram-ratebot.ts (5)
server/utils/useSDK.ts (1)
  • useSDK (11-11)
drizzle/actions.ts (1)
  • ActionData (55-57)
server/utils/useLogger.ts (1)
  • useLogger (11-11)
controller/LevelController.ts (1)
  • LevelController (9-187)
controller/Level.ts (1)
  • LevelWithUser (9-11)
sdk/events/context.ts (2)
server/utils/useSDK.ts (1)
  • useEventContext (15-15)
server/utils/useServerConfig.ts (1)
  • ServerConfig (17-56)
server/utils/useSDK.ts (2)
sdk/music/SDKMusic.ts (1)
  • SDKMusic (5-97)
sdk/events/SDKEvents.ts (1)
  • SDKEvents (6-31)
server/plugins/plugin-discord-ratebot.ts (4)
server/utils/useSDK.ts (1)
  • useSDK (11-11)
drizzle/actions.ts (1)
  • ActionData (55-57)
server/utils/useLogger.ts (1)
  • useLogger (11-11)
controller/Level.ts (1)
  • LevelWithUser (9-11)
controller/ActionController.ts (2)
server/utils/useSDK.ts (1)
  • useSDK (11-11)
drizzle/actions.ts (1)
  • ActionData (55-57)
🔇 Additional comments (15)
server/utils/types.ts (1)

8-8: Хорошее добавление утилитного типа!

Тип ArgumentTypes<F> корректно извлекает типы аргументов функции и будет полезен для типизации ActionInvoker в системе событий.

server/utils/useServerConfig.ts (1)

17-56: Правильное решение экспортировать тип!

Экспорт ServerConfig необходим для использования в публичном API контекста событий (Context в sdk/events/context.ts). Структура конфигурации остается неизменной.

server/utils/useSDK.ts (1)

3-15: Хорошая интеграция событийной системы в SDK!

Добавление SDKEvents следует существующему паттерну (аналогично commands и music). Экспорт useEventContext обеспечивает удобный доступ к контексту событий из публичного API.

sdk/events/SDKEvents.ts (1)

22-30: Логика эмиссии событий корректна.

Метод emitAction правильно передает все параметры в фабрику событий. После добавления импорта useFabric, указанного в предыдущем комментарии, метод будет работать корректно.

server/plugins/plugin-telegram-ratebot.ts (5)

19-27: Правильная регистрация плагина!

Использование defineNitroPlugin и подписка на событие level_rate реализованы корректно. Обработка ошибок с логированием предотвращает падение приложения при сбоях отправки уведомлений.


76-100: Отличная реализация форматирования сообщений!

Функция buildTelegramMessage хорошо структурирована с использованием опциональных цепочек, значений по умолчанию и фильтрации. Логика построения сообщения понятна и поддерживаема.


102-123: Логика определения сложности реализована корректно.

Функция describeDifficulty охватывает все диапазоны звезд от 0 до бесконечности. Комментарий правильно указывает, что fallback теоретически недостижим при корректных данных.


125-133: Проверьте маппинг сложности демонов.

В объекте map отсутствует ключ 3, хотя присутствуют ключи 0, 1, 2, и 4. Если значение 3 должно возвращать "Insane Demon" (через дефолтное значение), это поведение следует явно задокументировать. В противном случае, это может быть ошибкой.

Пожалуйста, подтвердите:

  • Является ли отсутствие ключа 3 намеренным?
  • Если да, рассмотрите добавление комментария для ясности:
const resolveDemonLabel = (value: number) => {
    const map: Record<number, string> = {
        // 3 intentionally omitted - defaults to "Insane Demon"
    }
    return map[value] || "Insane Demon"
}

135-154: Вспомогательные функции реализованы хорошо.

Функции resolveEpic и formatCoins корректно обрабатывают соответствующие значения с понятной логикой и правильными значениями по умолчанию.

server/utils/useFabric.ts (2)

14-21: Реализация useFabric корректна.

Функция правильно управляет именованными экземплярами фабрики событий. Приведение типов as unknown as EventEmitter<T> необходимо для обеспечения типобезопасности с обобщенными типами событий.


27-35: Отличная реализация временной фабрики!

Функция useTemporalFabric предоставляет чистый API для создания изолированных эмиттеров событий с механизмом очистки. Использование removeAllListeners() перед удалением предотвращает утечки памяти.

controller/ActionController.ts (1)

168-173: LGTM!

Типы AvailableActions и LikeItemType корректно определены и соответствуют логике switch-case в методах класса.

server/plugins/plugin-discord-ratebot.ts (3)

29-37: LGTM!

Регистрация плагина корректна: используется правильный паттерн с defineNitroPlugin, ошибки перехватываются и логируются.


85-134: LGTM!

Функция createWebhookBody хорошо структурирована: корректно формирует embed для Discord, условно исключает поле "Epic Tier" когда оно не применимо, и правильно обрабатывает опциональные поля конфигурации.


136-156: LGTM!

Вспомогательные функции resolveDifficulty, resolveEpic и formatCoins корректно реализованы и покрывают все необходимые случаи.

Also applies to: 169-188

Comment on lines +158 to +167
const resolveDemon = (value: number) => {
const map: Record<number, string> = {
3: "Easy Demon",
4: "Medium Demon",
0: "Hard Demon",
5: "Insane Demon",
6: "Extreme Demon",
}
return map[value] || "Insane Demon"
}
Copy link

@coderabbitai coderabbitai bot Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Несогласованное значение по умолчанию для демонов.

Согласно LevelController.countDemonStats, значение по умолчанию (когда demonDifficulty не соответствует 3, 4, 5 или 6) — это Hard Demon, а не "Insane Demon":

 const resolveDemon = (value: number) => {
     const map: Record<number, string> = {
     }
-    return map[value] || "Insane Demon"
+    return map[value] || "Hard Demon"
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const resolveDemon = (value: number) => {
const map: Record<number, string> = {
3: "Easy Demon",
4: "Medium Demon",
0: "Hard Demon",
5: "Insane Demon",
6: "Extreme Demon",
}
return map[value] || "Insane Demon"
}
const resolveDemon = (value: number) => {
const map: Record<number, string> = {
3: "Easy Demon",
4: "Medium Demon",
0: "Hard Demon",
5: "Insane Demon",
6: "Extreme Demon",
}
return map[value] || "Hard Demon"
}
🤖 Prompt for AI Agents
In server/plugins/plugin-discord-ratebot.ts around lines 158 to 167, the
resolveDemon function returns "Insane Demon" as the fallback for unknown values,
but LevelController.countDemonStats uses "Hard Demon" as the default; change the
fallback to "Hard Demon" so the function returns map[value] || "Hard Demon" (no
other logic changes needed).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Contributor

@m41denx m41denx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@m41denx m41denx merged commit 85efbdd into main Dec 10, 2025
5 checks passed
@m41denx m41denx deleted the feat/eventhooks branch December 10, 2025 12:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants