Compare commits

...

7 Commits

Author SHA1 Message Date
shokollm
42640679c7 feat: implement JWT authentication system
- Add register endpoint with bcrypt password hashing
- Add login endpoint returning JWT tokens
- Add logout endpoint with token blacklisting
- Add /me endpoint for current user info
- Add rate limiting (5/minute) for login attempts using slowapi
- Add user settings GET and PATCH endpoints
- Create auth middleware via get_current_user dependency
- Add UserSettings and UserSettingsUpdate schemas
2026-04-08 05:48:38 +00:00
f59e595ffd Merge pull request '[Backend] Database Models - Add missing Pydantic schemas' (#14) from fix/issue-3 into main 2026-04-08 06:57:46 +02:00
shokollm
a5e41ab449 Add missing Pydantic schemas for BotConversation and Signal
Based on IMPLEMENTATION_PLAN.md Section 4 schema, the existing schemas.py
was missing schemas for:
- BotConversationCreate/Response (for bot_conversations table)
- SignalResponse (for signals table)

These were identified as gaps during issue #3 review.
2026-04-08 04:41:31 +00:00
6977203748 Merge pull request '[Backend] Project Setup - FastAPI Structure and Dependencies' (#13) from fix/issue-2 into main 2026-04-08 06:35:53 +02:00
shokollm
7dfd79dea2 fix: remove pycache and .env from git tracking
These files are listed in .gitignore but were accidentally committed.
Removing them from git while keeping them locally.
2026-04-08 03:54:26 +00:00
shokollm
a03304f9ef chore: update .gitignore to exclude Python artifacts 2026-04-08 03:49:06 +00:00
shokollm
f2b5bd5f45 feat: backend project setup with FastAPI structure and dependencies
- Create directory structure per IMPLEMENTATION_PLAN.md Section 12
- Add requirements.txt with FastAPI, SQLAlchemy, CrewAI, etc.
- Add core/config.py for environment variable configuration
- Add core/database.py for SQLite connection
- Add core/security.py for password hashing and JWT
- Add FastAPI app entry point (main.py) with all API routers
- Add Uvicorn runner (run.py)
- Add API route stubs (auth, bots, backtest, simulate, config)
- Add db/models.py with SQLAlchemy models
- Add db/schemas.py with Pydantic schemas
- Add service stubs (ai_agent, backtest, simulate engines)
- Add .env.example with all required environment variables
- Verify server starts correctly
2026-04-08 03:48:21 +00:00
28 changed files with 830 additions and 0 deletions

16
.gitignore vendored Normal file
View File

@@ -0,0 +1,16 @@
.kugetsu/
__pycache__/
*.pyc
*.pyo
*.pyd
.Python
*.so
*.egg
*.egg-info/
dist/
build/
.env
venv/
.venv/
*.db
data/

11
src/backend/.env.example Normal file
View File

@@ -0,0 +1,11 @@
DATABASE_URL=sqlite:///./data/app.db
SECRET_KEY=your-super-secret-key-change-in-production
JWT_ALGORITHM=HS256
ACCESS_TOKEN_EXPIRE_MINUTES=1440
MINIMAX_API_KEY=your-minimax-api-key
MINIMAX_MODEL=MiniMax-Text-01
AVE_API_KEY=your-ave-cloud-api-key
AVE_API_PLAN=free
HOST=0.0.0.0
PORT=8000
DEBUG=false

View File

143
src/backend/app/api/auth.py Normal file
View File

@@ -0,0 +1,143 @@
from fastapi import APIRouter, Depends, HTTPException, status, Request
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from sqlalchemy.orm import Session
from typing import Annotated
from ..core.database import get_db
from ..core.security import (
get_password_hash,
verify_password,
create_access_token,
verify_token,
)
from ..core.config import get_settings
from ..core.limiter import limiter
from ..db.schemas import (
UserCreate,
UserResponse,
Token,
UserSettings,
UserSettingsUpdate,
)
from ..db.models import User
router = APIRouter()
settings = get_settings()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/login")
TOKEN_BLACKLIST = set()
def get_current_user(
token: Annotated[str, Depends(oauth2_scheme)], db: Session = Depends(get_db)
) -> User:
if token in TOKEN_BLACKLIST:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Token has been revoked",
)
payload = verify_token(token)
if payload is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid or expired token",
)
user_id = payload.get("sub")
if user_id is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid token payload",
)
user = db.query(User).filter(User.id == user_id).first()
if user is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="User not found",
)
return user
@router.post(
"/register", response_model=UserResponse, status_code=status.HTTP_201_CREATED
)
def register(user: UserCreate, db: Session = Depends(get_db)):
existing_user = db.query(User).filter(User.email == user.email).first()
if existing_user:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Email already registered",
)
hashed_password = get_password_hash(user.password)
db_user = User(
email=user.email,
password_hash=hashed_password,
)
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
@router.post("/login", response_model=Token)
@limiter.limit("5/minute")
def login(
request: Request,
form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
db: Session = Depends(get_db),
):
user = db.query(User).filter(User.email == form_data.username).first()
if not user or not verify_password(form_data.password, user.password_hash):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect email or password",
)
access_token = create_access_token(data={"sub": user.id})
return Token(access_token=access_token, token_type="bearer")
@router.post("/logout")
def logout(
current_user: Annotated[User, Depends(get_current_user)],
token: Annotated[str, Depends(oauth2_scheme)],
):
TOKEN_BLACKLIST.add(token)
return {"message": "Successfully logged out"}
@router.get("/me", response_model=UserResponse)
def get_me(
current_user: Annotated[User, Depends(get_current_user)],
):
return current_user
@router.get("/settings", response_model=UserSettings)
def get_settings_endpoint(
current_user: Annotated[User, Depends(get_current_user)],
):
return UserSettings(email=current_user.email)
@router.patch("/settings", response_model=UserSettings)
def update_settings(
current_user: Annotated[User, Depends(get_current_user)],
settings_update: UserSettingsUpdate,
db: Session = Depends(get_db),
):
if settings_update.email:
existing = (
db.query(User)
.filter(User.email == settings_update.email, User.id != current_user.id)
.first()
)
if existing:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Email already in use",
)
current_user.email = settings_update.email
if settings_update.password:
current_user.password_hash = get_password_hash(settings_update.password)
db.commit()
db.refresh(current_user)
return UserSettings(email=current_user.email)

