Skip to content

Let validators link Telegram support groups through the Deckard bot - #960

Merged
JoaquinBN merged 3 commits into
devfrom
feature/telegram-group-bind-codes
Aug 2, 2026
Merged

Let validators link Telegram support groups through the Deckard bot#960
JoaquinBN merged 3 commits into
devfrom
feature/telegram-group-bind-codes

Conversation

@rasca

@rasca rasca commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a portal flow for validators to authorize Deckard (the Foundation's Telegram support bot) into their own Telegram groups. This PR is the portal half of the feature and coordinates with the Deckard bot (deckard-testnet-agent), which will implement the /bindcode command against the contract below.

The flow

  1. A validator signs in with their wallet (existing SIWE auth) and opens the new Telegram Support page in the validator area (/validators/telegram).
  2. They generate a one-time group bind code tied to their validator. The plaintext code is shown exactly once (48h expiry); only its SHA-256 digest is stored, using the same identifier-lookup + constant-time-compare design as service account tokens.
  3. They create a Telegram group, add the Deckard bot, and run /bindcode <code> there.
  4. Deckard redeems the code server-to-server; redemption is atomic and single-use, records the bound group_chat_id and redeeming Telegram uid, and upserts a TelegramConnection (social_connections pattern, bot-confirmed — Telegram has no OAuth here) so the portal account carries a verifiable Telegram identity.

Multiple groups per validator are supported (one code per group). DM bindings are refused bot-side, before the portal is ever called.

Backend

  • validators.TelegramGroupBindCode model + migration (statuses issued/redeemed/expired/revoked, lazy expiry, revoke).
  • social_connections.TelegramConnection model + migration (numeric Telegram uid = identity, username display-only).
  • Endpoints:
    • POST /api/v1/validators/telegram-bind-codes/ — auth + validator profile; returns the plaintext once; throttled 10/hour (telegram_bind_issue).
    • GET /api/v1/validators/telegram-bind-codes/mine/ — metadata only, never raw codes.
    • POST /api/v1/validators/telegram-bind-codes/{id}/revoke/ — owner-only; 409 once redeemed.
    • POST /api/v1/validators/telegram-bind-codes/redeem/bot-only, gated by HasServiceAccountScope with the new telegram_bind:redeem scope.

Bot-side contract (for Deckard's /bindcode)

POST /api/v1/validators/telegram-bind-codes/redeem/
Authorization: Bearer sa_<id>_<secret>   (service account token with scope telegram_bind:redeem)
Content-Type: application/json

{"code": "tgb_...", "group_chat_id": "-100123...", "telegram_uid": "42...", "telegram_username": "optional"}

Success 200: {"success": true, "bind_code_id", "validator_id", "user_id", "user_name", "group_chat_id", "redeemed_at"}.
Errors carry a machine code: invalid_request (400), invalid_code (404), already_redeemed / revoked (409), expired (410).

Frontend

  • New role-gated /validators/telegram page (Svelte 5 runes): generate a code, one-time display with a copyable /bindcode command, code list with status chips and revoke.
  • Sidebar "Telegram Support" links in the validator section; bot handle configurable via VITE_DECKARD_BOT_USERNAME.

Tests

22 new Django tests (validators/tests/test_telegram_bind_codes.py): issuance auth + validator gate, one-shot plaintext, mine isolation + lazy expiry, revoke rules, redeem scope gate, single-use, expiry, tampered/unknown codes, TelegramConnection upsert. validators + social_connections + service_accounts suites green (243 tests).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a Telegram Support page for validators.
    • Validators can generate one-time codes to link Telegram support groups, copy setup commands, view code status, and revoke unused codes.
    • Multiple Telegram groups are supported; codes expire after 48 hours and can be revoked.
    • Added Telegram Support navigation for desktop and mobile validator menus.
  • Documentation

    • Updated release and product documentation with Telegram group-linking instructions and code lifecycle details.

rasca added 2 commits July 30, 2026 12:37
Validators can now connect Telegram groups to their validator from the
portal. A new "Telegram Support" page in the validator area issues
one-time group bind codes: the validator creates a Telegram group, adds
the Deckard support bot, and runs /bindcode <code> there; the bot
redeems the code server-to-server and the group is bound to the
validator. A validator can bind multiple groups (one code per group),
codes expire after 48 hours, and unredeemed codes can be revoked.

The bind code follows the service-account token design: only a SHA-256
digest is stored, lookup is by a non-secret identifier embedded in the
code, and redemption compares digests in constant time. Redemption is
gated by a new telegram_bind:redeem service-account scope reserved for
the Deckard bot and is atomic and single-use: it records the bound
group chat id and the redeeming Telegram uid, and upserts a new
TelegramConnection (social connection pattern, bot-confirmed instead of
OAuth) so the portal account carries a verifiable Telegram identity.
Issuance is throttled per user; the plaintext code is returned exactly
once and never listed again.

## Claude Implementation Notes
- backend/validators/models.py: TelegramGroupBindCode model — identifier
  + digest storage (hash_code/identifier_from_plaintext/issue mirroring
  ServiceAccountToken), issued/redeemed/expired/revoked statuses, 48h
  default TTL, lazy effective_status, is_redeemable helper
- backend/validators/migrations/0018_telegramgroupbindcode.py: schema
- backend/validators/views.py: TelegramBindCodeViewSet (create issues +
  returns plaintext once, mine lists metadata, revoke with row lock and
  409 for redeemed codes) and TelegramBindCodeRedeemView (service
  account auth, required_scopes {'*': telegram_bind:redeem}, payload
  validation, select_for_update single-use redemption, constant-time
  digest compare, TelegramConnection upsert that never blanks an
  existing username)
- backend/validators/serializers.py: TelegramBindCodeSerializer —
  metadata only, status is the lazy-expiry effective status
- backend/validators/urls.py: explicit redeem path before the router so
  the viewset detail route never swallows it; telegram-bind-codes
  registered before the catch-all validator route
- backend/social_connections/models.py: TelegramConnection subclass of
  the SocialConnection abstract base (numeric uid identity, display-only
  username, no OAuth), db_table social_connections_telegram
- backend/social_connections/migrations/0008_telegramconnection.py: schema
- backend/social_connections/admin.py: TelegramConnection admin
- backend/service_accounts/scopes.py: TELEGRAM_BIND_REDEEM_SCOPE added
  to ALLOWED_SERVICE_ACCOUNT_SCOPES so admin/command token issuance
  accepts it
- backend/utils/throttling.py + backend/tally/settings.py:
  TelegramBindCodeIssueRateThrottle, telegram_bind_issue 10/hour
- backend/validators/tests/test_telegram_bind_codes.py: 22 tests —
  issuance auth + validator gate + single-shot plaintext, mine
  isolation and lazy expiry, revoke rules, redeem scope gate,
  single-use, expiry, invalid/tampered codes, connection upsert
- frontend/src/routes/ValidatorTelegram.svelte: Link-a-Telegram-group
  page (Svelte 5 runes) — generate code, show once with /bindcode copy
  command, list codes with status chips and revoke
- frontend/src/App.svelte: /validators/telegram role-gated route
- frontend/src/components/Sidebar.svelte: Telegram Support link in both
  validator sections (role-locked pattern)
- frontend/src/lib/api.js: issue/list/revoke bind-code helpers
- frontend/src/lib/config.js: DECKARD_BOT_USERNAME from
  VITE_DECKARD_BOT_USERNAME with generic-wording fallback
- backend/CLAUDE.md + frontend/CLAUDE.md: documented the new model,
  endpoints, scope, route, and API helpers
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c2dc68bd-cf85-4ecd-94c3-a303d24c21f3

📥 Commits

Reviewing files that changed from the base of the PR and between b019899 and 2762716.

📒 Files selected for processing (3)
  • backend/validators/views.py
  • frontend/.env.example
  • frontend/CLAUDE.md

📝 Walkthrough

Walkthrough

Adds one-time, expiring Telegram group bind codes for validators. The backend supports issuance, listing, revocation, and scoped bot redemption. The frontend provides a role-gated page for managing codes and Telegram support links.

Changes

Telegram validator group binding

Layer / File(s) Summary
Bind-code data contracts
backend/validators/models.py, backend/validators/migrations/..., backend/social_connections/..., backend/validators/serializers.py, backend/service_accounts/scopes.py
Adds TelegramGroupBindCode lifecycle and digest storage, TelegramConnection persistence, serializer output, migrations, admin management, and the telegram_bind:redeem scope.
Bind-code API lifecycle
backend/validators/views.py, backend/validators/urls.py, backend/utils/throttling.py, backend/tally/settings.py, backend/validators/tests/*
Adds throttled validator issuance, metadata listing, owner-only revocation, and scoped service-account redemption with atomic single-use transitions and Telegram connection upsert behavior. Tests cover authorization, lifecycle states, redemption, validation, and upsert behavior.
Validator Telegram support page
frontend/src/App.svelte, frontend/src/components/Sidebar.svelte, frontend/src/lib/api.js, frontend/src/lib/config.js, frontend/src/routes/ValidatorTelegram.svelte, frontend/.env.example
Adds role-gated navigation and a page for generating, copying, listing, and revoking Telegram bind codes. Adds optional Deckard bot username configuration.
Supporting documentation
CHANGELOG.md, backend/CLAUDE.md, frontend/CLAUDE.md
Documents the Telegram binding flow, API routes, service-account scope, models, configuration, and frontend route/API additions.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Validator
  participant ValidatorTelegram
  participant ValidatorsAPI
  participant TelegramBindCodeViewSet
  participant TelegramBindCodeRedeemView
  participant DeckardTelegramBot
  participant TelegramConnection

  Validator->>ValidatorTelegram: Generate group code
  ValidatorTelegram->>ValidatorsAPI: Issue bind code
  ValidatorsAPI->>TelegramBindCodeViewSet: Create code
  TelegramBindCodeViewSet-->>ValidatorTelegram: Return plaintext once
  Validator->>DeckardTelegramBot: Submit bind code in group
  DeckardTelegramBot->>TelegramBindCodeRedeemView: Redeem with scoped service token
  TelegramBindCodeRedeemView->>TelegramConnection: Upsert Telegram connection
  TelegramBindCodeRedeemView-->>DeckardTelegramBot: Return binding result
Loading

Possibly related PRs

Suggested reviewers: joaquinbn

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.60% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: enabling validators to link Telegram support groups through the Deckard bot.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/telegram-group-bind-codes

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/service_accounts/scopes.py`:
- Around line 13-15: Update the ALLOWED_SERVICE_ACCOUNT_SCOPES definition to
build the frozenset using iterable unpacking for AI_REVIEW_SCOPES and
TELEGRAM_BIND_REDEEM_SCOPE instead of tuple concatenation, preserving the same
allowed scope values.

In `@backend/social_connections/models.py`:
- Around line 51-67: The requested change is a broader follow-up: the social
connection model hierarchy currently does not inherit BaseModel. Do not modify
TelegramConnection in this PR; track updating SocialConnection and its
GitHubConnection, TwitterConnection, DiscordConnection, and TelegramConnection
subclasses to inherit utils.models.BaseModel as a separate migration-aware
change.

In `@backend/validators/views.py`:
- Around line 1300-1345: Add an explicit throttle scope to
TelegramBindCodeRedeemView and configure the corresponding scoped rate limit so
the inherited ScopedRateThrottle is active for redemption requests. Keep the
existing redemption validation and response behavior unchanged, using the
project’s established throttle-scope configuration pattern.
- Around line 1183-1199: Update the logging in the create method to remove
request.user.address from the Telegram bind-code issuance message, while
retaining request.user.id and bind_code.id as identifiers.

In `@frontend/CLAUDE.md`:
- Line 481: Update the ## Environment Variables section in frontend/CLAUDE.md to
include VITE_DECKARD_BOT_USERNAME in the environment-variable checklist, while
retaining the existing Telegram bind-code documentation and references.

In `@frontend/src/lib/config.js`:
- Around line 16-19: Document the new VITE_DECKARD_BOT_USERNAME environment
variable in the frontend environment example or documentation alongside the
existing VITE_ variables, describing that it should contain the bot username
without the @ symbol. Keep the existing DECKARD_BOT_USERNAME configuration
behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: bd9b324e-5248-497d-a81f-3e36deff1c9e

📥 Commits

Reviewing files that changed from the base of the PR and between 61c2e06 and b019899.

📒 Files selected for processing (20)
  • CHANGELOG.md
  • backend/CLAUDE.md
  • backend/service_accounts/scopes.py
  • backend/social_connections/admin.py
  • backend/social_connections/migrations/0008_telegramconnection.py
  • backend/social_connections/models.py
  • backend/tally/settings.py
  • backend/utils/throttling.py
  • backend/validators/migrations/0018_telegramgroupbindcode.py
  • backend/validators/models.py
  • backend/validators/serializers.py
  • backend/validators/tests/test_telegram_bind_codes.py
  • backend/validators/urls.py
  • backend/validators/views.py
  • frontend/CLAUDE.md
  • frontend/src/App.svelte
  • frontend/src/components/Sidebar.svelte
  • frontend/src/lib/api.js
  • frontend/src/lib/config.js
  • frontend/src/routes/ValidatorTelegram.svelte

Comment on lines +13 to +15
ALLOWED_SERVICE_ACCOUNT_SCOPES = frozenset(
AI_REVIEW_SCOPES + (TELEGRAM_BIND_REDEEM_SCOPE,)
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor: prefer unpacking over concatenation.

Ruff flags the tuple concatenation; unpacking is idiomatic and avoids the implicit tuple-type check.

♻️ Suggested fix
-ALLOWED_SERVICE_ACCOUNT_SCOPES = frozenset(
-    AI_REVIEW_SCOPES + (TELEGRAM_BIND_REDEEM_SCOPE,)
-)
+ALLOWED_SERVICE_ACCOUNT_SCOPES = frozenset(
+    (*AI_REVIEW_SCOPES, TELEGRAM_BIND_REDEEM_SCOPE)
+)
📝 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
ALLOWED_SERVICE_ACCOUNT_SCOPES = frozenset(
AI_REVIEW_SCOPES + (TELEGRAM_BIND_REDEEM_SCOPE,)
)
ALLOWED_SERVICE_ACCOUNT_SCOPES = frozenset(
(*AI_REVIEW_SCOPES, TELEGRAM_BIND_REDEEM_SCOPE)
)
🧰 Tools
🪛 Ruff (0.16.0)

[warning] 14-14: Consider (*AI_REVIEW_SCOPES, TELEGRAM_BIND_REDEEM_SCOPE) instead of concatenation

Replace with (*AI_REVIEW_SCOPES, TELEGRAM_BIND_REDEEM_SCOPE)

(RUF005)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/service_accounts/scopes.py` around lines 13 - 15, Update the
ALLOWED_SERVICE_ACCOUNT_SCOPES definition to build the frozenset using iterable
unpacking for AI_REVIEW_SCOPES and TELEGRAM_BIND_REDEEM_SCOPE instead of tuple
concatenation, preserving the same allowed scope values.

Source: Linters/SAST tools

Comment on lines +51 to +67
class TelegramConnection(SocialConnection):
"""Telegram identity for a portal account.

Telegram has no OAuth flow here: the connection is created/confirmed when
the Deckard support bot redeems a Telegram group bind code issued by this
user (see validators.TelegramGroupBindCode). `platform_user_id` holds the
numeric Telegram user id (the stable identity); `platform_username` is the
display-only @username, which Telegram users can change at any time. The
inherited OAuth token fields stay empty.
"""

class Meta:
db_table = 'social_connections_telegram'
verbose_name = 'Telegram Connection'
verbose_name_plural = 'Telegram Connections'


Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

TelegramConnection doesn't inherit utils.models.BaseModel.

It subclasses the abstract SocialConnection, which is models.Model, not BaseModel. This mirrors existing GitHubConnection/TwitterConnection/DiscordConnection siblings, but as per coding guidelines, "All Django models must inherit from utils.models.BaseModel." Fixing this cleanly would require migrating the whole social_connections app's base, so this is best tracked as a follow-up rather than blocking this PR.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/social_connections/models.py` around lines 51 - 67, The requested
change is a broader follow-up: the social connection model hierarchy currently
does not inherit BaseModel. Do not modify TelegramConnection in this PR; track
updating SocialConnection and its GitHubConnection, TwitterConnection,
DiscordConnection, and TelegramConnection subclasses to inherit
utils.models.BaseModel as a separate migration-aware change.

Source: Coding guidelines

Comment thread backend/validators/views.py
Comment on lines +1300 to +1345

if bind_code.status == TelegramGroupBindCode.STATUS_REVOKED:
return Response(
{'error': 'This bind code was revoked.', 'code': 'revoked'},
status=status.HTTP_409_CONFLICT,
)
if bind_code.status == TelegramGroupBindCode.STATUS_REDEEMED:
return Response(
{'error': 'This bind code was already redeemed.', 'code': 'already_redeemed'},
status=status.HTTP_409_CONFLICT,
)
if not bind_code.is_redeemable(now):
if bind_code.status == TelegramGroupBindCode.STATUS_ISSUED:
bind_code.status = TelegramGroupBindCode.STATUS_EXPIRED
bind_code.save(update_fields=['status', 'updated_at'])
return Response(
{'error': 'This bind code has expired.', 'code': 'expired'},
status=status.HTTP_410_GONE,
)

bind_code.status = TelegramGroupBindCode.STATUS_REDEEMED
bind_code.redeemed_at = now
bind_code.redeemed_group_chat_id = group_chat_id
bind_code.redeemed_by_telegram_uid = telegram_uid
bind_code.save(update_fields=[
'status',
'redeemed_at',
'redeemed_group_chat_id',
'redeemed_by_telegram_uid',
'updated_at',
])

# Verifiable Telegram identity for the issuing portal account. The
# numeric uid is the stable identity; the display-only username is
# kept only when the bot supplies one, so a later redemption
# without it never blanks an existing handle.
connection_defaults = {
'platform_user_id': telegram_uid,
'linked_at': now,
}
if telegram_username:
connection_defaults['platform_username'] = telegram_username
TelegramConnection.objects.update_or_create(
user=bind_code.created_by,
defaults=connection_defaults,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Redeem endpoint has no throttle.

TelegramBindCodeRedeemView inherits the default ScopedRateThrottle with no throttle_scope, so it is effectively unthrottled. Even with high-entropy secrets, adding a scoped throttle bounds bot-side loops and abuse of a compromised service-account token.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/validators/views.py` around lines 1300 - 1345, Add an explicit
throttle scope to TelegramBindCodeRedeemView and configure the corresponding
scoped rate limit so the inherited ScopedRateThrottle is active for redemption
requests. Keep the existing redemption validation and response behavior
unchanged, using the project’s established throttle-scope configuration pattern.

Comment thread frontend/CLAUDE.md
Comment on lines +16 to +19

// Deckard Telegram support bot @username (without the @). When unset, the
// Telegram group linking page falls back to generic wording.
export const DECKARD_BOT_USERNAME = import.meta.env.VITE_DECKARD_BOT_USERNAME || '';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the new VITE_DECKARD_BOT_USERNAME variable.

Add it to the frontend env example/docs alongside the other VITE_ variables so deployments don't silently fall back to the generic wording.

Based on learnings: "Keep this reference updated when adding routes, components, API functions, pages, navigation, stores, Svelte 5 patterns, common fixes, or environment variables." and "Required environment variables include VITE_API_URL, VITE_VALIDATOR_RPC_URL, VITE_EXPLORER_URL, and VITE_RECAPTCHA_SITE_KEY."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/lib/config.js` around lines 16 - 19, Document the new
VITE_DECKARD_BOT_USERNAME environment variable in the frontend environment
example or documentation alongside the existing VITE_ variables, describing that
it should contain the bot username without the @ symbol. Keep the existing
DECKARD_BOT_USERNAME configuration behavior unchanged.

Source: Learnings

…v var

The bind code issuance log now identifies the issuing user by portal id
only, keeping full wallet addresses out of application log sinks in line
with the portal's address-privacy stance. The Deckard bot username
setting is now part of the documented frontend environment so
deployments configure it instead of silently falling back to generic
wording.

## Claude Implementation Notes
- backend/validators/views.py: issuance log drops request.user.address; user_id + code_id remain as identifiers
- frontend/CLAUDE.md: add VITE_DECKARD_BOT_USERNAME to the Environment Variables section
- frontend/.env.example: add VITE_DECKARD_BOT_USERNAME with a note that unset falls back to generic wording
@JoaquinBN
JoaquinBN merged commit c253b43 into dev Aug 2, 2026
3 checks passed
@JoaquinBN
JoaquinBN deleted the feature/telegram-group-bind-codes branch August 2, 2026 16:43
JoaquinBN added a commit that referenced this pull request Aug 5, 2026
Dev independently introduced its own TelegramConnection (Deckard support
group bind codes, PR #960) on the same table this branch creates, so this
merge reconciles the two Telegram identities into one model:

- social_connections/models.py keeps a single TelegramConnection carrying
  this branch's notifications_enabled/blocked_at fields and unique
  platform_user_id constraint, with a docstring covering both verifiable
  creation paths (portal bot /start deep link, Deckard bind-code
  redemption). Dev's duplicate class definition is removed.
- Dev's 0008_telegramconnection migration (already applied in deployed
  environments) is kept as the base; this branch's migration is
  regenerated as 0009_telegram_notifications_and_outbox, which only adds
  the new fields and constraint and creates TelegramMessage.
- admin.py keeps one TelegramConnection registration (both sides had one,
  which would have raised AlreadyRegistered) merging dev's
  platform_user_id search field into the richer admin.
- The Deckard bind-code redemption now rejects a Telegram account already
  linked to a different portal user with a clean 409 (pre-check before
  consuming the code, IntegrityError guard for the race) instead of
  crashing on the new unique constraint; the rejected code stays
  redeemable.
- CHANGELOG.md keeps both sides' entries.
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