Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 13 additions & 19 deletions .idea/workspace.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file modified ApiServer/app/__pycache__/chat_client.cpython-314.pyc
Binary file not shown.
Binary file modified ApiServer/app/__pycache__/dependencies.cpython-314.pyc
Binary file not shown.
11 changes: 11 additions & 0 deletions ApiServer/app/chat_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,14 @@ async def remove_conversation_member(conversation_id: str, user_id: str):
)
if response.status_code != 204:
raise HTTPException(status_code=502, detail="Failed to remove member on chat server")


async def get_conversation_messages(conversation_id: str) -> list:
async with httpx.AsyncClient() as client:
response = await client.get(
f"{settings.chat_server_url}/conversations/{conversation_id}/messages",
headers=_INTERNAL_HEADERS,
)
if not response.is_success:
raise HTTPException(status_code=502, detail="Failed to fetch messages")
return response.json()
4 changes: 1 addition & 3 deletions ApiServer/app/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from app.config import settings
from app.database import AsyncSessionLocal
from app.models.user import User
from app.schemas.token import TokenData

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/login")

Expand All @@ -31,11 +30,10 @@ async def get_current_user(
user_id: str = payload.get("sub")
if user_id is None:
raise credentials_exception
token_data = TokenData(user_id=user_id)
except JWTError:
raise credentials_exception

result = await db.execute(select(User).where(User.id == token_data.user_id))
result = await db.execute(select(User).where(User.id == user_id))
user = result.scalar_one_or_none()
if user is None:
raise credentials_exception
Expand Down
9 changes: 0 additions & 9 deletions ApiServer/app/repos/users.py

This file was deleted.

Binary file modified ApiServer/app/routers/__pycache__/conversations.cpython-314.pyc
Binary file not shown.
15 changes: 3 additions & 12 deletions ApiServer/app/routers/conversations.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import httpx
from fastapi import APIRouter, Depends

from fastapi import APIRouter, Depends, HTTPException

from app.config import settings
from app.chat_client import get_conversation_messages
from app.dependencies import get_current_user
from app.models.user import User

Expand All @@ -14,11 +12,4 @@ async def get_messages(
conversation_id: str,
_: User = Depends(get_current_user),
):
async with httpx.AsyncClient() as client:
resp = await client.get(
f"{settings.chat_server_url}/conversations/{conversation_id}/messages",
headers={"x-internal-key": settings.internal_api_key},
)
if not resp.is_success:
raise HTTPException(status_code=502, detail="Failed to fetch messages")
return resp.json()
return await get_conversation_messages(conversation_id)
Binary file modified ApiServer/app/schemas/__pycache__/token.cpython-314.pyc
Binary file not shown.
4 changes: 0 additions & 4 deletions ApiServer/app/schemas/token.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,3 @@
class Token(BaseModel):
access_token: str
token_type: str


class TokenData(BaseModel):
user_id: str | None = None
Binary file not shown.
17 changes: 4 additions & 13 deletions ApiServer/tests/test_conversations.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from unittest.mock import AsyncMock, MagicMock, patch

import pytest
from unittest.mock import AsyncMock, patch

from tests.conftest import register_and_login

Expand All @@ -11,17 +10,9 @@ def mock_chat_server_messages():
{"fromUserId": "alice-id", "conversationId": "conv-1", "content": "hello", "sentAt": "2024-01-01T00:00:00Z"},
{"fromUserId": "bob-id", "conversationId": "conv-1", "content": "hi", "sentAt": "2024-01-01T00:00:01Z"},
]
mock_response = MagicMock()
mock_response.is_success = True
mock_response.json.return_value = messages

mock_client = AsyncMock()
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
mock_client.__aexit__ = AsyncMock(return_value=False)
mock_client.get = AsyncMock(return_value=mock_response)

with patch("app.routers.conversations.httpx.AsyncClient", return_value=mock_client):
yield mock_response.json.return_value
with patch("app.routers.conversations.get_conversation_messages", new_callable=AsyncMock) as mock:
mock.return_value = messages
yield messages