View File

@@ -0,0 +1,36 @@
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
from typing import List
from ..core.database import get_db
from ..db.schemas import BacktestCreate, BacktestResponse
router = APIRouter()
@router.post("/bots/{bot_id}/backtest", response_model=BacktestResponse)
def start_backtest(bot_id: str, config: BacktestCreate, db: Session = Depends(get_db)):
raise HTTPException(
status_code=status.HTTP_501_NOT_IMPLEMENTED, detail="Not implemented"
)
@router.get("/bots/{bot_id}/backtest/{run_id}", response_model=BacktestResponse)
def get_backtest(bot_id: str, run_id: str, db: Session = Depends(get_db)):
raise HTTPException(
status_code=status.HTTP_501_NOT_IMPLEMENTED, detail="Not implemented"
)
@router.get("/bots/{bot_id}/backtests", response_model=List[BacktestResponse])
def list_backtests(bot_id: str, db: Session = Depends(get_db)):
raise HTTPException(
status_code=status.HTTP_501_NOT_IMPLEMENTED, detail="Not implemented"
)
@router.post("/bots/{bot_id}/backtest/{run_id}/stop")
def stop_backtest(bot_id: str, run_id: str, db: Session = Depends(get_db)):
raise HTTPException(
status_code=status.HTTP_501_NOT_IMPLEMENTED, detail="Not implemented"
)

View File

