Skip to content
Open
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
46 changes: 46 additions & 0 deletions fastapi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# FastAPI Real-time Chat Template

A real-time chat application built with FastAPI and WebSockets.

## Features
- Real-time messaging via WebSockets
- Simple username/password authentication
- Join/leave notifications
- Broadcast messaging
- Clean, responsive UI

## Quick Start

1. **Install dependencies**
```bash
pip install -r requirements.txt
```

2. **Run the server**
```bash
uvicorn main:app --host 0.0.0.0 --port 8080 --reload
```

3. **Open in browser**
Navigate to `http://localhost:8080`

## Test Users
- Username: `alice`, Password: `password123`
- Username: `bob`, Password: `securepass456`

## API Endpoints
- `GET /` - Chat web interface
- `POST /login` - User login
- `WS /ws/{username}` - WebSocket chat connection

## Structure
```
fastapi/
├── main.py # FastAPI application
├── public/
│ └── index.html # Chat frontend
├── requirements.txt # Python dependencies
├── ci.yml # Codesphere CI pipeline
├── start.sh # Startup script
└── README.md # This file
```
14 changes: 14 additions & 0 deletions fastapi/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
prepare:
steps:
- name: "Install dependencies"
command: "pip install -r requirements.txt"

test:
steps:
- name: "Run tests"
command: "python -m pytest tests/ -v"

run:
steps:
- name: "Start FastAPI server"
command: "uvicorn main:app --host 0.0.0.0 --port 8080"
77 changes: 77 additions & 0 deletions fastapi/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Depends, HTTPException, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from fastapi.responses import HTMLResponse
import json
import asyncio
from typing import Set, Dict
from datetime import datetime

app = FastAPI(title="FastAPI Chat", version="1.0.0")
security = HTTPBasic()

# Simple in-memory user store (for demo purposes)
USERS = {
"alice": "password123",
"bob": "securepass456"
}

# WebSocket connection manager
class ConnectionManager:
def __init__(self):
self.active_connections: Dict[str, WebSocket] = {}

async def connect(self, username: str, websocket: WebSocket):
await websocket.accept()
self.active_connections[username] = websocket
await self.broadcast(f"{username} joined the chat!", "system")

def disconnect(self, username: str):
if username in self.active_connections:
del self.active_connections[username]

async def broadcast(self, message: str, sender: str = "system"):
disconnected = []
for username, ws in self.active_connections.items():
try:
await ws.send_json({
"sender": sender,
"message": message,
"timestamp": datetime.now().isoformat()
})
except:
disconnected.append(username)
for username in disconnected:
self.disconnect(username)

manager = ConnectionManager()

@app.get("/")
async def get():
with open("public/index.html", "r") as f:
return HTMLResponse(f.read())

@app.post("/login")
async def login(credentials: HTTPBasicCredentials = Depends(security)):
username = credentials.username
password = credentials.password

if username in USERS and USERS[username] == password:
return {"username": username, "status": "logged_in"}

raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid credentials",
)

