## Agents Added - Alateya: R&D, biotech, innovations - Clan (Spirit): Community spirit agent - Eonarch: Consciousness evolution agent ## Changes - docker-compose.node1.yml: Added tokens for all 3 new agents - gateway-bot/http_api.py: Added configs and webhook endpoints - gateway-bot/clan_prompt.txt: New prompt file - gateway-bot/eonarch_prompt.txt: New prompt file ## Fixes - Fixed ROUTER_URL from :9102 to :8000 (internal container port) - All 9 Telegram agents now working ## Documentation - Created PROJECT-MASTER-INDEX.md - single entry point - Added various status documents and scripts Tokens configured: - Helion, NUTRA, Agromatrix (existing) - Alateya, Clan, Eonarch (new) - Druid, GreenFood, DAARWIZZ (configured)
178 lines
5.5 KiB
Python
178 lines
5.5 KiB
Python
# Identity Endpoints for Account Linking (Telegram ↔ Energy Union)
|
|
# This file is appended to main.py
|
|
|
|
import secrets
|
|
from datetime import datetime, timedelta
|
|
from pydantic import BaseModel
|
|
from typing import Optional
|
|
|
|
|
|
# ============================================================================
|
|
# IDENTITY & ACCOUNT LINKING
|
|
# ============================================================================
|
|
|
|
class LinkStartRequest(BaseModel):
|
|
account_id: str # UUID as string
|
|
ttl_minutes: int = 10
|
|
|
|
|
|
class LinkStartResponse(BaseModel):
|
|
link_code: str
|
|
expires_at: datetime
|
|
|
|
|
|
class ResolveResponse(BaseModel):
|
|
account_id: Optional[str] = None
|
|
linked: bool = False
|
|
linked_at: Optional[datetime] = None
|
|
|
|
|
|
@app.post("/identity/link/start", response_model=LinkStartResponse)
|
|
async def start_link(request: LinkStartRequest):
|
|
"""
|
|
Generate a one-time link code for account linking.
|
|
This is called from Energy Union dashboard when user clicks "Link Telegram".
|
|
"""
|
|
try:
|
|
# Generate secure random code
|
|
link_code = secrets.token_urlsafe(16)[:20].upper()
|
|
expires_at = datetime.utcnow() + timedelta(minutes=request.ttl_minutes)
|
|
|
|
# Store in database
|
|
await db.pool.execute(
|
|
"""
|
|
INSERT INTO link_codes (code, account_id, expires_at, generated_via)
|
|
VALUES ($1, $2::uuid, $3, 'api')
|
|
""",
|
|
link_code,
|
|
request.account_id,
|
|
expires_at
|
|
)
|
|
|
|
logger.info("link_code_generated", account_id=request.account_id, code=link_code[:4] + "***")
|
|
|
|
return LinkStartResponse(
|
|
link_code=link_code,
|
|
expires_at=expires_at
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error("link_code_generation_failed", error=str(e))
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
@app.get("/identity/resolve", response_model=ResolveResponse)
|
|
async def resolve_telegram(telegram_user_id: int):
|
|
"""
|
|
Resolve Telegram user ID to Energy Union account ID.
|
|
Returns null account_id if not linked.
|
|
"""
|
|
try:
|
|
row = await db.pool.fetchrow(
|
|
"""
|
|
SELECT account_id, linked_at
|
|
FROM account_links
|
|
WHERE telegram_user_id = $1 AND status = 'active'
|
|
""",
|
|
telegram_user_id
|
|
)
|
|
|
|
if row:
|
|
return ResolveResponse(
|
|
account_id=str(row['account_id']),
|
|
linked=True,
|
|
linked_at=row['linked_at']
|
|
)
|
|
else:
|
|
return ResolveResponse(linked=False)
|
|
|
|
except Exception as e:
|
|
logger.error("telegram_resolve_failed", error=str(e))
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
@app.get("/identity/user/{account_id}/timeline")
|
|
async def get_user_timeline(
|
|
account_id: str,
|
|
limit: int = Query(default=20, le=100),
|
|
channel: Optional[str] = None
|
|
):
|
|
"""
|
|
Get user's interaction timeline across all channels.
|
|
Only available for linked accounts.
|
|
"""
|
|
try:
|
|
if channel:
|
|
rows = await db.pool.fetch(
|
|
"""
|
|
SELECT id, channel, channel_id, event_type, summary,
|
|
metadata, importance_score, event_at
|
|
FROM user_timeline
|
|
WHERE account_id = $1::uuid AND channel = $2
|
|
ORDER BY event_at DESC
|
|
LIMIT $3
|
|
""",
|
|
account_id, channel, limit
|
|
)
|
|
else:
|
|
rows = await db.pool.fetch(
|
|
"""
|
|
SELECT id, channel, channel_id, event_type, summary,
|
|
metadata, importance_score, event_at
|
|
FROM user_timeline
|
|
WHERE account_id = $1::uuid
|
|
ORDER BY event_at DESC
|
|
LIMIT $3
|
|
""",
|
|
account_id, limit
|
|
)
|
|
|
|
events = []
|
|
for row in rows:
|
|
events.append({
|
|
"id": str(row['id']),
|
|
"channel": row['channel'],
|
|
"channel_id": row['channel_id'],
|
|
"event_type": row['event_type'],
|
|
"summary": row['summary'],
|
|
"metadata": row['metadata'] or {},
|
|
"importance_score": row['importance_score'],
|
|
"event_at": row['event_at'].isoformat()
|
|
})
|
|
|
|
return {"events": events, "account_id": account_id, "count": len(events)}
|
|
|
|
except Exception as e:
|
|
logger.error("timeline_fetch_failed", error=str(e), account_id=account_id)
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
@app.post("/identity/timeline/add")
|
|
async def add_timeline_event(
|
|
account_id: str,
|
|
channel: str,
|
|
channel_id: str,
|
|
event_type: str,
|
|
summary: str,
|
|
metadata: Optional[dict] = None,
|
|
importance_score: float = 0.5
|
|
):
|
|
"""
|
|
Add an event to user's timeline.
|
|
Called by Gateway when processing messages from linked accounts.
|
|
"""
|
|
try:
|
|
event_id = await db.pool.fetchval(
|
|
"""
|
|
SELECT add_timeline_event($1::uuid, $2, $3, $4, $5, $6::jsonb, $7)
|
|
""",
|
|
account_id, channel, channel_id, event_type,
|
|
summary, metadata or {}, importance_score
|
|
)
|
|
|
|
return {"event_id": str(event_id), "success": True}
|
|
|
|
except Exception as e:
|
|
logger.error("timeline_add_failed", error=str(e))
|
|
raise HTTPException(status_code=500, detail=str(e))
|