@@ -0,0 +1,57 @@
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
from typing import List
from ..core.database import get_db
from ..db.schemas import BotCreate, BotUpdate, BotResponse
router = APIRouter()
@router.get("", response_model=List[BotResponse])
def list_bots(db: Session = Depends(get_db)):
raise HTTPException(
status_code=status.HTTP_501_NOT_IMPLEMENTED, detail="Not implemented"
)
@router.post("", response_model=BotResponse)
def create_bot(bot: BotCreate, db: Session = Depends(get_db)):
raise HTTPException(
status_code=status.HTTP_501_NOT_IMPLEMENTED, detail="Not implemented"
)
@router.get("/{bot_id}", response_model=BotResponse)
def get_bot(bot_id: str, db: Session = Depends(get_db)):
raise HTTPException(
status_code=status.HTTP_501_NOT_IMPLEMENTED, detail="Not implemented"
)
@router.put("/{bot_id}", response_model=BotResponse)
def update_bot(bot_id: str, bot: BotUpdate, db: Session = Depends(get_db)):
raise HTTPException(
status_code=status.HTTP_501_NOT_IMPLEMENTED, detail="Not implemented"
)
@router.delete("/{bot_id}")
def delete_bot(bot_id: str, db: Session = Depends(get_db)):
raise HTTPException(
status_code=status.HTTP_501_NOT_IMPLEMENTED, detail="Not implemented"
)
@router.post("/{bot_id}/chat")
def chat(bot_id: str, message: dict, db: Session = Depends(get_db)):
raise HTTPException(
status_code=status.HTTP_501_NOT_IMPLEMENTED, detail="Not implemented"
)
@router.get("/{bot_id}/history")
def get_history(bot_id: str, db: Session = Depends(get_db)):
raise HTTPException(
status_code=status.HTTP_501_NOT_IMPLEMENTED, detail="Not implemented"
)

View File

@@ -0,0 +1,13 @@
from fastapi import APIRouter
router = APIRouter()
@router.get("/chains")
def get_chains():
return {"chains": []}
@router.get("/tokens")
def get_tokens():
return {"tokens": []}

View File

@@ -0,0 +1,38 @@
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
from typing import List
from ..core.database import get_db
from ..db.schemas import SimulationCreate, SimulationResponse
router = APIRouter()
@router.post("/bots/{bot_id}/simulate", response_model=SimulationResponse)
def start_simulation(
bot_id: str, config: SimulationCreate, db: Session = Depends(get_db)
):
raise HTTPException(
status_code=status.HTTP_501_NOT_IMPLEMENTED, detail="Not implemented"
)
@router.get("/bots/{bot_id}/simulate/{run_id}", response_model=SimulationResponse)
def get_simulation(bot_id: str, run_id: str, db: Session = Depends(get_db)):
raise HTTPException(
status_code=status.HTTP_501_NOT_IMPLEMENTED, detail="Not implemented"
)
@router.get("/bots/{bot_id}/simulations", response_model=List[SimulationResponse])
def list_simulations(bot_id: str, db: Session = Depends(get_db)):
raise HTTPException(
status_code=status.HTTP_501_NOT_IMPLEMENTED, detail="Not implemented"
)
@router.post("/bots/{bot_id}/simulate/{run_id}/stop")
def stop_simulation(bot_id: str, run_id: str, db: Session = Depends(get_db)):
raise HTTPException(
status_code=status.HTTP_501_NOT_IMPLEMENTED, detail="Not implemented"
)

View File

View File

@@ -0,0 +1,25 @@
from pydantic_settings import BaseSettings
from functools import lru_cache
class Settings(BaseSettings):
DATABASE_URL: str = "sqlite:///./data/app.db"
SECRET_KEY: str
JWT_ALGORITHM: str = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES: int = 1440
MINIMAX_API_KEY: str
MINIMAX_MODEL: str = "MiniMax-Text-01"
AVE_API_KEY: str
AVE_API_PLAN: str = "free"
HOST: str = "0.0.0.0"
PORT: int = 8000
DEBUG: bool = False
class Config:
env_file = ".env"
extra = "allow"
@lru_cache()
def get_settings() -> Settings:
return Settings()

View File

