mirror of
https://gitverse.ru/kpa39l/telepost.git
synced 2026-09-29 09:55:09 +00:00
Initial commit
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
from typing import Dict, Any
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ....database.database import get_db
|
||||
from ....auth.telegram_auth import authenticate_user_telegram
|
||||
from ....schemas.token import Token
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/telegram-login", response_model=Token)
|
||||
async def telegram_login(auth_data: Dict[str, Any], db: Session = Depends(get_db)):
|
||||
"""
|
||||
Authenticate user via Telegram Login Widget
|
||||
Expects data received from Telegram Login Widget
|
||||
"""
|
||||
result = authenticate_user_telegram(db, auth_data)
|
||||
|
||||
if not result:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid Telegram login data",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
return result
|
||||
@@ -0,0 +1,101 @@
|
||||
from typing import List
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from ....database.database import get_db
|
||||
from ....auth.jwt import get_current_user
|
||||
from ....models.user import User
|
||||
from ....schemas.post import Post, PostCreate, PostUpdate
|
||||
from ....services.post_service import PostService
|
||||
|
||||
router = APIRouter(prefix="/posts", tags=["posts"])
|
||||
|
||||
|
||||
@router.get("/", response_model=List[Post])
|
||||
def get_posts(
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Получить список постов пользователя
|
||||
"""
|
||||
post_service = PostService(db)
|
||||
posts = post_service.get_posts(owner_id=current_user.id, skip=skip, limit=limit)
|
||||
return posts
|
||||
|
||||
|
||||
@router.post("/", response_model=Post)
|
||||
def create_post(
|
||||
post: PostCreate,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Создать новый пост
|
||||
"""
|
||||
post_service = PostService(db)
|
||||
db_post = post_service.create_post(post_data=post, owner_id=current_user.id)
|
||||
return db_post
|
||||
|
||||
|
||||
@router.get("/{post_id}", response_model=Post)
|
||||
def get_post(
|
||||
post_id: int,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Получить конкретный пост
|
||||
"""
|
||||
post_service = PostService(db)
|
||||
db_post = post_service.get_post(post_id=post_id, owner_id=current_user.id)
|
||||
if not db_post:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Post not found"
|
||||
)
|
||||
return db_post
|
||||
|
||||
|
||||
@router.put("/{post_id}", response_model=Post)
|
||||
def update_post(
|
||||
post_id: int,
|
||||
post_update: PostUpdate,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Обновить пост
|
||||
"""
|
||||
post_service = PostService(db)
|
||||
db_post = post_service.update_post(
|
||||
post_id=post_id,
|
||||
post_data=post_update,
|
||||
owner_id=current_user.id
|
||||
)
|
||||
if not db_post:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Post not found"
|
||||
)
|
||||
return db_post
|
||||
|
||||
|
||||
@router.delete("/{post_id}")
|
||||
def delete_post(
|
||||
post_id: int,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Удалить пост
|
||||
"""
|
||||
post_service = PostService(db)
|
||||
success = post_service.delete_post(post_id=post_id, owner_id=current_user.id)
|
||||
if not success:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Post not found"
|
||||
)
|
||||
return {"message": "Post deleted successfully"}
|
||||
@@ -0,0 +1,11 @@
|
||||
from fastapi import APIRouter
|
||||
from .endpoints import auth
|
||||
from .endpoints import posts
|
||||
|
||||
api_router = APIRouter()
|
||||
|
||||
# Include authentication routes
|
||||
api_router.include_router(auth.router, prefix="/auth", tags=["authentication"])
|
||||
|
||||
# Include posts routes
|
||||
api_router.include_router(posts.router)
|
||||
@@ -0,0 +1,61 @@
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional
|
||||
from jose import JWTError, jwt
|
||||
from fastapi import HTTPException, status, Depends
|
||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ..database.config import settings
|
||||
from ..models.user import User
|
||||
from ..schemas.token import TokenData
|
||||
from ..database.database import get_db
|
||||
|
||||
security = HTTPBearer()
|
||||
|
||||
|
||||
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
|
||||
to_encode = data.copy()
|
||||
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.ALGORITHM)
|
||||
return encoded_jwt
|
||||
|
||||
|
||||
def verify_token(token: str, credentials_exception):
|
||||
try:
|
||||
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
|
||||
username: str = payload.get("sub")
|
||||
if username is None:
|
||||
raise credentials_exception
|
||||
token_data = TokenData(username=username)
|
||||
except JWTError:
|
||||
raise credentials_exception
|
||||
return token_data
|
||||
|
||||
|
||||
async def get_current_user(
|
||||
credentials: HTTPAuthorizationCredentials = Depends(security),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
credentials_exception = HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Could not validate credentials",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
token_data = verify_token(credentials.credentials, credentials_exception)
|
||||
|
||||
user = db.query(User).filter(User.username == token_data.username).first()
|
||||
if user is None:
|
||||
raise credentials_exception
|
||||
return user
|
||||
|
||||
|
||||
async def get_current_active_user(current_user: User = Depends(get_current_user)):
|
||||
if not current_user.is_active:
|
||||
raise HTTPException(status_code=400, detail="Inactive user")
|
||||
return current_user
|
||||
@@ -0,0 +1,117 @@
|
||||
import hashlib
|
||||
import hmac
|
||||
from typing import Dict, Any, Optional
|
||||
from datetime import datetime, timedelta
|
||||
from fastapi import HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ..database.config import settings
|
||||
from ..models.user import User
|
||||
from ..schemas.user import UserCreate, UserInDB
|
||||
from .jwt import create_access_token
|
||||
|
||||
|
||||
def verify_telegram_login_data(auth_data: Dict[str, Any]) -> bool:
|
||||
"""
|
||||
Verify the authenticity of Telegram login data
|
||||
"""
|
||||
if not settings.TELEGRAM_BOT_TOKEN:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Telegram bot token not configured"
|
||||
)
|
||||
|
||||
# Extract the hash
|
||||
received_hash = auth_data.pop('hash', None)
|
||||
|
||||
if not received_hash:
|
||||
return False
|
||||
|
||||
# Get the auth date
|
||||
auth_date = auth_data.pop('auth_date', None)
|
||||
if auth_date:
|
||||
# Check if the data is not too old (more than 1 day)
|
||||
auth_time = datetime.fromtimestamp(int(auth_date))
|
||||
if (datetime.now() - auth_time).days > 0:
|
||||
return False
|
||||
|
||||
# Sort the remaining parameters alphabetically by key
|
||||
data_check_arr = []
|
||||
for key, value in sorted(auth_data.items()):
|
||||
data_check_arr.append(f"{key}={value}")
|
||||
|
||||
data_check_string = '\n'.join(data_check_arr)
|
||||
|
||||
# Create secret key using SHA256 of bot token
|
||||
secret_key = hashlib.sha256(settings.TELEGRAM_BOT_TOKEN.encode()).digest()
|
||||
|
||||
# Calculate HMAC-SHA256 signature
|
||||
calculated_hash = hmac.new(
|
||||
secret_key,
|
||||
data_check_string.encode(),
|
||||
hashlib.sha256
|
||||
).hexdigest()
|
||||
|
||||
# Compare the calculated hash with the received hash
|
||||
return calculated_hash == received_hash
|
||||
|
||||
|
||||
def authenticate_user_telegram(db: Session, auth_data: Dict[str, Any]):
|
||||
"""
|
||||
Authenticate user via Telegram login data and return JWT token
|
||||
"""
|
||||
# Verify the authenticity of the data
|
||||
if not verify_telegram_login_data(auth_data.copy()): # Copy to avoid modifying original
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid Telegram login data"
|
||||
)
|
||||
|
||||
# Extract user data
|
||||
telegram_id = str(auth_data.get('id'))
|
||||
username = auth_data.get('username')
|
||||
first_name = auth_data.get('first_name')
|
||||
last_name = auth_data.get('last_name')
|
||||
photo_url = auth_data.get('photo_url')
|
||||
|
||||
# Try to find existing user by telegram_id
|
||||
user = db.query(User).filter(User.telegram_id == telegram_id).first()
|
||||
|
||||
if user:
|
||||
# Update user data if it has changed
|
||||
user.username = username
|
||||
user.first_name = first_name
|
||||
user.last_name = last_name
|
||||
user.avatar_url = photo_url
|
||||
user.is_verified = True
|
||||
|
||||
db.commit()
|
||||
db.refresh(user)
|
||||
else:
|
||||
# Create new user
|
||||
user = User(
|
||||
telegram_id=telegram_id,
|
||||
username=username,
|
||||
first_name=first_name,
|
||||
last_name=last_name,
|
||||
avatar_url=photo_url,
|
||||
is_verified=True,
|
||||
is_active=True
|
||||
)
|
||||
|
||||
db.add(user)
|
||||
db.commit()
|
||||
db.refresh(user)
|
||||
|
||||
# Create JWT token
|
||||
access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
access_token = create_access_token(
|
||||
data={"sub": user.username or user.telegram_id},
|
||||
expires_delta=access_token_expires
|
||||
)
|
||||
|
||||
return {
|
||||
"access_token": access_token,
|
||||
"token_type": "bearer",
|
||||
"user": UserInDB.from_orm(user) if hasattr(UserInDB, 'from_orm') else UserInDB.model_validate(user)
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
from pydantic_settings import BaseSettings
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
DATABASE_URL: str = "postgresql://user:password@localhost/telepost"
|
||||
REDIS_URL: str = "redis://localhost:6379"
|
||||
|
||||
SECRET_KEY: str = "your-secret-key-here"
|
||||
ALGORITHM: str = "HS256"
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
|
||||
|
||||
# Telegram Bot API settings
|
||||
TELEGRAM_BOT_TOKEN: Optional[str] = None
|
||||
TELEGRAM_BOT_NAME: Optional[str] = None
|
||||
|
||||
class Config:
|
||||
env_file = ".env"
|
||||
|
||||
|
||||
settings = Settings()
|
||||
@@ -0,0 +1,31 @@
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from .config import settings
|
||||
|
||||
# Create the database engine
|
||||
engine = create_engine(
|
||||
settings.DATABASE_URL,
|
||||
pool_pre_ping=True, # Verify connections before use
|
||||
pool_recycle=300, # Recycle connections after 5 minutes
|
||||
)
|
||||
|
||||
# Create a configured "SessionLocal" class
|
||||
SessionLocal = sessionmaker(
|
||||
autocommit=False,
|
||||
autoflush=False,
|
||||
bind=engine
|
||||
)
|
||||
|
||||
# Create a Base class for declarative models
|
||||
Base = declarative_base()
|
||||
|
||||
def get_db():
|
||||
"""
|
||||
Dependency to get database session
|
||||
"""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
@@ -0,0 +1,19 @@
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy import create_engine
|
||||
from typing import Generator
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
def get_db() -> Generator:
|
||||
"""
|
||||
Dependency to get database session
|
||||
"""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
# Engine and session will be configured in database/database.py
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False)
|
||||
@@ -0,0 +1,26 @@
|
||||
from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey, Boolean
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.sql import func
|
||||
from ..database.database import Base
|
||||
from datetime import datetime
|
||||
import uuid
|
||||
|
||||
|
||||
class Post(Base):
|
||||
__tablename__ = "posts"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
title = Column(String(255), nullable=False)
|
||||
content = Column(Text, nullable=False)
|
||||
scheduled_time = Column(DateTime, nullable=True)
|
||||
status = Column(String(50), default="draft") # draft, scheduled, published, cancelled
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
||||
published_at = Column(DateTime(timezone=True), nullable=True)
|
||||
owner_id = Column(Integer, ForeignKey("users.id"), nullable=False)
|
||||
|
||||
# Relationship
|
||||
owner = relationship("User", back_populates="posts")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Post(id={self.id}, title='{self.title}', status='{self.status}')>"
|
||||
@@ -0,0 +1,28 @@
|
||||
from sqlalchemy import Column, Integer, String, Boolean, DateTime
|
||||
from sqlalchemy.sql import func
|
||||
from typing import TYPE_CHECKING
|
||||
from .base import Base
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = "users"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
telegram_id = Column(String, unique=True, index=True, nullable=False)
|
||||
username = Column(String, unique=True, index=True, nullable=True)
|
||||
first_name = Column(String, nullable=True)
|
||||
last_name = Column(String, nullable=True)
|
||||
email = Column(String, unique=True, index=True, nullable=True)
|
||||
avatar_url = Column(String, nullable=True)
|
||||
|
||||
is_active = Column(Boolean, default=True)
|
||||
is_verified = Column(Boolean, default=False)
|
||||
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
||||
|
||||
def __repr__(self):
|
||||
return f"<User(id={self.id}, telegram_id={self.telegram_id}, username={self.username})>"
|
||||
@@ -0,0 +1,44 @@
|
||||
from pydantic import BaseModel
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class PostStatus(str, Enum):
|
||||
draft = "draft"
|
||||
scheduled = "scheduled"
|
||||
published = "published"
|
||||
cancelled = "cancelled"
|
||||
|
||||
|
||||
class PostBase(BaseModel):
|
||||
title: str
|
||||
content: str
|
||||
scheduled_time: Optional[datetime] = None
|
||||
status: PostStatus = PostStatus.draft
|
||||
|
||||
|
||||
class PostCreate(PostBase):
|
||||
pass
|
||||
|
||||
|
||||
class PostUpdate(BaseModel):
|
||||
title: Optional[str] = None
|
||||
content: Optional[str] = None
|
||||
scheduled_time: Optional[datetime] = None
|
||||
status: Optional[PostStatus] = None
|
||||
|
||||
|
||||
class PostInDB(PostBase):
|
||||
id: int
|
||||
owner_id: int
|
||||
created_at: datetime
|
||||
updated_at: Optional[datetime] = None
|
||||
published_at: Optional[datetime] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class Post(PostInDB):
|
||||
pass
|
||||
@@ -0,0 +1,12 @@
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class Token(BaseModel):
|
||||
access_token: str
|
||||
token_type: str
|
||||
|
||||
|
||||
class TokenData(BaseModel):
|
||||
username: Optional[str] = None
|
||||
user_id: Optional[int] = None
|
||||
@@ -0,0 +1,48 @@
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class UserBase(BaseModel):
|
||||
telegram_id: str
|
||||
username: Optional[str] = None
|
||||
first_name: Optional[str] = None
|
||||
last_name: Optional[str] = None
|
||||
email: Optional[str] = None
|
||||
avatar_url: Optional[str] = None
|
||||
is_active: Optional[bool] = True
|
||||
is_verified: Optional[bool] = False
|
||||
|
||||
|
||||
class UserCreate(UserBase):
|
||||
pass
|
||||
|
||||
|
||||
class UserUpdate(BaseModel):
|
||||
username: Optional[str] = None
|
||||
first_name: Optional[str] = None
|
||||
last_name: Optional[str] = None
|
||||
email: Optional[str] = None
|
||||
avatar_url: Optional[str] = None
|
||||
|
||||
|
||||
class UserInDB(UserBase):
|
||||
id: int
|
||||
created_at: datetime
|
||||
updated_at: Optional[datetime] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class UserPublic(BaseModel):
|
||||
id: int
|
||||
telegram_id: str
|
||||
username: Optional[str] = None
|
||||
first_name: Optional[str] = None
|
||||
last_name: Optional[str] = None
|
||||
avatar_url: Optional[str] = None
|
||||
is_verified: bool
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
@@ -0,0 +1,88 @@
|
||||
from typing import List, Optional
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import and_, or_
|
||||
from ..models.post import Post
|
||||
from ..schemas.post import PostCreate, PostUpdate
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class PostService:
|
||||
def __init__(self, db_session: Session):
|
||||
self.db = db_session
|
||||
|
||||
def get_post(self, post_id: int, owner_id: int) -> Optional[Post]:
|
||||
"""Получить пост по ID и владельцу"""
|
||||
return self.db.query(Post).filter(
|
||||
and_(Post.id == post_id, Post.owner_id == owner_id)
|
||||
).first()
|
||||
|
||||
def get_posts(self, owner_id: int, skip: int = 0, limit: int = 100) -> List[Post]:
|
||||
"""Получить список постов пользователя"""
|
||||
return self.db.query(Post).filter(Post.owner_id == owner_id)\
|
||||
.order_by(Post.created_at.desc())\
|
||||
.offset(skip)\
|
||||
.limit(limit)\
|
||||
.all()
|
||||
|
||||
def create_post(self, post_data: PostCreate, owner_id: int) -> Post:
|
||||
"""Создать новый пост"""
|
||||
db_post = Post(
|
||||
title=post_data.title,
|
||||
content=post_data.content,
|
||||
scheduled_time=post_data.scheduled_time,
|
||||
status=post_data.status,
|
||||
owner_id=owner_id
|
||||
)
|
||||
self.db.add(db_post)
|
||||
self.db.commit()
|
||||
self.db.refresh(db_post)
|
||||
return db_post
|
||||
|
||||
def update_post(self, post_id: int, post_data: PostUpdate, owner_id: int) -> Optional[Post]:
|
||||
"""Обновить пост"""
|
||||
db_post = self.get_post(post_id, owner_id)
|
||||
if not db_post:
|
||||
return None
|
||||
|
||||
# Обновляем только те поля, которые предоставлены
|
||||
update_data = post_data.dict(exclude_unset=True)
|
||||
|
||||
for field, value in update_data.items():
|
||||
setattr(db_post, field, value)
|
||||
|
||||
# Обновляем время последнего изменения
|
||||
db_post.updated_at = datetime.utcnow()
|
||||
|
||||
# Если статус меняется на published и published_at еще не установлен, устанавливаем его
|
||||
if post_data.status == "published" and not db_post.published_at:
|
||||
db_post.published_at = datetime.utcnow()
|
||||
|
||||
self.db.commit()
|
||||
self.db.refresh(db_post)
|
||||
return db_post
|
||||
|
||||
def delete_post(self, post_id: int, owner_id: int) -> bool:
|
||||
"""Удалить пост"""
|
||||
db_post = self.get_post(post_id, owner_id)
|
||||
if not db_post:
|
||||
return False
|
||||
|
||||
self.db.delete(db_post)
|
||||
self.db.commit()
|
||||
return True
|
||||
|
||||
def get_scheduled_posts(self) -> List[Post]:
|
||||
"""Получить посты, готовые к публикации (статус scheduled и время наступило)"""
|
||||
current_time = datetime.utcnow()
|
||||
return self.db.query(Post).filter(
|
||||
and_(
|
||||
Post.status == "scheduled",
|
||||
Post.scheduled_time <= current_time
|
||||
)
|
||||
).all()
|
||||
|
||||
def get_posts_by_status(self, owner_id: int, status: str) -> List[Post]:
|
||||
"""Получить посты по статусу"""
|
||||
return self.db.query(Post).filter(
|
||||
and_(Post.owner_id == owner_id, Post.status == status)
|
||||
).all()
|
||||
@@ -0,0 +1,48 @@
|
||||
from fastapi import FastAPI, Depends
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.v1.routers import api_router
|
||||
from app.database.database import engine, Base, get_db
|
||||
from app.database.config import settings
|
||||
|
||||
# Create database tables
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
# Initialize FastAPI app
|
||||
app = FastAPI(
|
||||
title="TelePost Clone API",
|
||||
description="API for TelePost Clone application",
|
||||
version="0.1.0"
|
||||
)
|
||||
|
||||
# Add CORS middleware
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"], # In production, replace with specific origins
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# Include API routes
|
||||
app.include_router(api_router, prefix="/api/v1", tags=["api"])
|
||||
|
||||
@app.get("/")
|
||||
def read_root():
|
||||
return {"message": "Welcome to TelePost Clone API"}
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
def health_check():
|
||||
return {"status": "healthy", "service": "TelePost Clone API"}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
uvicorn.run(
|
||||
"main:app",
|
||||
host="0.0.0.0",
|
||||
port=8000,
|
||||
reload=True # Only for development
|
||||
)
|
||||
@@ -0,0 +1,13 @@
|
||||
fastapi==0.104.1
|
||||
uvicorn==0.24.0
|
||||
sqlalchemy==2.0.23
|
||||
alembic==1.13.1
|
||||
psycopg2-binary==2.9.9
|
||||
python-jose[cryptography]==3.3.0
|
||||
passlib[bcrypt]==1.7.4
|
||||
python-multipart==0.0.6
|
||||
requests==2.31.0
|
||||
pydantic==2.5.0
|
||||
pydantic-settings==2.1.0
|
||||
redis==5.0.1
|
||||
cachetools==5.3.2
|
||||
Reference in New Issue
Block a user