async def test_get_messages(client, mock_chat_server_messages):
Expand Down
13 changes: 3 additions & 10 deletions ChatClient/src/components/chat/MessageBubble.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useState } from 'react'
import { useEffect } from 'react'
import type { StoredMessage } from '../../types'
import { useUserCacheStore } from '../../store/userCacheStore'
import { getUser } from '../../lib/api'
Expand All @@ -21,23 +21,16 @@ function formatTime(sentAt: string): string {

export function MessageBubble({ message, currentUserId }: MessageBubbleProps) {
const isMine = message.fromUserId === currentUserId
const getCache = useUserCacheStore((s) => s.getUser)
const addUser = useUserCacheStore((s) => s.addUser)
const cached = useUserCacheStore((s) => s.cache[message.fromUserId])
const [, forceRender] = useState(0)

useEffect(() => {
if (!cached) {
getUser(message.fromUserId)
.then((user) => {
addUser(user)
forceRender((n) => n + 1)
})
.catch(() => {})
getUser(message.fromUserId).then(addUser).catch(() => {})
}
}, [message.fromUserId])

const username = getCache(message.fromUserId)?.username ?? message.fromUserId
const username = cached?.username ?? message.fromUserId

return (
<div className={`flex flex-col mb-4 ${isMine ? 'items-end' : 'items-start'}`}>
Expand Down
6 changes: 0 additions & 6 deletions ChatClient/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,6 @@ export interface GroupResponse {
created_at: string
}

export interface GroupMemberResponse {
group_id: string
user_id: string
joined_at: string
}

export interface StoredMessage {
fromUserId: string
conversationId: string
Expand Down
10 changes: 5 additions & 5 deletions ChatInfra/modules/dynamodb/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ resource "aws_dynamodb_table" "server_registry" {
enabled = true
}

tags = { Name = "chatsystem-server-registry" }
tags = { Name = "${var.project_name}-server-registry" }
}

resource "aws_dynamodb_table" "user_connections" {
Expand All @@ -29,7 +29,7 @@ resource "aws_dynamodb_table" "user_connections" {
enabled = true
}

tags = { Name = "chatsystem-user-connections" }
tags = { Name = "${var.project_name}-user-connections" }
}

resource "aws_dynamodb_table" "conversation_members" {
Expand Down Expand Up @@ -58,7 +58,7 @@ resource "aws_dynamodb_table" "conversation_members" {
enabled = true
}

tags = { Name = "chatsystem-conversation-members" }
tags = { Name = "${var.project_name}-conversation-members" }
}

resource "aws_dynamodb_table" "messages" {
Expand All @@ -81,7 +81,7 @@ resource "aws_dynamodb_table" "messages" {
enabled = true
}

tags = { Name = "chatsystem-messages" }
tags = { Name = "${var.project_name}-messages" }
}

resource "aws_dynamodb_table" "pending_messages" {
Expand All @@ -104,5 +104,5 @@ resource "aws_dynamodb_table" "pending_messages" {
enabled = true
}

tags = { Name = "chatsystem-pending-messages" }
tags = { Name = "${var.project_name}-pending-messages" }
}
3 changes: 0 additions & 3 deletions ChatInfra/terraform.tfvars

This file was deleted.

2 changes: 1 addition & 1 deletion ChatServer/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ services:
- "8080:8080"
env_file: .env
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost:8000/health" ]
test: [ "CMD", "curl", "-f", "http://localhost:8080/health" ]
interval: 30s
retries: 3
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import org.chatserver.models.ChatMessage
import org.slf4j.LoggerFactory
import software.amazon.awssdk.services.dynamodb.DynamoDbClient
import software.amazon.awssdk.services.dynamodb.model.AttributeValue
import software.amazon.awssdk.services.dynamodb.model.DeleteItemRequest
import software.amazon.awssdk.services.dynamodb.model.BatchWriteItemRequest
import software.amazon.awssdk.services.dynamodb.model.DeleteRequest
import software.amazon.awssdk.services.dynamodb.model.PutItemRequest
import software.amazon.awssdk.services.dynamodb.model.QueryRequest
import software.amazon.awssdk.services.dynamodb.model.WriteRequest
import java.time.Instant
import java.util.UUID

Expand Down Expand Up @@ -61,11 +63,23 @@ class PendingMessageRepository(dynamoClient: DynamoDbClient) : AbstractDynamoCom
)
}

response.items().forEach { item ->
dynamoClient.deleteItem(
DeleteItemRequest.builder()
.tableName(tableName)
.key(mapOf(partitionKey to item[partitionKey]!!, sortKey to item[sortKey]!!))
response.items().chunked(25).forEach { chunk ->
dynamoClient.batchWriteItem(
BatchWriteItemRequest.builder()
.requestItems(
mapOf(
tableName to
chunk.map { item ->
WriteRequest.builder()
.deleteRequest(
DeleteRequest.builder()
.key(mapOf(partitionKey to item[partitionKey]!!, sortKey to item[sortKey]!!))
.build(),
)
.build()
},
),
)
.build(),
)
}
Expand Down
1 change: 0 additions & 1 deletion ChatServer/src/main/kotlin/plugins/ConversationRoutes.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ fun Application.configureConversationRoutes(
internalApiKey: String = System.getenv("INTERNAL_API_KEY") ?: error("INTERNAL_API_KEY env var not set"),
) {
val conversationRegistry by inject<ConversationRegistry>()
conversationRegistry.init()

routing {
post("/conversations") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import io.mockk.verify
import org.chatserver.models.ChatMessage
import software.amazon.awssdk.services.dynamodb.DynamoDbClient
import software.amazon.awssdk.services.dynamodb.model.AttributeValue
import software.amazon.awssdk.services.dynamodb.model.BatchWriteItemRequest
import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest
import software.amazon.awssdk.services.dynamodb.model.DeleteItemRequest
import software.amazon.awssdk.services.dynamodb.model.DescribeTableRequest
import software.amazon.awssdk.services.dynamodb.model.PutItemRequest
import software.amazon.awssdk.services.dynamodb.model.QueryRequest
Expand Down Expand Up @@ -98,7 +98,7 @@ class PendingMessageRepositoryTest {

repository.fetchAndClear("bob")

verify(exactly = 2) { dynamoClient.deleteItem(any<DeleteItemRequest>()) }
verify(exactly = 1) { dynamoClient.batchWriteItem(any<BatchWriteItemRequest>()) }
}

@Test
Expand All @@ -109,6 +109,6 @@ class PendingMessageRepositoryTest {
val messages = repository.fetchAndClear("alice")

assertTrue(messages.isEmpty())
verify(exactly = 0) { dynamoClient.deleteItem(any<DeleteItemRequest>()) }
verify(exactly = 0) { dynamoClient.batchWriteItem(any<BatchWriteItemRequest>()) }
}
}
2 changes: 1 addition & 1 deletion PresenceServer/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM python:3.14-slim AS build
FROM python:3.14-slim
WORKDIR /app

COPY requirements.txt .
Expand Down
Binary file modified PresenceServer/app/routers/__pycache__/presence.cpython-314.pyc
Binary file not shown.
2 changes: 1 addition & 1 deletion PresenceServer/app/routers/presence.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from fastapi import APIRouter, Depends
from redis.asyncio import Redis

from app.dependencies import get_current_user_id, verify_internal_key # get_current_user_id used by GET /{user_id}
from app.dependencies import get_current_user_id, verify_internal_key
from app.redis import get_redis
from app.repos.presence import set_online, is_online, are_online
from app.schemas.presence import HeartbeatRequest, PresenceResponse, BatchPresenceRequest, BatchPresenceResponse
Expand Down
Binary file not shown.
Binary file not shown.
16 changes: 8 additions & 8 deletions PresenceServer/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
from app.redis import get_redis


def _make_redis_override(redis):
async def override():
return redis
return override


@pytest_asyncio.fixture
async def fake_redis():
redis = FakeRedis()
Expand All @@ -16,16 +22,13 @@ async def fake_redis():

@pytest_asyncio.fixture
async def client(fake_redis):
async def override_get_redis():
return fake_redis

async def override_get_current_user_id():
return "test-user-id"

async def override_verify_internal_key():
return None

app.dependency_overrides[get_redis] = override_get_redis
app.dependency_overrides[get_redis] = _make_redis_override(fake_redis)
app.dependency_overrides[get_current_user_id] = override_get_current_user_id
app.dependency_overrides[verify_internal_key] = override_verify_internal_key

Expand All @@ -37,10 +40,7 @@ async def override_verify_internal_key():

@pytest_asyncio.fixture
async def unauthed_client(fake_redis):
async def override_get_redis():
return fake_redis

app.dependency_overrides[get_redis] = override_get_redis
app.dependency_overrides[get_redis] = _make_redis_override(fake_redis)

async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac:
yield ac
Expand Down
Loading
Loading