@@ -0,0 +1,40 @@
import os
from sqlalchemy import create_engine, event
from sqlalchemy.orm import sessionmaker, declarative_base
from sqlalchemy.engine import Engine
from .config import get_settings
settings = get_settings()
if settings.DATABASE_URL.startswith("sqlite"):
db_path = settings.DATABASE_URL.replace("sqlite:///", "")
os.makedirs(
os.path.dirname(db_path) if os.path.dirname(db_path) else ".", exist_ok=True
)
@event.listens_for(Engine, "connect")
def set_sqlite_pragma(dbapi_conn, connection_record):
cursor = dbapi_conn.cursor()
cursor.execute("PRAGMA foreign_keys=ON")
cursor.close()
engine = create_engine(
settings.DATABASE_URL,
connect_args={"check_same_thread": False}
if settings.DATABASE_URL.startswith("sqlite")
else {},
echo=settings.DEBUG,
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()

View File

@@ -0,0 +1,4 @@
from slowapi import Limiter
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)

View File

@@ -0,0 +1,42 @@
from datetime import datetime, timedelta
from typing import Any, Optional
from jose import jwt, JWTError
from passlib.context import CryptContext
from .config import get_settings
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
to_encode = data.copy()
settings = get_settings()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(
minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES
)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(
to_encode, settings.SECRET_KEY, algorithm=settings.JWT_ALGORITHM
)
return encoded_jwt
def verify_token(token: str) -> Optional[dict[str, Any]]:
settings = get_settings()
try:
payload = jwt.decode(
token, settings.SECRET_KEY, algorithms=[settings.JWT_ALGORITHM]
)
return payload
except JWTError:
return None
def verify_password(plain_password: str, hashed_password: str) -> bool:
return pwd_context.verify(plain_password, hashed_password)
def get_password_hash(password: str) -> str:
return pwd_context.hash(password)

View File

View File

@@ -0,0 +1,121 @@
import uuid
from datetime import datetime
from sqlalchemy import (
Column,
String,
Text,
Float,
Boolean,
DateTime,
ForeignKey,
Index,
JSON,
)
from sqlalchemy.orm import relationship
from ..core.database import Base
def generate_uuid():
return str(uuid.uuid4())
class User(Base):
__tablename__ = "users"
id = Column(String, primary_key=True, default=generate_uuid)
email = Column(String, unique=True, nullable=False)
password_hash = Column(String, nullable=False)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
bots = relationship("Bot", back_populates="user", cascade="all, delete-orphan")
class Bot(Base):
__tablename__ = "bots"
id = Column(String, primary_key=True, default=generate_uuid)
user_id = Column(String, ForeignKey("users.id"), nullable=False)
name = Column(String, nullable=False)
description = Column(Text)
strategy_config = Column(JSON, nullable=False)
llm_config = Column(JSON, nullable=False)
status = Column(String, default="draft")
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
user = relationship("User", back_populates="bots")
conversations = relationship(
"BotConversation", back_populates="bot", cascade="all, delete-orphan"
)
backtests = relationship(
"Backtest", back_populates="bot", cascade="all, delete-orphan"
)
simulations = relationship(
"Simulation", back_populates="bot", cascade="all, delete-orphan"
)
signals = relationship("Signal", back_populates="bot", cascade="all, delete-orphan")
class BotConversation(Base):
__tablename__ = "bot_conversations"
id = Column(String, primary_key=True, default=generate_uuid)
bot_id = Column(String, ForeignKey("bots.id"), nullable=False)
role = Column(String, nullable=False)
content = Column(Text, nullable=False)
created_at = Column(DateTime, default=datetime.utcnow)
bot = relationship("Bot", back_populates="conversations")
class Backtest(Base):
__tablename__ = "backtests"
id = Column(String, primary_key=True, default=generate_uuid)
bot_id = Column(String, ForeignKey("bots.id"), nullable=False)
started_at = Column(DateTime, nullable=False)
ended_at = Column(DateTime)
status = Column(String, nullable=False)
config = Column(JSON, nullable=False)
result = Column(JSON)
bot = relationship("Bot", back_populates="backtests")
class Simulation(Base):
__tablename__ = "simulations"
id = Column(String, primary_key=True, default=generate_uuid)
bot_id = Column(String, ForeignKey("bots.id"), nullable=False)
started_at = Column(DateTime, nullable=False)
status = Column(String, nullable=False)
config = Column(JSON, nullable=False)
signals = Column(JSON)
bot = relationship("Bot", back_populates="simulations")
class Signal(Base):
__tablename__ = "signals"
id = Column(String, primary_key=True, default=generate_uuid)
bot_id = Column(String, ForeignKey("bots.id"), nullable=False)
run_id = Column(String, nullable=False)
signal_type = Column(String, nullable=False)
token = Column(String, nullable=False)
price = Column(Float, nullable=False)
confidence = Column(Float)
reasoning = Column(Text)
executed = Column(Boolean, default=False)
created_at = Column(DateTime, default=datetime.utcnow)
bot = relationship("Bot", back_populates="signals")
Index("idx_bots_user_id", Bot.user_id)
Index("idx_conversations_bot_id", BotConversation.bot_id)
Index("idx_backtests_bot_id", Backtest.bot_id)
Index("idx_simulations_bot_id", Simulation.bot_id)
Index("idx_signals_bot_id", Signal.bot_id)
Index("idx_signals_run_id", Signal.run_id)

