feat: add telegram notification channel with outbox service and per-user preferences
Extend the notification system with a third channel (telegram) alongside existing in_app and push channels. Add `telegram_enabled` column to `notification_preferences` table, update `NOTIFICATION_CHANNELS` and `_NOTIFICATION_CHANNEL_COLUMNS` mappings, and set telegram default to off (`_NOTIFICATION_CHANNEL_DEFAULTS`). Create `telegram_outbox` table with columns for uid, user_uid, chat_id, text, status, attempts, created_at, and sent_at, plus an index on status/id for efficient polling. Register `TelegramOutboxService` in the service manager lifecycle. Update API documentation strings to describe the new channel and its pairing requirement. Extend admin notification defaults view to include telegram column. Pass `notif_telegram_paired` flag to profile template based on `telegram_store.is_paired()` check. Update `NotificationPrefForm` and `NotificationDefaultForm` model literals to accept "telegram" as a valid channel value.
This commit is contained in:
@@ -126,7 +126,7 @@ The **Code Farm** (`/game`) is a cooperative idle game in the spirit of Farmvill
|
||||
- **Refactor (prestige).** At level 10 you can refactor: the farm resets (coins, level, CI, extra plots, perks) in exchange for a permanent +25% coin bonus that stacks with every refactor.
|
||||
- **Visit and water friends.** Open another member's farm at `/game/farm/{username}` and water their growing builds to speed them up - you earn coins for helping, and the owner sees the help live. This is the social loop that makes the game cooperative.
|
||||
- **Steal a harvest.** A ready build on someone else's farm can be stolen once a 60-second protection window passes - the owner gets that grace period to harvest it first. A successful steal pays the thief half the build's coin value (the owner loses the whole build) and earns the **Cat Burglar** badge; the victim gets the **Robbed** badge and a live notification that someone raided their farm (the thief is never named). Stealing pays coins only, so the leaderboard stays earned by real farming. This is the competitive counterpart to watering.
|
||||
- **Leaderboard.** Top farmers are ranked by level, XP, and total harvests, shown alongside your own farm.
|
||||
- **Leaderboard.** Top farmers are ranked by a composite achievement score that weighs every factor the game tracks - refactor (prestige) count, XP, lifetime harvests, current coins, CI tier, plots bought, perk levels, and login streak - so total accomplishment decides position rather than just the current post-refactor cycle. The score is shown alongside your own farm next to each player's level.
|
||||
|
||||
The farm refreshes live over the pub/sub bus (a watered build appears on the owner's screen at once) and every plot countdown ticks client-side. Every endpoint also answers JSON, and Devii can play the game on the member's behalf via the `game_*` tools (`game_state`, `game_plant`, `game_harvest`, `game_buy_plot`, `game_upgrade_ci`, `game_water`, `game_steal`, `game_view_farm`, `game_leaderboard`). See the API reference group **Code Farm**.
|
||||
|
||||
@@ -715,7 +715,7 @@ installable Progressive Web App. Push uses only standard libraries (`cryptograph
|
||||
### Events
|
||||
|
||||
Every event flows through a single funnel - `create_notification()` in `utils.py` -
|
||||
which delivers on two independent channels, in-app and web push, each gated by the
|
||||
which delivers on three independent channels, in-app, web push and Telegram, each gated by the
|
||||
recipient's preferences (see "Configurable notifications" below). Whenever the in-app
|
||||
channel delivers, the recipient's open browser also raises a live, click-through toast
|
||||
in real time, bridged onto the in-process pub/sub bus by a lock-owner relay:
|
||||
@@ -741,17 +741,23 @@ return `404`/`410` are soft-deleted.
|
||||
|
||||
Every notification type can be turned on or off per channel, per user. The **Notifications**
|
||||
tab on a profile page (`/profile/{username}?tab=notifications`, visible to the profile owner
|
||||
and to admins) shows one row per type with two checkboxes - **In-app** and **Push** - saved
|
||||
individually as you toggle them (`POST /profile/{username}/notifications`). A "Reset to
|
||||
defaults" button clears all of a user's overrides (`POST /profile/{username}/notifications/reset`).
|
||||
and to admins) shows one row per type with three checkboxes - **In-app**, **Push** and
|
||||
**Telegram** - saved individually as you toggle them (`POST /profile/{username}/notifications`).
|
||||
A "Reset to defaults" button clears all of a user's overrides
|
||||
(`POST /profile/{username}/notifications/reset`). The Telegram column is disabled until the
|
||||
user pairs Telegram from the profile Telegram panel; once paired, opting a type in delivers
|
||||
that notification to the user's Telegram chat.
|
||||
|
||||
Defaults are opt-out: a type/channel a user never touched is enabled. Admins set the
|
||||
platform-wide default for each type/channel on `/admin/notifications`
|
||||
(`POST /admin/notifications`); a default applies only to users who have not made an explicit
|
||||
choice. Resolution is: user override, else admin default, else on. Preferences are stored in
|
||||
the `notification_preferences` table (per `user_uid` + `notification_type`, soft-deletable)
|
||||
and enforced inside `create_notification()`: the in-app row is written only when the in-app
|
||||
channel is enabled, and `push.notify_user` is scheduled only when the push channel is enabled.
|
||||
Defaults are opt-out for in-app and push (a type/channel a user never touched is enabled) and
|
||||
opt-in for Telegram (every type is off by default). Admins set the platform-wide default for
|
||||
each type/channel on `/admin/notifications` (`POST /admin/notifications`); a default applies
|
||||
only to users who have not made an explicit choice. Resolution is: user override, else admin
|
||||
default, else the channel fallback. Preferences are stored in the `notification_preferences`
|
||||
table (per `user_uid` + `notification_type`, soft-deletable) and enforced inside
|
||||
`create_notification()`: the in-app row is written only when the in-app channel is enabled,
|
||||
`push.notify_user` is scheduled only when the push channel is enabled, and a Telegram message
|
||||
is queued (to the `telegram_outbox`, drained on the service-lock owner where the bot runs)
|
||||
only when the Telegram channel is enabled and the user is paired.
|
||||
|
||||
### VAPID keys
|
||||
|
||||
|
||||
Reference in New Issue
Block a user