Initial commit from template

This commit is contained in:
Backend IM Bot 2025-05-14 15:25:47 +00:00
commit e31e8348f6
18 changed files with 371 additions and 0 deletions

27
alembic.ini Normal file
View File

@ -0,0 +1,27 @@
[alembic]
script_location = alembic
sqlalchemy.url = sqlite:///./storage/db/db.sqlite
[loggers]
keys = root
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

48
alembic/env.py Normal file
View File

@ -0,0 +1,48 @@
from logging.config import fileConfig
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from alembic import context
import sys
import os
# Add project root to Python path
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
from core.database import Base, SQLALCHEMY_DATABASE_URL
from models.user import User # Import all models
config = context.config
fileConfig(config.config_file_name)
config.set_main_option('sqlalchemy.url', SQLALCHEMY_DATABASE_URL)
target_metadata = Base.metadata
def run_migrations_offline():
context.configure(
url=SQLALCHEMY_DATABASE_URL,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online():
connectable = engine_from_config(
{"sqlalchemy.url": SQLALCHEMY_DATABASE_URL},
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=target_metadata,
compare_type=True
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

22
alembic/script.py.mako Normal file
View File

@ -0,0 +1,22 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}
def upgrade():
${upgrades if upgrades else "pass"}
def downgrade():
${downgrades if downgrades else "pass"}

View File

@ -0,0 +1,31 @@
"""Initial migration
Revision ID: 0001
Revises:
Create Date: 2024-01-01 00:00:00.000000
"""
from alembic import op
import sqlalchemy as sa
revision = '0001'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
op.create_table('users',
sa.Column('id', sa.String(), nullable=False),
sa.Column('username', sa.String(), nullable=False),
sa.Column('email', sa.String(), nullable=False),
sa.Column('hashed_password', sa.String(), nullable=False),
sa.Column('disabled', sa.Boolean(), server_default='false', nullable=False),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_users_email'), 'users', ['email'], unique=True)
op.create_index(op.f('ix_users_username'), 'users', ['username'], unique=True)
def downgrade():
op.drop_index(op.f('ix_users_username'), table_name='users')
op.drop_index(op.f('ix_users_email'), table_name='users')
op.drop_table('users')

0
core/__init__.py Normal file
View File

53
core/auth.py Normal file
View File

@ -0,0 +1,53 @@
from fastapi import Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
from datetime import datetime, timedelta
from passlib.context import CryptContext
from models.user import User
from core.database import get_db
from sqlalchemy.orm import Session
from typing import Optional
# OAuth2 scheme
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="login")
# Password hashing
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
SECRET_KEY = "demo-secret-key"
ALGORITHM = "HS256"
def get_password_hash(password: str):
return pwd_context.hash(password)
def verify_password(plain_password: str, hashed_password: str):
return pwd_context.verify(plain_password, hashed_password)
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=15)
to_encode.update({"exp": expire})
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
async def get_current_user_demo(
token: str = Depends(oauth2_scheme),
db: Session = Depends(get_db)
):
credentials_exception = HTTPException(
status_code=401,
detail="Could not validate credentials"
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
user_id: str = payload.get("sub")
if user_id is None:
raise credentials_exception
except JWTError:
raise credentials_exception
user = db.query(User).filter(User.id == user_id).first()
if not user:
raise credentials_exception
return user

25
core/database.py Normal file
View File

@ -0,0 +1,25 @@
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, Session
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent.parent
DB_DIR = BASE_DIR / "storage" / "db"
DB_DIR.mkdir(parents=True, exist_ok=True)
SQLALCHEMY_DATABASE_URL = f"sqlite:///{DB_DIR}/db.sqlite"
engine = create_engine(
SQLALCHEMY_DATABASE_URL,
connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()

18
core/router.py Normal file
View File

@ -0,0 +1,18 @@
import importlib.util
from pathlib import Path
from fastapi import APIRouter
def load_endpoints(base_path: Path = Path("endpoints")) -> APIRouter:
router = APIRouter()
for file_path in base_path.glob("**/*.*.py"):
# Load the module
spec = importlib.util.spec_from_file_location("", file_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
# Find the router in the module and include it directly
if hasattr(module, "router"):
router.include_router(module.router)
return router

0
endpoints/__init__.py Normal file
View File

11
endpoints/health.get.py Normal file
View File

@ -0,0 +1,11 @@
from fastapi import APIRouter
router = APIRouter()
@router.get("/health")
async def health_check():
"""Health check endpoint"""
return {
"status": "ok",
"message": "Service is healthy"
}

37
endpoints/login.post.py Normal file
View File

@ -0,0 +1,37 @@
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from datetime import timedelta
from core.database import get_db
from sqlalchemy.orm import Session
from core.auth import verify_password, create_access_token
from models.user import User
router = APIRouter()
class UserAuth(BaseModel):
username: str
password: str
@router.post("/login")
async def login(
user_data: UserAuth,
db: Session = Depends(get_db)
):
"""User authentication endpoint"""
user = db.query(User).filter(User.username == user_data.username).first()
if not user or not verify_password(user_data.password, user.hashed_password):
raise HTTPException(status_code=400, detail="Invalid credentials")
# Generate token with expiration
access_token = create_access_token(
data={"sub": user.id},
expires_delta=timedelta(hours=1)
)
return {
"access_token": access_token,
"token_type": "bearer",
"user_id": user.id,
"username": user.username
}

50
endpoints/signup.post.py Normal file
View File

@ -0,0 +1,50 @@
from fastapi import APIRouter, HTTPException, Depends
from sqlalchemy.orm import Session
from pydantic import BaseModel
from core.database import get_db
from core.auth import get_password_hash, create_access_token
import uuid
from models.user import User
router = APIRouter()
class UserCreate(BaseModel):
username: str
email: str
password: str
@router.post("/signup")
async def signup(
user_data: UserCreate,
db: Session = Depends(get_db)
):
"""User registration endpoint"""
# Check existing user
db_user = db.query(User).filter(
(User.username == user_data.username) |
(User.email == user_data.email)
).first()
if db_user:
raise HTTPException(
status_code=400,
detail="Username or email already exists"
)
# Create new user
new_user = User(
id=str(uuid.uuid4()),
username=user_data.username,
email=user_data.email,
hashed_password=get_password_hash(user_data.password)
)
db.add(new_user)
db.commit()
# Return token directly after registration
return {
"message": "User created successfully",
"access_token": create_access_token({"sub": new_user.id}),
"token_type": "bearer"
}

0
helpers/__init__.py Normal file
View File

29
main.py Normal file
View File

@ -0,0 +1,29 @@
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware # New import
from pathlib import Path
from core.router import load_endpoints
app = FastAPI(title="API by BackendIM")
# Add CORS middleware configuration
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # Allows all origins
allow_credentials=True,
allow_methods=["*"], # Allows all methods
allow_headers=["*"], # Allows all headers
)
# Load all endpoints
app.include_router(load_endpoints(Path("endpoints")))
@app.get("/")
def root():
return {
"message": "FastAPI App Running",
"endpoints": {
"health": "/health (GET)",
"login": "/login (POST)",
"signup": "/signup (POST)"
}
}

0
models/__init__.py Normal file
View File

11
models/user.py Normal file
View File

@ -0,0 +1,11 @@
from sqlalchemy import Column, String, Boolean
from core.database import Base
class User(Base):
__tablename__ = "users"
id = Column(String, primary_key=True)
username = Column(String, unique=True, index=True)
email = Column(String, unique=True, index=True)
hashed_password = Column(String)
disabled = Column(Boolean, default=False)

9
requirements.txt Normal file
View File

@ -0,0 +1,9 @@
fastapi>=0.68.0
uvicorn[standard]
python-multipart
python-jose[cryptography]
passlib[bcrypt]
sqlalchemy>=1.4.0
python-dotenv>=0.19.0
bcrypt>=3.2.0
alembic>=1.13.1

0
schemas/__init__.py Normal file
View File