View File

@@ -0,0 +1,134 @@
from pydantic import BaseModel, EmailStr
from typing import Optional, List, Any
from datetime import datetime
class UserCreate(BaseModel):
email: EmailStr
password: str
class UserResponse(BaseModel):
id: str
email: str
created_at: datetime
updated_at: datetime
class Config:
from_attributes = True
class Token(BaseModel):
access_token: str
token_type: str
class UserSettings(BaseModel):
email: EmailStr
class UserSettingsUpdate(BaseModel):
email: Optional[EmailStr] = None
password: Optional[str] = None
class BotCreate(BaseModel):
name: str
description: Optional[str] = None
strategy_config: dict
llm_config: dict
class BotUpdate(BaseModel):
name: Optional[str] = None
description: Optional[str] = None
strategy_config: Optional[dict] = None
llm_config: Optional[dict] = None
status: Optional[str] = None
class BotResponse(BaseModel):
id: str
user_id: str
name: str
description: Optional[str]
strategy_config: dict
llm_config: dict
status: str
created_at: datetime
updated_at: datetime
class Config:
from_attributes = True
class BacktestCreate(BaseModel):
token: str
chain: str
timeframe: str
start_date: str
end_date: str
class BacktestResponse(BaseModel):
id: str
bot_id: str
started_at: datetime
ended_at: Optional[datetime]
status: str
config: dict
result: Optional[dict]
class Config:
from_attributes = True
class SimulationCreate(BaseModel):
token: str
chain: str
duration_seconds: int = 3600
auto_execute: bool = False
class SimulationResponse(BaseModel):
id: str
bot_id: str
started_at: datetime
status: str
config: dict
signals: Optional[List[dict]]
class Config:
from_attributes = True
class BotConversationCreate(BaseModel):
role: str
content: str
class BotConversationResponse(BaseModel):
id: str
bot_id: str
role: str
content: str
created_at: datetime
class Config:
from_attributes = True
class SignalResponse(BaseModel):
id: str
bot_id: str
run_id: str
signal_type: str
token: str
price: float
confidence: Optional[float]
reasoning: Optional[str]
executed: bool
created_at: datetime
class Config:
from_attributes = True

38
src/backend/app/main.py Normal file
View File

@@ -0,0 +1,38 @@
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from slowapi import Limiter
from slowapi.util import get_remote_address
from .api import auth, bots, backtest, simulate, config
from .core.limiter import limiter
app = FastAPI(
title="Randebu Trading Bot API",
description="AI-powered trading bot platform API",
version="0.1.0",
)
app.state.limiter = limiter
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
app.include_router(auth.router, prefix="/api/auth", tags=["auth"])
app.include_router(bots.router, prefix="/api/bots", tags=["bots"])
app.include_router(backtest.router, prefix="/api", tags=["backtest"])
app.include_router(simulate.router, prefix="/api", tags=["simulate"])
app.include_router(config.router, prefix="/api/config", tags=["config"])
@app.get("/")
def root():
return {"status": "ok", "message": "Randebu Trading Bot API"}
@app.get("/health")
def health():
return {"status": "healthy"}

