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 asyncio
|
||||||
import time
|
import time
|
||||||
from fastapi import FastAPI, Request
|
from fastapi import FastAPI, Request, HTTPException
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
from routes import account, messaging, profile, push, webrtc, devices, moderation
|
import httpx
|
||||||
import logging
|
import logging
|
||||||
from backend.shared.models import User
|
# Gateway doesn't need direct model access - it's a stateless proxy
|
||||||
from backend.shared.constants import OWNER_USERNAME
|
# Gateway doesn't need constants - it's a stateless proxy
|
||||||
from backend.shared.utils import get_client_ip
|
from backend.shared.utils import get_client_ip
|
||||||
|
|
||||||
from backend.shared.db import POOL_CONFIG, SessionLocal
|
# Gateway doesn't need database access - it's a stateless proxy
|
||||||
from logging_config import access_logger # noqa: F401 - ensure loggers configured
|
from backend.logging_config import access_logger # noqa: F401 - ensure loggers configured
|
||||||
from security.audit import log_access
|
from backend.security.audit import log_access
|
||||||
from security.rate_limit import limiter
|
from backend.security.rate_limit import limiter
|
||||||
from slowapi.middleware import SlowAPIMiddleware
|
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")
|
logger = logging.getLogger("uvicorn.error")
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def lifespan(app: FastAPI):
|
async def lifespan(app: FastAPI):
|
||||||
# Startup - run migration in subprocess to avoid logging interference
|
# Gateway is a stateless proxy - no database operations or background tasks needed
|
||||||
try:
|
logger.info("Gateway proxy service initialized - routing to microservices")
|
||||||
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
|
|
||||||
|
|
||||||
yield
|
yield
|
||||||
|
logger.info("Gateway proxy service shutting down.")
|
||||||
# Shutdown - cancel cleanup task if it exists
|
|
||||||
if cleanup_task:
|
|
||||||
cleanup_task.cancel()
|
|
||||||
try:
|
|
||||||
await cleanup_task
|
|
||||||
except asyncio.CancelledError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Инициализация FastAPI
|
# Инициализация FastAPI
|
||||||
app = FastAPI(title="FromChat", lifespan=lifespan)
|
app = FastAPI(title="FromChat", lifespan=lifespan)
|
||||||
@@ -168,11 +108,83 @@ app.add_middleware(
|
|||||||
allow_headers=["*"],
|
allow_headers=["*"],
|
||||||
)
|
)
|
||||||
|
|
||||||
# Routes
|
# Common API endpoints - route to appropriate services (defined first for priority)
|
||||||
app.include_router(account.router)
|
@app.api_route("/login", methods=["POST"])
|
||||||
app.include_router(messaging.router)
|
async def login(request: Request):
|
||||||
app.include_router(profile.router)
|
"""Login endpoint - routes to account service."""
|
||||||
app.include_router(push.router, prefix="/push")
|
return await _proxy_to_service("account", "login", request)
|
||||||
app.include_router(webrtc.router, prefix="/webrtc")
|
|
||||||
app.include_router(devices.router, prefix="/devices")
|
@app.api_route("/register", methods=["POST"])
|
||||||
app.include_router(moderation.router)
|
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.utils import convert_user
|
||||||
from backend.shared.constants import OWNER_USERNAME
|
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
|
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
|
from PIL import Image
|
||||||
import io
|
import io
|
||||||
import json
|
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.audit import log_access, log_dm, log_public_chat, log_security
|
||||||
from backend.security.profanity import contains_profanity
|
from backend.security.profanity import contains_profanity
|
||||||
from backend.security.rate_limit import rate_limit_per_ip
|
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
|
from backend.shared.models import FcmToken
|
||||||
|
|
||||||
@@ -393,7 +394,16 @@ async def _send_message_internal(
|
|||||||
|
|
||||||
# Send push notifications for public messages
|
# Send push notifications for public messages
|
||||||
try:
|
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:
|
except Exception as e:
|
||||||
logger.error(f"Failed to send push notification for message {new_message.id}: {e}")
|
logger.error(f"Failed to send push notification for message {new_message.id}: {e}")
|
||||||
|
|
||||||
@@ -1561,3 +1571,21 @@ async def get_file_encrypted(filename: str, current_user: User = Depends(get_cur
|
|||||||
raise HTTPException(500)
|
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 backend.shared.models import User, UpdateBioRequest, UserProfileResponse
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from backend.shared.validation import is_valid_username, is_valid_display_name
|
from backend.shared.validation import is_valid_username, is_valid_display_name
|
||||||
from backend.similarity import is_user_similar_to_verified
|
from backend.shared.similarity import is_user_similar_to_verified
|
||||||
from .messaging import messagingManager
|
import os
|
||||||
|
import httpx
|
||||||
from backend.security.audit import log_security
|
from backend.security.audit import log_security
|
||||||
from backend.security.profanity import contains_profanity
|
from backend.security.profanity import contains_profanity
|
||||||
from backend.security.rate_limit import rate_limit_per_ip
|
from backend.security.rate_limit import rate_limit_per_ip
|
||||||
@@ -479,7 +480,16 @@ async def suspend_user(
|
|||||||
|
|
||||||
# Send WebSocket suspension message
|
# Send WebSocket suspension message
|
||||||
try:
|
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:
|
except Exception as e:
|
||||||
# Log error but don't fail the request
|
# Log error but don't fail the request
|
||||||
pass
|
pass
|
||||||
|
|||||||
+24
-1
@@ -1,11 +1,16 @@
|
|||||||
from fastapi import APIRouter, Depends, HTTPException
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
from pydantic import BaseModel
|
||||||
from backend.shared.dependencies import get_current_user, get_db
|
from backend.shared.dependencies import get_current_user, get_db
|
||||||
from backend.shared.models import User, PushSubscriptionRequest
|
from backend.shared.models import User, PushSubscriptionRequest
|
||||||
import backend.push_service as push_service
|
from backend.services.push.files import push_service
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
class SendPublicNotificationRequest(BaseModel):
|
||||||
|
message_id: int
|
||||||
|
exclude_user_id: int
|
||||||
|
|
||||||
@router.post("/subscribe")
|
@router.post("/subscribe")
|
||||||
async def subscribe_to_push_notifications(
|
async def subscribe_to_push_notifications(
|
||||||
request: PushSubscriptionRequest,
|
request: PushSubscriptionRequest,
|
||||||
@@ -44,3 +49,21 @@ async def unsubscribe_from_push_notifications(
|
|||||||
raise HTTPException(status_code=500, detail="Failed to disable push notifications")
|
raise HTTPException(status_code=500, detail="Failed to disable push notifications")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=500, detail=str(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 os
|
||||||
|
|
||||||
# Import service routers
|
# Import service routers
|
||||||
from routes.account import router as account_router
|
from backend.routes.account import router as account_router
|
||||||
from routes.profile import router as profile_router
|
from backend.routes.profile import router as profile_router
|
||||||
from routes.devices import router as device_router
|
from backend.routes.devices import router as device_router
|
||||||
from routes.messaging import router as messaging_router
|
from backend.routes.messaging import router as messaging_router
|
||||||
from routes.push import router as push_router
|
from backend.routes.push import router as push_router
|
||||||
from routes.webrtc import router as webrtc_router
|
from backend.routes.webrtc import router as webrtc_router
|
||||||
from routes.moderation import router as moderation_router
|
from backend.routes.moderation import router as moderation_router
|
||||||
|
|
||||||
# Import security modules
|
# Import security modules
|
||||||
from security.audit import log_access
|
from security.audit import log_access
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
from fastapi import FastAPI
|
# Gateway service - runs the main gateway app from backend/app.py
|
||||||
|
|
||||||
# Gateway service - handles complex operations that Caddy cannot
|
|
||||||
# This will be expanded later with routing logic to other services
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app = FastAPI(title="Gateway Service")
|
from backend.app import app
|
||||||
|
|
||||||
import os, uvicorn
|
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):
|
class User(Base):
|
||||||
__tablename__ = "users"
|
__tablename__ = "users"
|
||||||
|
__table_args__ = {"schema": "account_schema"}
|
||||||
|
|
||||||
id = Column(BigInteger, primary_key=True, index=True)
|
id = Column(BigInteger, primary_key=True, index=True)
|
||||||
username = Column(String(50), unique=True, index=True, nullable=False)
|
username = Column(String(50), unique=True, index=True, nullable=False)
|
||||||
@@ -42,9 +43,10 @@ class User(Base):
|
|||||||
|
|
||||||
class Message(Base):
|
class Message(Base):
|
||||||
__tablename__ = "messages"
|
__tablename__ = "messages"
|
||||||
|
__table_args__ = {"schema": "messaging_schema"}
|
||||||
|
|
||||||
id = Column(BigInteger, primary_key=True, index=True)
|
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 = Column(Text, nullable=False)
|
||||||
content_type = Column(String(50), default="text")
|
content_type = Column(String(50), default="text")
|
||||||
encrypted_content = Column(Text, nullable=True)
|
encrypted_content = Column(Text, nullable=True)
|
||||||
@@ -53,8 +55,8 @@ class Message(Base):
|
|||||||
edited_at = Column(DateTime, nullable=True)
|
edited_at = Column(DateTime, nullable=True)
|
||||||
edited = Column(Boolean, default=False)
|
edited = Column(Boolean, default=False)
|
||||||
deleted = Column(Boolean, default=False)
|
deleted = Column(Boolean, default=False)
|
||||||
reply_to_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("messages.id"), nullable=True)
|
thread_id = Column(BigInteger, ForeignKey("messaging_schema.messages.id"), nullable=True)
|
||||||
is_public = Column(Boolean, default=False)
|
is_public = Column(Boolean, default=False)
|
||||||
|
|
||||||
# Relationships
|
# Relationships
|
||||||
@@ -63,13 +65,15 @@ class Message(Base):
|
|||||||
reply_to = relationship("Message", remote_side=[id], foreign_keys=[reply_to_id])
|
reply_to = relationship("Message", remote_side=[id], foreign_keys=[reply_to_id])
|
||||||
thread = relationship("Message", remote_side=[id], foreign_keys=[thread_id])
|
thread = relationship("Message", remote_side=[id], foreign_keys=[thread_id])
|
||||||
reactions = relationship("MessageReaction", back_populates="message", cascade="all, delete-orphan")
|
reactions = relationship("MessageReaction", back_populates="message", cascade="all, delete-orphan")
|
||||||
|
files = relationship("MessageFile", back_populates="message", cascade="all, delete-orphan")
|
||||||
|
|
||||||
class MessageRecipient(Base):
|
class MessageRecipient(Base):
|
||||||
__tablename__ = "message_recipients"
|
__tablename__ = "message_recipients"
|
||||||
|
__table_args__ = {"schema": "messaging_schema"}
|
||||||
|
|
||||||
id = Column(BigInteger, primary_key=True, index=True)
|
id = Column(BigInteger, 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)
|
||||||
recipient_id = Column(BigInteger, ForeignKey("users.id"), nullable=False, index=True)
|
recipient_id = Column(BigInteger, ForeignKey("account_schema.users.id"), nullable=False, index=True)
|
||||||
read_at = Column(DateTime, nullable=True)
|
read_at = Column(DateTime, nullable=True)
|
||||||
delivered_at = Column(DateTime, nullable=True)
|
delivered_at = Column(DateTime, nullable=True)
|
||||||
encrypted_key = Column(Text, nullable=True)
|
encrypted_key = Column(Text, nullable=True)
|
||||||
@@ -80,10 +84,11 @@ class MessageRecipient(Base):
|
|||||||
|
|
||||||
class MessageReaction(Base):
|
class MessageReaction(Base):
|
||||||
__tablename__ = "message_reactions"
|
__tablename__ = "message_reactions"
|
||||||
|
__table_args__ = {"schema": "messaging_schema"}
|
||||||
|
|
||||||
id = Column(BigInteger, primary_key=True, index=True)
|
id = Column(BigInteger, 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)
|
||||||
user_id = Column(BigInteger, ForeignKey("users.id"), nullable=False, index=True)
|
user_id = Column(BigInteger, ForeignKey("account_schema.users.id"), nullable=False, index=True)
|
||||||
reaction = Column(String(50), nullable=False)
|
reaction = Column(String(50), nullable=False)
|
||||||
created_at = Column(DateTime, default=datetime.utcnow)
|
created_at = Column(DateTime, default=datetime.utcnow)
|
||||||
|
|
||||||
@@ -92,9 +97,10 @@ class MessageReaction(Base):
|
|||||||
|
|
||||||
class Device(Base):
|
class Device(Base):
|
||||||
__tablename__ = "devices"
|
__tablename__ = "devices"
|
||||||
|
__table_args__ = {"schema": "device_schema"}
|
||||||
|
|
||||||
id = Column(BigInteger, primary_key=True, index=True)
|
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_id = Column(String(255), unique=True, nullable=False, index=True)
|
||||||
device_name = Column(String(255), nullable=True)
|
device_name = Column(String(255), nullable=True)
|
||||||
device_type = Column(String(50), nullable=True)
|
device_type = Column(String(50), nullable=True)
|
||||||
@@ -110,10 +116,11 @@ class Device(Base):
|
|||||||
|
|
||||||
class PushSubscription(Base):
|
class PushSubscription(Base):
|
||||||
__tablename__ = "push_subscriptions"
|
__tablename__ = "push_subscriptions"
|
||||||
|
__table_args__ = {"schema": "push_schema"}
|
||||||
|
|
||||||
id = Column(BigInteger, primary_key=True, index=True)
|
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(BigInteger, ForeignKey("devices.id"), nullable=True, index=True)
|
device_id = Column(BigInteger, ForeignKey("device_schema.devices.id"), nullable=True, index=True)
|
||||||
endpoint = Column(String(500), nullable=False)
|
endpoint = Column(String(500), nullable=False)
|
||||||
p256dh = Column(String(255), nullable=False)
|
p256dh = Column(String(255), nullable=False)
|
||||||
auth = Column(String(255), nullable=False)
|
auth = Column(String(255), nullable=False)
|
||||||
@@ -126,10 +133,11 @@ class PushSubscription(Base):
|
|||||||
|
|
||||||
class WebRTCSession(Base):
|
class WebRTCSession(Base):
|
||||||
__tablename__ = "webrtc_sessions"
|
__tablename__ = "webrtc_sessions"
|
||||||
|
__table_args__ = {"schema": "webrtc_schema"}
|
||||||
|
|
||||||
id = Column(BigInteger, primary_key=True, index=True)
|
id = Column(BigInteger, primary_key=True, index=True)
|
||||||
session_id = Column(String(255), unique=True, nullable=False, 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)
|
participant_ids = Column(JSON, nullable=False)
|
||||||
offer = Column(JSON, nullable=True)
|
offer = Column(JSON, nullable=True)
|
||||||
answer = Column(JSON, nullable=True)
|
answer = Column(JSON, nullable=True)
|
||||||
@@ -140,11 +148,12 @@ class WebRTCSession(Base):
|
|||||||
|
|
||||||
class ModerationAction(Base):
|
class ModerationAction(Base):
|
||||||
__tablename__ = "moderation_actions"
|
__tablename__ = "moderation_actions"
|
||||||
|
__table_args__ = {"schema": "moderation_schema"}
|
||||||
|
|
||||||
id = Column(BigInteger, primary_key=True, index=True)
|
id = Column(BigInteger, primary_key=True, index=True)
|
||||||
moderator_id = Column(BigInteger, ForeignKey("users.id"), nullable=False)
|
moderator_id = Column(BigInteger, ForeignKey("account_schema.users.id"), nullable=False)
|
||||||
target_user_id = Column(BigInteger, ForeignKey("users.id"), nullable=True)
|
target_user_id = Column(BigInteger, ForeignKey("account_schema.users.id"), nullable=True)
|
||||||
target_message_id = Column(BigInteger, ForeignKey("messages.id"), nullable=True)
|
target_message_id = Column(BigInteger, ForeignKey("messaging_schema.messages.id"), nullable=True)
|
||||||
action_type = Column(String(50), nullable=False)
|
action_type = Column(String(50), nullable=False)
|
||||||
reason = Column(Text, nullable=True)
|
reason = Column(Text, nullable=True)
|
||||||
created_at = Column(DateTime, default=datetime.utcnow)
|
created_at = Column(DateTime, default=datetime.utcnow)
|
||||||
@@ -153,9 +162,10 @@ class ModerationAction(Base):
|
|||||||
|
|
||||||
class MessageFile(Base):
|
class MessageFile(Base):
|
||||||
__tablename__ = "message_file"
|
__tablename__ = "message_file"
|
||||||
|
__table_args__ = {"schema": "messaging_schema"}
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True, index=True)
|
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)
|
path = Column(Text, nullable=False)
|
||||||
name = Column(Text, nullable=False)
|
name = Column(Text, nullable=False)
|
||||||
|
|
||||||
@@ -166,7 +176,7 @@ class CryptoPublicKey(Base):
|
|||||||
__tablename__ = "crypto_public_key"
|
__tablename__ = "crypto_public_key"
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True, index=True)
|
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)
|
public_key_b64 = Column(Text, nullable=False)
|
||||||
|
|
||||||
|
|
||||||
@@ -174,16 +184,17 @@ class CryptoBackup(Base):
|
|||||||
__tablename__ = "crypto_backup"
|
__tablename__ = "crypto_backup"
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True, index=True)
|
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)
|
blob_json = Column(Text, nullable=False)
|
||||||
|
|
||||||
|
|
||||||
class DMEnvelope(Base):
|
class DMEnvelope(Base):
|
||||||
__tablename__ = "dm_envelope"
|
__tablename__ = "dm_envelope"
|
||||||
|
__table_args__ = {"schema": "messaging_schema"}
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True, index=True)
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
sender_id = Column(BigInteger, ForeignKey("users.id"), nullable=False)
|
sender_id = Column(BigInteger, ForeignKey("account_schema.users.id"), nullable=False)
|
||||||
recipient_id = Column(BigInteger, ForeignKey("users.id"), nullable=False)
|
recipient_id = Column(BigInteger, ForeignKey("account_schema.users.id"), nullable=False)
|
||||||
iv_b64 = Column(Text, nullable=False)
|
iv_b64 = Column(Text, nullable=False)
|
||||||
ciphertext_b64 = Column(Text, nullable=False)
|
ciphertext_b64 = Column(Text, nullable=False)
|
||||||
salt_b64 = Column(Text, nullable=False)
|
salt_b64 = Column(Text, nullable=False)
|
||||||
@@ -197,11 +208,12 @@ class DMEnvelope(Base):
|
|||||||
|
|
||||||
class DMFile(Base):
|
class DMFile(Base):
|
||||||
__tablename__ = "dm_file"
|
__tablename__ = "dm_file"
|
||||||
|
__table_args__ = {"schema": "messaging_schema"}
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True, index=True)
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
message_id = Column(Integer, ForeignKey("dm_envelope.id"), nullable=False, index=True)
|
message_id = Column(Integer, ForeignKey("messaging_schema.dm_envelope.id"), nullable=False, index=True)
|
||||||
sender_id = Column(BigInteger, ForeignKey("users.id"), nullable=False)
|
sender_id = Column(BigInteger, ForeignKey("account_schema.users.id"), nullable=False)
|
||||||
recipient_id = Column(BigInteger, ForeignKey("users.id"), nullable=False)
|
recipient_id = Column(BigInteger, ForeignKey("account_schema.users.id"), nullable=False)
|
||||||
name = Column(Text, nullable=False)
|
name = Column(Text, nullable=False)
|
||||||
path = Column(Text, nullable=False)
|
path = Column(Text, nullable=False)
|
||||||
|
|
||||||
@@ -210,9 +222,10 @@ class DMFile(Base):
|
|||||||
|
|
||||||
class FcmToken(Base):
|
class FcmToken(Base):
|
||||||
__tablename__ = "fcm_token"
|
__tablename__ = "fcm_token"
|
||||||
|
__table_args__ = {"schema": "push_schema"}
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True, index=True)
|
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)
|
token = Column(Text, nullable=False, unique=True)
|
||||||
created_at = Column(DateTime, default=datetime.now)
|
created_at = Column(DateTime, default=datetime.now)
|
||||||
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
|
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
|
||||||
@@ -220,10 +233,11 @@ class FcmToken(Base):
|
|||||||
|
|
||||||
class Reaction(Base):
|
class Reaction(Base):
|
||||||
__tablename__ = "reaction"
|
__tablename__ = "reaction"
|
||||||
|
__table_args__ = {"schema": "messaging_schema"}
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True, index=True)
|
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)
|
||||||
user_id = Column(BigInteger, ForeignKey("users.id"), nullable=False)
|
user_id = Column(BigInteger, ForeignKey("account_schema.users.id"), nullable=False)
|
||||||
emoji = Column(String(10), nullable=False) # Store emoji as string
|
emoji = Column(String(10), nullable=False) # Store emoji as string
|
||||||
timestamp = Column(DateTime, default=datetime.now)
|
timestamp = Column(DateTime, default=datetime.now)
|
||||||
|
|
||||||
@@ -236,10 +250,11 @@ class Reaction(Base):
|
|||||||
|
|
||||||
class DMReaction(Base):
|
class DMReaction(Base):
|
||||||
__tablename__ = "dm_reaction"
|
__tablename__ = "dm_reaction"
|
||||||
|
__table_args__ = {"schema": "messaging_schema"}
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True, index=True)
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
dm_envelope_id = Column(Integer, ForeignKey("dm_envelope.id"), nullable=False, index=True)
|
dm_envelope_id = Column(Integer, ForeignKey("messaging_schema.dm_envelope.id"), nullable=False, index=True)
|
||||||
user_id = Column(BigInteger, ForeignKey("users.id"), nullable=False)
|
user_id = Column(BigInteger, ForeignKey("account_schema.users.id"), nullable=False)
|
||||||
emoji = Column(String(10), nullable=False) # Store emoji as string
|
emoji = Column(String(10), nullable=False) # Store emoji as string
|
||||||
timestamp = Column(DateTime, default=datetime.now)
|
timestamp = Column(DateTime, default=datetime.now)
|
||||||
|
|
||||||
@@ -254,9 +269,10 @@ class DMReaction(Base):
|
|||||||
# Tracks authenticated device sessions per user
|
# Tracks authenticated device sessions per user
|
||||||
class DeviceSession(Base):
|
class DeviceSession(Base):
|
||||||
__tablename__ = "device_session"
|
__tablename__ = "device_session"
|
||||||
|
__table_args__ = {"schema": "device_schema"}
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True, index=True)
|
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 for reference/debugging
|
||||||
raw_user_agent = Column(Text, nullable=True)
|
raw_user_agent = Column(Text, nullable=True)
|
||||||
@@ -392,9 +408,10 @@ class DMReactionResponse(BaseModel):
|
|||||||
class UpdateLog(Base):
|
class UpdateLog(Base):
|
||||||
"""Stores update sequence numbers and updates for gap detection"""
|
"""Stores update sequence numbers and updates for gap detection"""
|
||||||
__tablename__ = "update_log"
|
__tablename__ = "update_log"
|
||||||
|
__table_args__ = {"schema": "public"}
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True, index=True)
|
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)
|
sequence = Column(Integer, nullable=False, index=True)
|
||||||
updates = Column(Text, nullable=False) # JSON array of updates
|
updates = Column(Text, nullable=False) # JSON array of updates
|
||||||
timestamp = Column(DateTime, default=datetime.now, index=True)
|
timestamp = Column(DateTime, default=datetime.now, index=True)
|
||||||
|
|||||||
@@ -2,13 +2,15 @@
|
|||||||
-- This script creates dedicated users with limited privileges for each service
|
-- This script creates dedicated users with limited privileges for each service
|
||||||
|
|
||||||
-- Create service-specific database roles with limited privileges
|
-- Create service-specific database roles with limited privileges
|
||||||
CREATE ROLE account_service_user LOGIN PASSWORD 'account_service_password';
|
-- All services use the same password from DB_PASSWORD environment variable
|
||||||
CREATE ROLE profile_service_user LOGIN PASSWORD 'profile_service_password';
|
CREATE ROLE account_service_user LOGIN PASSWORD '${DB_PASSWORD}';
|
||||||
CREATE ROLE device_service_user LOGIN PASSWORD 'device_service_password';
|
CREATE ROLE profile_service_user LOGIN PASSWORD '${DB_PASSWORD}';
|
||||||
CREATE ROLE messaging_service_user LOGIN PASSWORD 'messaging_service_password';
|
CREATE ROLE device_service_user LOGIN PASSWORD '${DB_PASSWORD}';
|
||||||
CREATE ROLE push_service_user LOGIN PASSWORD 'push_service_user_password';
|
CREATE ROLE messaging_service_user LOGIN PASSWORD '${DB_PASSWORD}';
|
||||||
CREATE ROLE webrtc_service_user LOGIN PASSWORD 'webrtc_service_password';
|
CREATE ROLE push_service_user LOGIN PASSWORD '${DB_PASSWORD}';
|
||||||
CREATE ROLE moderation_service_user LOGIN PASSWORD 'moderation_service_password';
|
CREATE ROLE webrtc_service_user LOGIN PASSWORD '${DB_PASSWORD}';
|
||||||
|
CREATE ROLE moderation_service_user LOGIN PASSWORD '${DB_PASSWORD}';
|
||||||
|
CREATE ROLE gateway_user LOGIN PASSWORD '${DB_PASSWORD}';
|
||||||
|
|
||||||
-- Create dedicated schemas for each service
|
-- Create dedicated schemas for each service
|
||||||
CREATE SCHEMA IF NOT EXISTS account_schema AUTHORIZATION account_service_user;
|
CREATE SCHEMA IF NOT EXISTS account_schema AUTHORIZATION account_service_user;
|
||||||
|
|||||||
@@ -56,8 +56,17 @@ services:
|
|||||||
context: ..
|
context: ..
|
||||||
dockerfile: docker/Dockerfile.multi
|
dockerfile: docker/Dockerfile.multi
|
||||||
target: gateway
|
target: gateway
|
||||||
|
ports: ["8300:8300"]
|
||||||
environment:
|
environment:
|
||||||
DATABASE_URL: postgresql://gateway_user:${DB_PASSWORD:-changeme}@database:5432/fromchat
|
DATABASE_URL: postgresql://gateway_user:${DB_PASSWORD:-changeme}@database:5432/fromchat
|
||||||
|
PORT: 8300
|
||||||
|
ACCOUNT_SERVICE_URL: http://account_service:8302
|
||||||
|
PROFILE_SERVICE_URL: http://profile_service:8303
|
||||||
|
DEVICE_SERVICE_URL: http://device_service:8304
|
||||||
|
MESSAGING_SERVICE_URL: http://messaging_service:8305
|
||||||
|
PUSH_SERVICE_URL: http://push_service:8306
|
||||||
|
WEBRTC_SERVICE_URL: http://webrtc_service:8307
|
||||||
|
MODERATION_SERVICE_URL: http://moderation_service:8308
|
||||||
depends_on:
|
depends_on:
|
||||||
migration_runner:
|
migration_runner:
|
||||||
condition: service_completed_successfully
|
condition: service_completed_successfully
|
||||||
@@ -95,6 +104,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
DATABASE_URL: postgresql://account_service_user:${DB_PASSWORD:-changeme}@database:5432/fromchat
|
DATABASE_URL: postgresql://account_service_user:${DB_PASSWORD:-changeme}@database:5432/fromchat
|
||||||
JWT_SECRET: ${JWT_SECRET:-changeme}
|
JWT_SECRET: ${JWT_SECRET:-changeme}
|
||||||
|
PORT: 8302
|
||||||
depends_on:
|
depends_on:
|
||||||
migration_runner:
|
migration_runner:
|
||||||
condition: service_completed_successfully
|
condition: service_completed_successfully
|
||||||
@@ -125,6 +135,8 @@ services:
|
|||||||
VAPID_PRIVATE_KEY: ${VAPID_PRIVATE_KEY:-}
|
VAPID_PRIVATE_KEY: ${VAPID_PRIVATE_KEY:-}
|
||||||
VAPID_PUBLIC_KEY: ${VAPID_PUBLIC_KEY:-}
|
VAPID_PUBLIC_KEY: ${VAPID_PUBLIC_KEY:-}
|
||||||
VAPID_SUBJECT: ${VAPID_SUBJECT:-mailto:admin@example.com}
|
VAPID_SUBJECT: ${VAPID_SUBJECT:-mailto:admin@example.com}
|
||||||
|
MESSAGING_SERVICE_URL: http://messaging_service:8305
|
||||||
|
PORT: 8303
|
||||||
depends_on:
|
depends_on:
|
||||||
migration_runner:
|
migration_runner:
|
||||||
condition: service_completed_successfully
|
condition: service_completed_successfully
|
||||||
@@ -151,6 +163,7 @@ services:
|
|||||||
target: device_service
|
target: device_service
|
||||||
environment:
|
environment:
|
||||||
DATABASE_URL: postgresql://device_service_user:${DB_PASSWORD:-changeme}@database:5432/fromchat
|
DATABASE_URL: postgresql://device_service_user:${DB_PASSWORD:-changeme}@database:5432/fromchat
|
||||||
|
PORT: 8304
|
||||||
depends_on:
|
depends_on:
|
||||||
migration_runner:
|
migration_runner:
|
||||||
condition: service_completed_successfully
|
condition: service_completed_successfully
|
||||||
@@ -175,12 +188,16 @@ services:
|
|||||||
context: ..
|
context: ..
|
||||||
dockerfile: docker/Dockerfile.multi
|
dockerfile: docker/Dockerfile.multi
|
||||||
target: messaging_service
|
target: messaging_service
|
||||||
|
ports:
|
||||||
|
- "8305:8305"
|
||||||
environment:
|
environment:
|
||||||
DATABASE_URL: postgresql://messaging_service_user:${DB_PASSWORD:-changeme}@database:5432/fromchat
|
DATABASE_URL: postgresql://messaging_service_user:${DB_PASSWORD:-changeme}@database:5432/fromchat
|
||||||
FIREBASE_CERT: ${FIREBASE_CERT:-}
|
FIREBASE_CERT: ${FIREBASE_CERT:-}
|
||||||
VAPID_PRIVATE_KEY: ${VAPID_PRIVATE_KEY:-}
|
VAPID_PRIVATE_KEY: ${VAPID_PRIVATE_KEY:-}
|
||||||
VAPID_PUBLIC_KEY: ${VAPID_PUBLIC_KEY:-}
|
VAPID_PUBLIC_KEY: ${VAPID_PUBLIC_KEY:-}
|
||||||
VAPID_SUBJECT: ${VAPID_SUBJECT:-mailto:admin@example.com}
|
VAPID_SUBJECT: ${VAPID_SUBJECT:-mailto:admin@example.com}
|
||||||
|
PUSH_SERVICE_URL: http://push_service:8306
|
||||||
|
PORT: 8305
|
||||||
depends_on:
|
depends_on:
|
||||||
migration_runner:
|
migration_runner:
|
||||||
condition: service_completed_successfully
|
condition: service_completed_successfully
|
||||||
@@ -214,6 +231,7 @@ services:
|
|||||||
VAPID_PRIVATE_KEY: ${VAPID_PRIVATE_KEY:-}
|
VAPID_PRIVATE_KEY: ${VAPID_PRIVATE_KEY:-}
|
||||||
VAPID_PUBLIC_KEY: ${VAPID_PUBLIC_KEY:-}
|
VAPID_PUBLIC_KEY: ${VAPID_PUBLIC_KEY:-}
|
||||||
VAPID_SUBJECT: ${VAPID_SUBJECT:-mailto:admin@example.com}
|
VAPID_SUBJECT: ${VAPID_SUBJECT:-mailto:admin@example.com}
|
||||||
|
PORT: 8306
|
||||||
depends_on:
|
depends_on:
|
||||||
migration_runner:
|
migration_runner:
|
||||||
condition: service_completed_successfully
|
condition: service_completed_successfully
|
||||||
@@ -243,6 +261,7 @@ services:
|
|||||||
target: webrtc_service
|
target: webrtc_service
|
||||||
environment:
|
environment:
|
||||||
DATABASE_URL: postgresql://webrtc_service_user:${DB_PASSWORD:-changeme}@database:5432/fromchat
|
DATABASE_URL: postgresql://webrtc_service_user:${DB_PASSWORD:-changeme}@database:5432/fromchat
|
||||||
|
PORT: 8307
|
||||||
depends_on:
|
depends_on:
|
||||||
migration_runner:
|
migration_runner:
|
||||||
condition: service_completed_successfully
|
condition: service_completed_successfully
|
||||||
@@ -269,6 +288,7 @@ services:
|
|||||||
target: moderation_service
|
target: moderation_service
|
||||||
environment:
|
environment:
|
||||||
DATABASE_URL: postgresql://moderation_service_user:${DB_PASSWORD:-changeme}@database:5432/fromchat
|
DATABASE_URL: postgresql://moderation_service_user:${DB_PASSWORD:-changeme}@database:5432/fromchat
|
||||||
|
PORT: 8308
|
||||||
depends_on:
|
depends_on:
|
||||||
migration_runner:
|
migration_runner:
|
||||||
condition: service_completed_successfully
|
condition: service_completed_successfully
|
||||||
@@ -315,6 +335,20 @@ services:
|
|||||||
- gateway
|
- gateway
|
||||||
profiles: ["prod"]
|
profiles: ["prod"]
|
||||||
|
|
||||||
|
# Frontend service - serves the React app
|
||||||
|
frontend:
|
||||||
|
build:
|
||||||
|
context: ..
|
||||||
|
dockerfile: deployment/frontend/Dockerfile
|
||||||
|
ports:
|
||||||
|
- "8301:8301"
|
||||||
|
environment:
|
||||||
|
- PORT=8301
|
||||||
|
- BACKEND_HOST=http://gateway:8300
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- fromchat_external
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
database:
|
database:
|
||||||
name: fromchat-database
|
name: fromchat-database
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
|
import type { Request, Response } from 'express';
|
||||||
import { createProxyMiddleware } from 'http-proxy-middleware';
|
import { createProxyMiddleware } from 'http-proxy-middleware';
|
||||||
import { resolve } from 'path';
|
import { resolve } from 'path';
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const port = process.env.PORT || 3000;
|
const port = Number(process.env.PORT) || 8301;
|
||||||
const backendHost = process.env.BACKEND_HOST || "http://localhost:8300";
|
const backendHost = process.env.BACKEND_HOST || "http://localhost:8300";
|
||||||
const filePath = process.env.STATIC_FILE_PATH || ".";
|
const filePath = process.env.STATIC_FILE_PATH || ".";
|
||||||
|
|
||||||
@@ -15,14 +16,17 @@ app.use('/api', createProxyMiddleware({
|
|||||||
ws: true
|
ws: true
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// WebSockets are handled by the general API proxy above
|
||||||
|
|
||||||
// Serve static files
|
// Serve static files
|
||||||
app.use(express.static(resolve(filePath)));
|
app.use(express.static(resolve(filePath)));
|
||||||
|
|
||||||
// SPA routing - catch all handler for client-side routing
|
// SPA routing - catch all handler for client-side routing
|
||||||
app.use((_req, res) => {
|
app.use((_req: Request, res: Response) => {
|
||||||
res.sendFile(resolve(filePath, 'index.html'));
|
res.sendFile(resolve(filePath, 'index.html'));
|
||||||
});
|
});
|
||||||
|
|
||||||
app.listen(port, () => {
|
app.listen(port, '0.0.0.0', () => {
|
||||||
console.log(`Server launched on http://localhost:${port}`);
|
console.log(`Backend host: ${backendHost}`);
|
||||||
|
console.log(`Server launched on http://0.0.0.0:${port}`);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -52,11 +52,11 @@ ENV SERVICE_NAME=account
|
|||||||
FROM base AS profile_service
|
FROM base AS profile_service
|
||||||
COPY backend/routes/profile.py /app/backend/routes/profile.py
|
COPY backend/routes/profile.py /app/backend/routes/profile.py
|
||||||
COPY backend/routes/messaging.py /app/backend/routes/messaging.py
|
COPY backend/routes/messaging.py /app/backend/routes/messaging.py
|
||||||
COPY backend/push_service.py /app/backend/push_service.py
|
COPY backend/services/push/files/push_service.py /app/backend/services/push/files/push_service.py
|
||||||
COPY backend/security /app/backend/security/
|
COPY backend/security /app/backend/security/
|
||||||
COPY backend/logging_config.py /app/backend/logging_config.py
|
COPY backend/logging_config.py /app/backend/logging_config.py
|
||||||
COPY backend/websocket /app/backend/websocket/
|
COPY backend/services/messaging/files/websocket /app/backend/services/messaging/files/websocket/
|
||||||
COPY backend/similarity.py /app/backend/similarity.py
|
COPY backend/shared/similarity.py /app/backend/shared/similarity.py
|
||||||
COPY backend/services/profile/main.py /app/backend/services/profile/main.py
|
COPY backend/services/profile/main.py /app/backend/services/profile/main.py
|
||||||
ENV SERVICE_NAME=profile
|
ENV SERVICE_NAME=profile
|
||||||
|
|
||||||
@@ -69,17 +69,17 @@ ENV SERVICE_NAME=device
|
|||||||
# Messaging service - minimal files only
|
# Messaging service - minimal files only
|
||||||
FROM base AS messaging_service
|
FROM base AS messaging_service
|
||||||
COPY backend/routes/messaging.py /app/backend/routes/messaging.py
|
COPY backend/routes/messaging.py /app/backend/routes/messaging.py
|
||||||
COPY backend/push_service.py /app/backend/push_service.py
|
COPY backend/services/push/files/push_service.py /app/backend/services/push/files/push_service.py
|
||||||
COPY backend/security /app/backend/security/
|
COPY backend/security /app/backend/security/
|
||||||
COPY backend/logging_config.py /app/backend/logging_config.py
|
COPY backend/logging_config.py /app/backend/logging_config.py
|
||||||
COPY backend/websocket /app/backend/websocket/
|
COPY backend/services/messaging/files/websocket /app/backend/services/messaging/files/websocket/
|
||||||
COPY backend/services/messaging/main.py /app/backend/services/messaging/main.py
|
COPY backend/services/messaging/main.py /app/backend/services/messaging/main.py
|
||||||
ENV SERVICE_NAME=messaging
|
ENV SERVICE_NAME=messaging
|
||||||
|
|
||||||
# Push service - minimal files only
|
# Push service - minimal files only
|
||||||
FROM base AS push_service
|
FROM base AS push_service
|
||||||
COPY backend/routes/push.py /app/backend/routes/push.py
|
COPY backend/routes/push.py /app/backend/routes/push.py
|
||||||
COPY backend/push_service.py /app/backend/push_service.py
|
COPY backend/services/push/files/push_service.py /app/backend/services/push/files/push_service.py
|
||||||
COPY backend/services/push/main.py /app/backend/services/push/main.py
|
COPY backend/services/push/main.py /app/backend/services/push/main.py
|
||||||
ENV SERVICE_NAME=push
|
ENV SERVICE_NAME=push
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ ENV SERVICE_NAME=webrtc
|
|||||||
FROM base AS moderation_service
|
FROM base AS moderation_service
|
||||||
COPY backend/routes/moderation.py /app/backend/routes/moderation.py
|
COPY backend/routes/moderation.py /app/backend/routes/moderation.py
|
||||||
COPY backend/security /app/backend/security/
|
COPY backend/security /app/backend/security/
|
||||||
COPY backend/similarity.py /app/backend/similarity.py
|
COPY backend/shared/similarity.py /app/backend/shared/similarity.py
|
||||||
COPY backend/logging_config.py /app/backend/logging_config.py
|
COPY backend/logging_config.py /app/backend/logging_config.py
|
||||||
COPY backend/services/moderation/main.py /app/backend/services/moderation/main.py
|
COPY backend/services/moderation/main.py /app/backend/services/moderation/main.py
|
||||||
ENV SERVICE_NAME=moderation
|
ENV SERVICE_NAME=moderation
|
||||||
@@ -103,6 +103,7 @@ FROM base AS gateway
|
|||||||
COPY backend/app.py /app/backend/app.py
|
COPY backend/app.py /app/backend/app.py
|
||||||
COPY backend/main.py /app/backend/main.py
|
COPY backend/main.py /app/backend/main.py
|
||||||
COPY backend/dependencies.py /app/backend/dependencies.py
|
COPY backend/dependencies.py /app/backend/dependencies.py
|
||||||
|
COPY backend/logging_config.py /app/backend/logging_config.py
|
||||||
COPY backend/security /app/backend/security/
|
COPY backend/security /app/backend/security/
|
||||||
COPY backend/services/gateway/main.py /app/backend/services/gateway/main.py
|
COPY backend/services/gateway/main.py /app/backend/services/gateway/main.py
|
||||||
ENV SERVICE_NAME=gateway
|
ENV SERVICE_NAME=gateway
|
||||||
|
|||||||
Reference in New Issue
Block a user