Skip to content

feat: merge discord bot into Notifierr, drop RabbitMQ#91

Draft
Pouleyy wants to merge 7 commits into
masterfrom
feat/merge-discord-bot
Draft

feat: merge discord bot into Notifierr, drop RabbitMQ#91
Pouleyy wants to merge 7 commits into
masterfrom
feat/merge-discord-bot

Conversation

@Pouleyy

@Pouleyy Pouleyy commented May 28, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Folds the standalone PlexNotifierrDiscord repo into this solution as a new PlexNotifierr.Discord project, hosted in-process by the API. The system now deploys as a single pod with no RabbitMQ broker between the worker and the bot.
  • DiscordNotificationSender implements INotificationSender by DM-ing the user directly via Discord.Net, replacing the old RabbitMQ publish + separate consumer pod with one in-process call.
  • New PlexNotifierr.Core.SubscriptionService (backed by IDbContextFactory, singleton) holds the subscribe/unsubscribe logic so the Discord module can inject it via plain DI — no manual scope, no HTTP round trip.
  • API host: swaps AddDbContext for AddDbContextFactory + a scoped DbContext that resolves through it, drops the now-empty MVC/Scalar/Auth pipeline (Hangfire dashboard at /dashboard is all that's left).
  • Removed: RabbitMQ.Client, the old NotificationSender, RabbitMqConfig, PublicationExtensions, the /subscription controller and its request models. docker-compose.yaml loses the rabbitmq service.

Test plan

  • dotnet build clean (5 projects, 0 errors)
  • Local smoke: app boots, DI resolves, Discord hosted service logs a warning + skips when token is unset, /dashboard returns HTTP 200 with <title>Overview – Notifierr Dashboard</title>, clean SIGINT shutdown
  • Integration: real Discord bot token + test guild → !help, !subscribe, !unsubscribe round trip
  • Integration: real Plex server + subscribed user → DM notification delivery from GetRecentlyAddedJob
  • Deploy to staging and verify the single-pod compose stack comes up

🤖 Generated with Claude Code

Pouleyy and others added 7 commits May 28, 2026 15:51
Folds the standalone PlexNotifierrDiscord repo into this solution as a
new project hosted by the API process, so the system deploys as a
single pod with no RabbitMQ broker between the worker and the bot.

- New PlexNotifierr.Discord project: hosted service that owns the
  DiscordShardedClient, command handler, !subscribe/!unsubscribe/!help
  modules, and a DiscordNotificationSender that implements
  INotificationSender by DM-ing the user directly via Discord.Net.
- New PlexNotifierr.Core.SubscriptionService: shared subscribe/
  unsubscribe logic backed by IDbContextFactory so it can live as a
  singleton and be injected straight into the Discord module without a
  manual scope.
- API host: registers the Discord bot, swaps AddDbContext for
  AddDbContextFactory + a scoped DbContext that resolves through it,
  drops the now-empty MVC/Scalar/Auth pipeline (only the Hangfire
  dashboard remains).
- Removed RabbitMQ.Client + the old NotificationSender, RabbitMqConfig,
  PublicationExtensions, /subscription controller and its request
  models. docker-compose loses the rabbitmq service.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ob coverage

Add xUnit + NSubstitute + Shouldly test projects:
- PlexNotifierr.TestSupport: shared SQLite in-memory IDbContextFactory helper
- PlexNotifierr.Core.Tests: SubscriptionService branch coverage
- PlexNotifierr.Worker.Tests: GetRecentlyAddedJob behavior coverage

Wire all three into the solution and run them in CI via `dotnet test`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PlexAccount members are non-virtual, so build a real PlexAccount over
substituted Plex clients and stub GetFriendsAsync (what Friends() delegates to).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…sode)

Drives the paging loop with a single sub-limit page and asserts HistoryPosition
advances to guard against the job's catch-all swallowing a misconfigured mock.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The old catch-all wrapped the whole page loop and only logged e.Message. When
GetPlayHistory threw, isLastPage stayed false and offset never advanced, so the
loop spun forever against a down Plex server.

Restructure the error boundaries:
- page fetch failure -> log with context + break (cursor untouched, next run retries)
- per-show failure -> log + skip that show so the rest of the page still processes
- save failure -> log + break

Add regression tests for the fetch-failure (single call, no retry) and
per-show-failure (skip one, process the rest, advance cursor) paths.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ender

DiscordShardedClient and RestUser are concrete Discord.Net types with
non-virtual members, so the sender could not be unit-tested. Extract a thin
IDiscordUserGateway/IDiscordDmRecipient seam (production adapter forwards to the
live client) so all notification logic stays in DiscordNotificationSender and
becomes testable. Behavior is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New PlexNotifierr.Discord.Tests project (xUnit + NSubstitute + Shouldly):
invalid id short-circuits the lookup, user-not-found and send-failure return
false, and the happy path builds the templated text + embed (title/description/
image/colour) with the Plex link field toggled by config.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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