View File

View File

@@ -0,0 +1,4 @@
from .crew import CrewAgent
from .llm_connector import LLMConnector
__all__ = ["CrewAgent", "LLMConnector"]

View File

@@ -0,0 +1,15 @@
from typing import List, Optional
class CrewAgent:
def __init__(self, role: str, goal: str, backstory: str):
self.role = role
self.goal = goal
self.backstory = backstory
def execute_task(self, task: str) -> str:
raise NotImplementedError("CrewAI agent not yet implemented")
def get_trading_crew():
raise NotImplementedError("Trading crew not yet implemented")

View File

@@ -0,0 +1,13 @@
from typing import Optional
class LLMConnector:
def __init__(self, api_key: str, model: str = "MiniMax-Text-01"):
self.api_key = api_key
self.model = model
def chat(self, messages: list[dict], **kwargs):
raise NotImplementedError("LLM integration not yet implemented")
def parse_strategy(self, user_message: str) -> dict:
raise NotImplementedError("Strategy parsing not yet implemented")

View File

@@ -0,0 +1,3 @@
from .engine import BacktestEngine
__all__ = ["BacktestEngine"]

View File

@@ -0,0 +1,15 @@
from typing import Optional, Dict, Any
class BacktestEngine:
def __init__(self, config: Dict[str, Any]):
self.config = config
async def run(self) -> Dict[str, Any]:
raise NotImplementedError("Backtest engine not yet implemented")
async def stop(self):
raise NotImplementedError("Backtest stop not yet implemented")
def get_results(self) -> Dict[str, Any]:
raise NotImplementedError("Backtest results not yet implemented")

View File

@@ -0,0 +1,3 @@
from .engine import SimulateEngine
__all__ = ["SimulateEngine"]

View File

@@ -0,0 +1,15 @@
from typing import Optional, Dict, Any, List
class SimulateEngine:
def __init__(self, config: Dict[str, Any]):
self.config = config
async def run(self) -> List[Dict[str, Any]]:
raise NotImplementedError("Simulation engine not yet implemented")
async def stop(self):
raise NotImplementedError("Simulation stop not yet implemented")
def get_signals(self) -> List[Dict[str, Any]]:
raise NotImplementedError("Simulation signals not yet implemented")

View File

@@ -0,0 +1,13 @@
fastapi>=0.109.0
uvicorn>=0.27.0
sqlalchemy>=2.0.0
pydantic>=2.5.0
pydantic-settings>=2.1.0
email-validator>=2.0.0
python-jose[cryptography]>=3.3.0
passlib[bcrypt]>=1.7.4
crewai>=0.1.0
anthropic>=0.18.0
httpx>=0.26.0
python-multipart>=0.0.6
slowapi>=0.1.9

11
src/backend/run.py Normal file
View File

@@ -0,0 +1,11 @@
import uvicorn
from app.core.config import get_settings
if __name__ == "__main__":
settings = get_settings()
uvicorn.run(
"app.main:app",
host=settings.HOST,
port=settings.PORT,
reload=settings.DEBUG,
)

20
src/backend/setup.py Normal file
View File

@@ -0,0 +1,20 @@
from setuptools import setup, find_packages
setup(
name="randebu-backend",
version="0.1.0",
packages=find_packages(),
install_requires=[
"fastapi>=0.109.0",
"uvicorn>=0.27.0",
"sqlalchemy>=2.0.0",
"pydantic>=2.5.0",
"pydantic-settings>=2.1.0",
"python-jose[cryptography]>=3.3.0",
"passlib[bcrypt]>=1.7.4",
"crewai>=0.1.0",
"anthropic>=0.18.0",
"httpx>=0.26.0",
"python-multipart>=0.0.6",
],
)