@app.websocket("/ws/{username}")
async def websocket_endpoint(websocket: WebSocket, username: str):
await manager.connect(username, websocket)
try:
while True:
data = await websocket.receive_text()
message_data = json.loads(data)
if message_data.get("type") == "message":
await manager.broadcast(message_data["content"], username)
except WebSocketDisconnect:
manager.disconnect(username)
await manager.broadcast(f"{username} left the chat!", "system")
14 changes: 14 additions & 0 deletions fastapi/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"Workspace": "free",
"Links": {
"Python": "https://codesphere.com/articles/python",
"FastAPI": "https://fastapi.tiangolo.com/"
},
"Categories": [
"Framework"
],
"Contributors": [
"ydd039"
],
"Title": "FastAPI - Real-time Chat App"
}
135 changes: 135 additions & 0 deletions fastapi/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FastAPI Chat</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); height: 100vh; display: flex; align-items: center; justify-content: center; }
.container { background: white; border-radius: 16px; box-shadow: 0 20px 60px rgba(0,0,0,0.3); width: 450px; max-width: 90vw; overflow: hidden; }
.header { background: #667eea; color: white; padding: 20px; text-align: center; }
.header h1 { font-size: 24px; margin-bottom: 5px; }
.header p { opacity: 0.8; font-size: 14px; }
.login-form { padding: 20px; display: flex; flex-direction: column; gap: 12px; }
.login-form input { padding: 12px; border: 2px solid #e0e0e0; border-radius: 8px; font-size: 14px; transition: border-color 0.3s; }
.login-form input:focus { outline: none; border-color: #667eea; }
.login-form button { background: #667eea; color: white; border: none; padding: 12px; border-radius: 8px; font-size: 16px; cursor: pointer; transition: background 0.3s; }
.login-form button:hover { background: #5a6fd6; }
.login-form .error { color: #e74c3c; font-size: 13px; text-align: center; }
.chat { display: none; flex-direction: column; padding: 0; }
.messages { height: 350px; overflow-y: auto; padding: 20px; background: #f8f9fa; display: flex; flex-direction: column; gap: 10px; }
.message { padding: 10px 14px; border-radius: 12px; max-width: 75%; word-wrap: break-word; }
.message.user { background: #667eea; color: white; align-self: flex-end; }
.message.other { background: white; color: #333; align-self: flex-start; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.message.system { background: transparent; color: #999; align-self: center; font-size: 12px; font-style: italic; }
.message .time { font-size: 11px; opacity: 0.7; margin-top: 4px; }
.input-area { display: flex; padding: 15px; gap: 10px; border-top: 1px solid #e0e0e0; }
.input-area input { flex: 1; padding: 12px; border: 2px solid #e0e0e0; border-radius: 8px; font-size: 14px; }
.input-area input:focus { outline: none; border-color: #667eea; }
.input-area button { background: #667eea; color: white; border: none; padding: 12px 20px; border-radius: 8px; cursor: pointer; }
.user-info { display: none; padding: 10px 20px; background: #f0f0f0; font-size: 13px; color: #666; justify-content: space-between; align-items: center; }
.user-info .logout { color: #e74c3c; cursor: pointer; text-decoration: underline; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>💬 FastAPI Chat</h1>
<p>Real-time messaging with WebSockets</p>
</div>

<div class="login-form" id="loginForm">
<h3 style="text-align:center;margin-bottom:5px;">Sign In</h3>
<input type="text" id="username" placeholder="Username" value="alice">
<input type="password" id="password" placeholder="Password" value="password123">
<div class="error" id="loginError"></div>
<button onclick="login()">Join Chat</button>
</div>

<div class="user-info" id="userInfo">
<span>Logged in as: <strong id="displayName"></strong></span>
<span class="logout" onclick="logout()">Logout</span>
</div>

<div class="chat" id="chatArea">
<div class="messages" id="messages"></div>
<div class="input-area">
<input type="text" id="messageInput" placeholder="Type a message..." onkeypress="if(event.key==='Enter') sendMessage()">
<button onclick="sendMessage()">Send</button>
</div>
</div>
</div>

<script>
let ws = null;
let currentUser = null;

async function login() {
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;

const res = await fetch('/login', {
method: 'POST',
headers: {
'Authorization': 'Basic ' + btoa(username + ':' + password)
}
});

if (res.ok) {
currentUser = username;
document.getElementById('loginForm').style.display = 'none';
document.getElementById('userInfo').style.display = 'flex';
document.getElementById('displayName').textContent = username;
document.getElementById('chatArea').style.display = 'flex';
connectWebSocket(username);
} else {
document.getElementById('loginError').textContent = 'Invalid credentials';
}
}

function connectWebSocket(username) {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const host = window.location.host;
ws = new WebSocket(`${protocol}//${host}/ws/${username}`);

ws.onmessage = function(event) {
const data = JSON.parse(event.data);
addMessage(data.sender, data.message, data.sender === currentUser ? 'user' : (data.sender === 'system' ? 'system' : 'other'));
};

ws.onclose = function() {
addMessage('system', 'Disconnected. Refresh to reconnect.', 'system');
};
}

function sendMessage() {
const input = document.getElementById('messageInput');
const text = input.value.trim();
if (text && ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({type: 'message', content: text}));
input.value = '';
}
}

function addMessage(sender, text, type) {
const messages = document.getElementById('messages');
const div = document.createElement('div');
div.className = 'message ' + type;
div.innerHTML = sender !== 'system' ? `<strong>${sender}</strong><br>${text}` : text;
messages.appendChild(div);
messages.scrollTop = messages.scrollHeight;
}

function logout() {
if (ws) ws.close();
currentUser = null;
document.getElementById('loginForm').style.display = 'flex';
document.getElementById('userInfo').style.display = 'none';
document.getElementById('chatArea').style.display = 'none';
document.getElementById('messages').innerHTML = '';
document.getElementById('loginError').textContent = '';
}
</script>
</body>
</html>
7 changes: 7 additions & 0 deletions fastapi/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
fastapi==0.115.6
uvicorn[standard]==0.34.0
websockets>=12.0
python-jose[cryptography]==3.3.0
passlib[bcrypt]==1.7.4
pydantic==2.10.3
python-multipart==0.0.17
6 changes: 6 additions & 0 deletions fastapi/start.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/bash
# For development:
uvicorn main:app --host 0.0.0.0 --port 8080 --reload

# For production:
# uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4
Empty file added fastapi/tests/__init__.py
Empty file.
20 changes: 20 additions & 0 deletions fastapi/tests/test_chat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from fastapi.testclient import TestClient
from main import app

client = TestClient(app)

def test_root_endpoint():
response = client.get("/")
assert response.status_code == 200
assert "text/html" in response.headers["content-type"]

def test_login_valid():
from requests.auth import HTTPBasicAuth
# Test with valid credentials
response = client.post("/login", auth=("alice", "password123"))
assert response.status_code == 200
assert response.json()["username"] == "alice"

def test_login_invalid():
response = client.post("/login", auth=("alice", "wrongpass"))
assert response.status_code == 401