mirror of
https://github.com/fromchat-messenger/web.git
synced 2026-09-22 19:15:08 +03:00
Fix inter-services communication
This commit is contained in:
+102
-90
@@ -1,103 +1,43 @@
|
||||
import asyncio
|
||||
import time
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi import FastAPI, Request, HTTPException
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from contextlib import asynccontextmanager
|
||||
import subprocess
|
||||
import sys
|
||||
import os
|
||||
from routes import account, messaging, profile, push, webrtc, devices, moderation
|
||||
import httpx
|
||||
import logging
|
||||
from backend.shared.models import User
|
||||
from backend.shared.constants import OWNER_USERNAME
|
||||
# Gateway doesn't need direct model access - it's a stateless proxy
|
||||
# Gateway doesn't need constants - it's a stateless proxy
|
||||
from backend.shared.utils import get_client_ip
|
||||
|
||||
from backend.shared.db import POOL_CONFIG, SessionLocal
|
||||
from logging_config import access_logger # noqa: F401 - ensure loggers configured
|
||||
from security.audit import log_access
|
||||
from security.rate_limit import limiter
|
||||
# Gateway doesn't need database access - it's a stateless proxy
|
||||
from backend.logging_config import access_logger # noqa: F401 - ensure loggers configured
|
||||
from backend.security.audit import log_access
|
||||
from backend.security.rate_limit import limiter
|
||||
from slowapi.middleware import SlowAPIMiddleware
|
||||
|
||||
# Service URL mapping for routing
|
||||
SERVICE_URLS = {
|
||||
"account": os.getenv("ACCOUNT_SERVICE_URL", "http://account_service:8302"),
|
||||
"profile": os.getenv("PROFILE_SERVICE_URL", "http://profile_service:8303"),
|
||||
"devices": os.getenv("DEVICE_SERVICE_URL", "http://device_service:8304"),
|
||||
"messaging": os.getenv("MESSAGING_SERVICE_URL", "http://messaging_service:8305"),
|
||||
"push": os.getenv("PUSH_SERVICE_URL", "http://push_service:8306"),
|
||||
"webrtc": os.getenv("WEBRTC_SERVICE_URL", "http://webrtc_service:8307"),
|
||||
"moderation": os.getenv("MODERATION_SERVICE_URL", "http://moderation_service:8308"),
|
||||
}
|
||||
|
||||
logger = logging.getLogger("uvicorn.error")
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
# Startup - run migration in subprocess to avoid logging interference
|
||||
try:
|
||||
logger.info("Starting database migration check...")
|
||||
# Run migration in a separate process
|
||||
subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
"-c",
|
||||
"import sys; sys.path.append('.'); from migration import run_migrations; run_migrations()"
|
||||
],
|
||||
cwd=os.path.dirname(os.path.abspath(__file__))
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to run database migrations: {e}")
|
||||
raise
|
||||
|
||||
try:
|
||||
with SessionLocal() as db:
|
||||
owner = db.query(User).filter(User.id == 1).first()
|
||||
if owner and not owner.verified:
|
||||
owner.verified = True
|
||||
db.commit()
|
||||
logger.info(f"Owner user '{OWNER_USERNAME}' has been verified")
|
||||
elif owner and owner.verified:
|
||||
logger.info(f"Owner user '{OWNER_USERNAME}' is already verified")
|
||||
else:
|
||||
logger.warning(f"Owner user '{OWNER_USERNAME}' not found")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to ensure owner verification: {e}")
|
||||
|
||||
logger.info(
|
||||
"SQLAlchemy pool configured (size=%s, max_overflow=%s, timeout=%ss, recycle=%ss, pre_ping=%s)",
|
||||
POOL_CONFIG["pool_size"],
|
||||
POOL_CONFIG["max_overflow"],
|
||||
POOL_CONFIG["pool_timeout"],
|
||||
POOL_CONFIG["pool_recycle"],
|
||||
POOL_CONFIG["pool_pre_ping"],
|
||||
)
|
||||
|
||||
# Start the messaging cleanup task
|
||||
try:
|
||||
from routes.messaging import messagingManager
|
||||
messagingManager.start_cleanup_task()
|
||||
logger.info("Messaging cleanup task started")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to start messaging cleanup task: {e}")
|
||||
|
||||
# Reset all rate limits on startup to ensure clean state
|
||||
# This prevents rate limits from persisting across restarts
|
||||
try:
|
||||
from security.rate_limit import reset_all_rate_limits
|
||||
cleared = reset_all_rate_limits()
|
||||
if cleared > 0:
|
||||
logger.info(f"Cleared {cleared} rate limit entries on startup")
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to reset rate limits on startup: {e}")
|
||||
|
||||
# Start the rate limit cleanup task
|
||||
try:
|
||||
from security.rate_limit import start_rate_limit_cleanup_task
|
||||
cleanup_task = asyncio.create_task(start_rate_limit_cleanup_task())
|
||||
logger.info("Rate limit cleanup task started")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to start rate limit cleanup task: {e}")
|
||||
cleanup_task = None
|
||||
|
||||
# Gateway is a stateless proxy - no database operations or background tasks needed
|
||||
logger.info("Gateway proxy service initialized - routing to microservices")
|
||||
yield
|
||||
|
||||
# Shutdown - cancel cleanup task if it exists
|
||||
if cleanup_task:
|
||||
cleanup_task.cancel()
|
||||
try:
|
||||
await cleanup_task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
logger.info("Gateway proxy service shutting down.")
|
||||
|
||||
# Инициализация FastAPI
|
||||
app = FastAPI(title="FromChat", lifespan=lifespan)
|
||||
@@ -168,11 +108,83 @@ app.add_middleware(
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# Routes
|
||||
app.include_router(account.router)
|
||||
app.include_router(messaging.router)
|
||||
app.include_router(profile.router)
|
||||
app.include_router(push.router, prefix="/push")
|
||||
app.include_router(webrtc.router, prefix="/webrtc")
|
||||
app.include_router(devices.router, prefix="/devices")
|
||||
app.include_router(moderation.router)
|
||||
# Common API endpoints - route to appropriate services (defined first for priority)
|
||||
@app.api_route("/login", methods=["POST"])
|
||||
async def login(request: Request):
|
||||
"""Login endpoint - routes to account service."""
|
||||
return await _proxy_to_service("account", "login", request)
|
||||
|
||||
@app.api_route("/register", methods=["POST"])
|
||||
async def register(request: Request):
|
||||
"""Register endpoint - routes to account service."""
|
||||
return await _proxy_to_service("account", "register", request)
|
||||
|
||||
@app.api_route("/chat/ws", methods=["GET"])
|
||||
async def chat_websocket(request: Request):
|
||||
"""Chat WebSocket endpoint - routes to messaging service."""
|
||||
return await _proxy_to_service("messaging", "chat/ws", request)
|
||||
|
||||
# API routes - route to appropriate microservices
|
||||
@app.api_route("/account/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"])
|
||||
async def proxy_account(path: str, request: Request):
|
||||
"""Proxy account service requests."""
|
||||
return await _proxy_to_service("account", path, request)
|
||||
|
||||
@app.api_route("/profile/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"])
|
||||
async def proxy_profile(path: str, request: Request):
|
||||
"""Proxy profile service requests."""
|
||||
return await _proxy_to_service("profile", path, request)
|
||||
|
||||
@app.api_route("/devices/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"])
|
||||
async def proxy_devices(path: str, request: Request):
|
||||
"""Proxy device service requests."""
|
||||
return await _proxy_to_service("devices", path, request)
|
||||
|
||||
@app.api_route("/messaging/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"])
|
||||
async def proxy_messaging(path: str, request: Request):
|
||||
"""Proxy messaging service requests."""
|
||||
return await _proxy_to_service("messaging", path, request)
|
||||
|
||||
@app.api_route("/push/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"])
|
||||
async def proxy_push(path: str, request: Request):
|
||||
"""Proxy push service requests."""
|
||||
return await _proxy_to_service("push", path, request)
|
||||
|
||||
@app.api_route("/webrtc/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"])
|
||||
async def proxy_webrtc(path: str, request: Request):
|
||||
"""Proxy WebRTC service requests."""
|
||||
return await _proxy_to_service("webrtc", path, request)
|
||||
|
||||
@app.api_route("/moderation/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"])
|
||||
async def proxy_moderation(path: str, request: Request):
|
||||
"""Proxy moderation service requests."""
|
||||
return await _proxy_to_service("moderation", path, request)
|
||||
|
||||
|
||||
async def _proxy_to_service(service: str, path: str, request: Request):
|
||||
"""Helper function to proxy requests to microservices."""
|
||||
service_url = SERVICE_URLS[service]
|
||||
target_url = f"{service_url}/{service}/{path}"
|
||||
|
||||
# Get request body
|
||||
body = await request.body()
|
||||
|
||||
# Prepare headers (remove host header)
|
||||
headers = dict(request.headers)
|
||||
headers.pop("host", None)
|
||||
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=30.0) as client:
|
||||
response = await client.request(
|
||||
method=request.method,
|
||||
url=target_url,
|
||||
headers=headers,
|
||||
content=body,
|
||||
params=request.query_params,
|
||||
)
|
||||
return response.json() if response.headers.get("content-type", "").startswith("application/json") else response.text
|
||||
except httpx.RequestError as exc:
|
||||
logging.error(f"Error communicating with {service} service: {exc}")
|
||||
raise HTTPException(status_code=503, detail=f"Service {service} unavailable")
|
||||
|
||||
# Routes are handled by the catch-all proxy above
|
||||
@@ -20,7 +20,8 @@ from backend.shared.dependencies import get_current_user, get_db
|
||||
from backend.shared.utils import convert_user
|
||||
from backend.shared.constants import OWNER_USERNAME
|
||||
from backend.shared.models import Message, SendMessageRequest, EditMessageRequest, User, DMEnvelope, MessageFile, DMFile, Reaction, ReactionRequest, ReactionResponse, DMReaction, DMReactionRequest, DMReactionResponse, UpdateLog
|
||||
import backend.push_service as push_service
|
||||
import os
|
||||
import httpx
|
||||
from PIL import Image
|
||||
import io
|
||||
import json
|
||||
@@ -29,7 +30,7 @@ from better_profanity import profanity as _bp
|
||||
from backend.security.audit import log_access, log_dm, log_public_chat, log_security
|
||||
from backend.security.profanity import contains_profanity
|
||||
from backend.security.rate_limit import rate_limit_per_ip
|
||||
from backend.websocket.utils import authenticate_user
|
||||
from backend.services.messaging.files.websocket.utils import authenticate_user
|
||||
|
||||
from backend.shared.models import FcmToken
|
||||
|
||||
@@ -393,7 +394,16 @@ async def _send_message_internal(
|
||||
|
||||
# Send push notifications for public messages
|
||||
try:
|
||||
await push_service.send_public_message_notification(db, new_message, exclude_user_id=current_user.id)
|
||||
push_service_url = os.getenv("PUSH_SERVICE_URL", "http://push_service:8306")
|
||||
async with httpx.AsyncClient(timeout=10.0) as client:
|
||||
response = await client.post(
|
||||
f"{push_service_url}/push/send-public-notification",
|
||||
json={
|
||||
"message_id": new_message.id,
|
||||
"exclude_user_id": current_user.id
|
||||
}
|
||||
)
|
||||
response.raise_for_status()
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to send push notification for message {new_message.id}: {e}")
|
||||
|
||||
@@ -1560,4 +1570,22 @@ async def get_file_encrypted(filename: str, current_user: User = Depends(get_cur
|
||||
else:
|
||||
raise HTTPException(500)
|
||||
|
||||
return FileResponse(str(path))
|
||||
return FileResponse(str(path))
|
||||
|
||||
|
||||
class SendSuspensionRequest(BaseModel):
|
||||
user_id: int
|
||||
reason: str
|
||||
|
||||
|
||||
@router.post("/send-suspension")
|
||||
async def send_suspension_to_user(
|
||||
request: SendSuspensionRequest,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Send suspension message to user via WebSocket (called by profile service)"""
|
||||
try:
|
||||
await messagingManager.send_suspension_to_user(request.user_id, request.reason)
|
||||
return {"status": "success"}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
@@ -13,8 +13,9 @@ from backend.shared.dependencies import get_db, get_current_user
|
||||
from backend.shared.models import User, UpdateBioRequest, UserProfileResponse
|
||||
from pydantic import BaseModel
|
||||
from backend.shared.validation import is_valid_username, is_valid_display_name
|
||||
from backend.similarity import is_user_similar_to_verified
|
||||
from .messaging import messagingManager
|
||||
from backend.shared.similarity import is_user_similar_to_verified
|
||||
import os
|
||||
import httpx
|
||||
from backend.security.audit import log_security
|
||||
from backend.security.profanity import contains_profanity
|
||||
from backend.security.rate_limit import rate_limit_per_ip
|
||||
@@ -479,7 +480,16 @@ async def suspend_user(
|
||||
|
||||
# Send WebSocket suspension message
|
||||
try:
|
||||
await messagingManager.send_suspension_to_user(user_id, request.reason)
|
||||
messaging_service_url = os.getenv("MESSAGING_SERVICE_URL", "http://messaging_service:8305")
|
||||
async with httpx.AsyncClient(timeout=10.0) as client:
|
||||
response = await client.post(
|
||||
f"{messaging_service_url}/messaging/send-suspension",
|
||||
json={
|
||||
"user_id": user_id,
|
||||
"reason": request.reason
|
||||
}
|
||||
)
|
||||
response.raise_for_status()
|
||||
except Exception as e:
|
||||
# Log error but don't fail the request
|
||||
pass
|
||||
|
||||
+25
-2
@@ -1,11 +1,16 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from pydantic import BaseModel
|
||||
from backend.shared.dependencies import get_current_user, get_db
|
||||
from backend.shared.models import User, PushSubscriptionRequest
|
||||
import backend.push_service as push_service
|
||||
from backend.services.push.files import push_service
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
class SendPublicNotificationRequest(BaseModel):
|
||||
message_id: int
|
||||
exclude_user_id: int
|
||||
|
||||
@router.post("/subscribe")
|
||||
async def subscribe_to_push_notifications(
|
||||
request: PushSubscriptionRequest,
|
||||
@@ -37,10 +42,28 @@ async def unsubscribe_from_push_notifications(
|
||||
"""Unsubscribe user from push notifications"""
|
||||
try:
|
||||
success = await push_service.unsubscribe_user(db=db, user_id=current_user.id)
|
||||
|
||||
|
||||
if success:
|
||||
return {"status": "success", "message": "Push notifications disabled"}
|
||||
else:
|
||||
raise HTTPException(status_code=500, detail="Failed to disable push notifications")
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.post("/send-public-notification")
|
||||
async def send_public_message_notification(
|
||||
request: SendPublicNotificationRequest,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Send push notification for public message (called by messaging service)"""
|
||||
try:
|
||||
# Get the message from database
|
||||
from backend.shared.models import Message
|
||||
message = db.query(Message).filter(Message.id == request.message_id).first()
|
||||
if not message:
|
||||
raise HTTPException(status_code=404, detail="Message not found")
|
||||
|
||||
await push_service.send_public_message_notification(db, message, exclude_user_id=request.exclude_user_id)
|
||||
return {"status": "success"}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@@ -10,13 +10,13 @@ import uvicorn
|
||||
import os
|
||||
|
||||
# Import service routers
|
||||
from routes.account import router as account_router
|
||||
from routes.profile import router as profile_router
|
||||
from routes.devices import router as device_router
|
||||
from routes.messaging import router as messaging_router
|
||||
from routes.push import router as push_router
|
||||
from routes.webrtc import router as webrtc_router
|
||||
from routes.moderation import router as moderation_router
|
||||
from backend.routes.account import router as account_router
|
||||
from backend.routes.profile import router as profile_router
|
||||
from backend.routes.devices import router as device_router
|
||||
from backend.routes.messaging import router as messaging_router
|
||||
from backend.routes.push import router as push_router
|
||||
from backend.routes.webrtc import router as webrtc_router
|
||||
from backend.routes.moderation import router as moderation_router
|
||||
|
||||
# Import security modules
|
||||
from security.audit import log_access
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
from fastapi import FastAPI
|
||||
|
||||
# Gateway service - handles complex operations that Caddy cannot
|
||||
# This will be expanded later with routing logic to other services
|
||||
# Gateway service - runs the main gateway app from backend/app.py
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = FastAPI(title="Gateway Service")
|
||||
|
||||
from backend.app import app
|
||||
import os, uvicorn
|
||||
uvicorn.run(app, host="0.0.0.0", port=int(os.getenv("PORT", 8301)))
|
||||
uvicorn.run(app, host="0.0.0.0", port=int(os.getenv("PORT", 8300)))
|
||||
|
||||
+46
-29
@@ -9,6 +9,7 @@ Base = declarative_base()
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = "users"
|
||||
__table_args__ = {"schema": "account_schema"}
|
||||
|
||||
id = Column(BigInteger, primary_key=True, index=True)
|
||||
username = Column(String(50), unique=True, index=True, nullable=False)
|
||||
@@ -42,9 +43,10 @@ class User(Base):
|
||||
|
||||
class Message(Base):
|
||||
__tablename__ = "messages"
|
||||
__table_args__ = {"schema": "messaging_schema"}
|
||||
|
||||
id = Column(BigInteger, primary_key=True, index=True)
|
||||
sender_id = Column(BigInteger, ForeignKey("users.id"), nullable=False, index=True)
|
||||
sender_id = Column(BigInteger, ForeignKey("account_schema.users.id"), nullable=False, index=True)
|
||||
content = Column(Text, nullable=False)
|
||||
content_type = Column(String(50), default="text")
|
||||
encrypted_content = Column(Text, nullable=True)
|
||||
@@ -53,8 +55,8 @@ class Message(Base):
|
||||
edited_at = Column(DateTime, nullable=True)
|
||||
edited = Column(Boolean, default=False)
|
||||
deleted = Column(Boolean, default=False)
|
||||
reply_to_id = Column(BigInteger, ForeignKey("messages.id"), nullable=True)
|
||||
thread_id = Column(BigInteger, ForeignKey("messages.id"), nullable=True)
|
||||
reply_to_id = Column(BigInteger, ForeignKey("messaging_schema.messages.id"), nullable=True)
|
||||
thread_id = Column(BigInteger, ForeignKey("messaging_schema.messages.id"), nullable=True)
|
||||
is_public = Column(Boolean, default=False)
|
||||
|
||||
# Relationships
|
||||
@@ -63,13 +65,15 @@ class Message(Base):
|
||||
reply_to = relationship("Message", remote_side=[id], foreign_keys=[reply_to_id])
|
||||
thread = relationship("Message", remote_side=[id], foreign_keys=[thread_id])
|
||||
reactions = relationship("MessageReaction", back_populates="message", cascade="all, delete-orphan")
|
||||
files = relationship("MessageFile", back_populates="message", cascade="all, delete-orphan")
|
||||
|
||||
class MessageRecipient(Base):
|
||||
__tablename__ = "message_recipients"
|
||||
__table_args__ = {"schema": "messaging_schema"}
|
||||
|
||||
id = Column(BigInteger, primary_key=True, index=True)
|
||||
message_id = Column(BigInteger, ForeignKey("messages.id"), nullable=False, index=True)
|
||||
recipient_id = Column(BigInteger, ForeignKey("users.id"), nullable=False, index=True)
|
||||
message_id = Column(BigInteger, ForeignKey("messaging_schema.messages.id"), nullable=False, index=True)
|
||||
recipient_id = Column(BigInteger, ForeignKey("account_schema.users.id"), nullable=False, index=True)
|
||||
read_at = Column(DateTime, nullable=True)
|
||||
delivered_at = Column(DateTime, nullable=True)
|
||||
encrypted_key = Column(Text, nullable=True)
|
||||
@@ -80,10 +84,11 @@ class MessageRecipient(Base):
|
||||
|
||||
class MessageReaction(Base):
|
||||
__tablename__ = "message_reactions"
|
||||
__table_args__ = {"schema": "messaging_schema"}
|
||||
|
||||
id = Column(BigInteger, primary_key=True, index=True)
|
||||
message_id = Column(BigInteger, ForeignKey("messages.id"), nullable=False, index=True)
|
||||
user_id = Column(BigInteger, ForeignKey("users.id"), nullable=False, index=True)
|
||||
message_id = Column(BigInteger, ForeignKey("messaging_schema.messages.id"), nullable=False, index=True)
|
||||
user_id = Column(BigInteger, ForeignKey("account_schema.users.id"), nullable=False, index=True)
|
||||
reaction = Column(String(50), nullable=False)
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
@@ -92,9 +97,10 @@ class MessageReaction(Base):
|
||||
|
||||
class Device(Base):
|
||||
__tablename__ = "devices"
|
||||
__table_args__ = {"schema": "device_schema"}
|
||||
|
||||
id = Column(BigInteger, primary_key=True, index=True)
|
||||
user_id = Column(BigInteger, ForeignKey("users.id"), nullable=False, index=True)
|
||||
user_id = Column(BigInteger, ForeignKey("account_schema.users.id"), nullable=False, index=True)
|
||||
device_id = Column(String(255), unique=True, nullable=False, index=True)
|
||||
device_name = Column(String(255), nullable=True)
|
||||
device_type = Column(String(50), nullable=True)
|
||||
@@ -110,10 +116,11 @@ class Device(Base):
|
||||
|
||||
class PushSubscription(Base):
|
||||
__tablename__ = "push_subscriptions"
|
||||
__table_args__ = {"schema": "push_schema"}
|
||||
|
||||
id = Column(BigInteger, primary_key=True, index=True)
|
||||
user_id = Column(BigInteger, ForeignKey("users.id"), nullable=False, index=True)
|
||||
device_id = Column(BigInteger, ForeignKey("devices.id"), nullable=True, index=True)
|
||||
user_id = Column(BigInteger, ForeignKey("account_schema.users.id"), nullable=False, index=True)
|
||||
device_id = Column(BigInteger, ForeignKey("device_schema.devices.id"), nullable=True, index=True)
|
||||
endpoint = Column(String(500), nullable=False)
|
||||
p256dh = Column(String(255), nullable=False)
|
||||
auth = Column(String(255), nullable=False)
|
||||
@@ -126,10 +133,11 @@ class PushSubscription(Base):
|
||||
|
||||
class WebRTCSession(Base):
|
||||
__tablename__ = "webrtc_sessions"
|
||||
__table_args__ = {"schema": "webrtc_schema"}
|
||||
|
||||
id = Column(BigInteger, primary_key=True, index=True)
|
||||
session_id = Column(String(255), unique=True, nullable=False, index=True)
|
||||
initiator_id = Column(BigInteger, ForeignKey("users.id"), nullable=False)
|
||||
initiator_id = Column(BigInteger, ForeignKey("account_schema.users.id"), nullable=False)
|
||||
participant_ids = Column(JSON, nullable=False)
|
||||
offer = Column(JSON, nullable=True)
|
||||
answer = Column(JSON, nullable=True)
|
||||
@@ -140,11 +148,12 @@ class WebRTCSession(Base):
|
||||
|
||||
class ModerationAction(Base):
|
||||
__tablename__ = "moderation_actions"
|
||||
__table_args__ = {"schema": "moderation_schema"}
|
||||
|
||||
id = Column(BigInteger, primary_key=True, index=True)
|
||||
moderator_id = Column(BigInteger, ForeignKey("users.id"), nullable=False)
|
||||
target_user_id = Column(BigInteger, ForeignKey("users.id"), nullable=True)
|
||||
target_message_id = Column(BigInteger, ForeignKey("messages.id"), nullable=True)
|
||||
moderator_id = Column(BigInteger, ForeignKey("account_schema.users.id"), nullable=False)
|
||||
target_user_id = Column(BigInteger, ForeignKey("account_schema.users.id"), nullable=True)
|
||||
target_message_id = Column(BigInteger, ForeignKey("messaging_schema.messages.id"), nullable=True)
|
||||
action_type = Column(String(50), nullable=False)
|
||||
reason = Column(Text, nullable=True)
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
@@ -153,9 +162,10 @@ class ModerationAction(Base):
|
||||
|
||||
class MessageFile(Base):
|
||||
__tablename__ = "message_file"
|
||||
__table_args__ = {"schema": "messaging_schema"}
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
message_id = Column(BigInteger, ForeignKey("messages.id"), nullable=False, index=True)
|
||||
message_id = Column(BigInteger, ForeignKey("messaging_schema.messages.id"), nullable=False, index=True)
|
||||
path = Column(Text, nullable=False)
|
||||
name = Column(Text, nullable=False)
|
||||
|
||||
@@ -166,7 +176,7 @@ class CryptoPublicKey(Base):
|
||||
__tablename__ = "crypto_public_key"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
user_id = Column(BigInteger, ForeignKey("users.id"), nullable=False, unique=True)
|
||||
user_id = Column(BigInteger, ForeignKey("account_schema.users.id"), nullable=False, unique=True)
|
||||
public_key_b64 = Column(Text, nullable=False)
|
||||
|
||||
|
||||
@@ -174,16 +184,17 @@ class CryptoBackup(Base):
|
||||
__tablename__ = "crypto_backup"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
user_id = Column(BigInteger, ForeignKey("users.id"), nullable=False, unique=True)
|
||||
user_id = Column(BigInteger, ForeignKey("account_schema.users.id"), nullable=False, unique=True)
|
||||
blob_json = Column(Text, nullable=False)
|
||||
|
||||
|
||||
class DMEnvelope(Base):
|
||||
__tablename__ = "dm_envelope"
|
||||
__table_args__ = {"schema": "messaging_schema"}
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
sender_id = Column(BigInteger, ForeignKey("users.id"), nullable=False)
|
||||
recipient_id = Column(BigInteger, ForeignKey("users.id"), nullable=False)
|
||||
sender_id = Column(BigInteger, ForeignKey("account_schema.users.id"), nullable=False)
|
||||
recipient_id = Column(BigInteger, ForeignKey("account_schema.users.id"), nullable=False)
|
||||
iv_b64 = Column(Text, nullable=False)
|
||||
ciphertext_b64 = Column(Text, nullable=False)
|
||||
salt_b64 = Column(Text, nullable=False)
|
||||
@@ -197,11 +208,12 @@ class DMEnvelope(Base):
|
||||
|
||||
class DMFile(Base):
|
||||
__tablename__ = "dm_file"
|
||||
__table_args__ = {"schema": "messaging_schema"}
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
message_id = Column(Integer, ForeignKey("dm_envelope.id"), nullable=False, index=True)
|
||||
sender_id = Column(BigInteger, ForeignKey("users.id"), nullable=False)
|
||||
recipient_id = Column(BigInteger, ForeignKey("users.id"), nullable=False)
|
||||
message_id = Column(Integer, ForeignKey("messaging_schema.dm_envelope.id"), nullable=False, index=True)
|
||||
sender_id = Column(BigInteger, ForeignKey("account_schema.users.id"), nullable=False)
|
||||
recipient_id = Column(BigInteger, ForeignKey("account_schema.users.id"), nullable=False)
|
||||
name = Column(Text, nullable=False)
|
||||
path = Column(Text, nullable=False)
|
||||
|
||||
@@ -210,9 +222,10 @@ class DMFile(Base):
|
||||
|
||||
class FcmToken(Base):
|
||||
__tablename__ = "fcm_token"
|
||||
__table_args__ = {"schema": "push_schema"}
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
user_id = Column(BigInteger, ForeignKey("users.id"), nullable=False, index=True)
|
||||
user_id = Column(BigInteger, ForeignKey("account_schema.users.id"), nullable=False, index=True)
|
||||
token = Column(Text, nullable=False, unique=True)
|
||||
created_at = Column(DateTime, default=datetime.now)
|
||||
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
|
||||
@@ -220,10 +233,11 @@ class FcmToken(Base):
|
||||
|
||||
class Reaction(Base):
|
||||
__tablename__ = "reaction"
|
||||
__table_args__ = {"schema": "messaging_schema"}
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
message_id = Column(BigInteger, ForeignKey("messages.id"), nullable=False, index=True)
|
||||
user_id = Column(BigInteger, ForeignKey("users.id"), nullable=False)
|
||||
message_id = Column(BigInteger, ForeignKey("messaging_schema.messages.id"), nullable=False, index=True)
|
||||
user_id = Column(BigInteger, ForeignKey("account_schema.users.id"), nullable=False)
|
||||
emoji = Column(String(10), nullable=False) # Store emoji as string
|
||||
timestamp = Column(DateTime, default=datetime.now)
|
||||
|
||||
@@ -236,10 +250,11 @@ class Reaction(Base):
|
||||
|
||||
class DMReaction(Base):
|
||||
__tablename__ = "dm_reaction"
|
||||
__table_args__ = {"schema": "messaging_schema"}
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
dm_envelope_id = Column(Integer, ForeignKey("dm_envelope.id"), nullable=False, index=True)
|
||||
user_id = Column(BigInteger, ForeignKey("users.id"), nullable=False)
|
||||
dm_envelope_id = Column(Integer, ForeignKey("messaging_schema.dm_envelope.id"), nullable=False, index=True)
|
||||
user_id = Column(BigInteger, ForeignKey("account_schema.users.id"), nullable=False)
|
||||
emoji = Column(String(10), nullable=False) # Store emoji as string
|
||||
timestamp = Column(DateTime, default=datetime.now)
|
||||
|
||||
@@ -254,9 +269,10 @@ class DMReaction(Base):
|
||||
# Tracks authenticated device sessions per user
|
||||
class DeviceSession(Base):
|
||||
__tablename__ = "device_session"
|
||||
__table_args__ = {"schema": "device_schema"}
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
user_id = Column(BigInteger, ForeignKey("users.id"), nullable=False, index=True)
|
||||
user_id = Column(BigInteger, ForeignKey("account_schema.users.id"), nullable=False, index=True)
|
||||
|
||||
# Raw User-Agent for reference/debugging
|
||||
raw_user_agent = Column(Text, nullable=True)
|
||||
@@ -392,9 +408,10 @@ class DMReactionResponse(BaseModel):
|
||||
class UpdateLog(Base):
|
||||
"""Stores update sequence numbers and updates for gap detection"""
|
||||
__tablename__ = "update_log"
|
||||
__table_args__ = {"schema": "public"}
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
user_id = Column(BigInteger, ForeignKey("users.id"), nullable=False, index=True)
|
||||
user_id = Column(BigInteger, ForeignKey("account_schema.users.id"), nullable=False, index=True)
|
||||
sequence = Column(Integer, nullable=False, index=True)
|
||||
updates = Column(Text, nullable=False) # JSON array of updates
|
||||
timestamp = Column(DateTime, default=datetime.now, index=True)
|
||||
|
||||
Reference in New Issue
Block a user