From 925077eca12a890ef434503b3bbc76a738af78b8 Mon Sep 17 00:00:00 2001 From: Tiger Ren Date: Thu, 12 Dec 2024 17:49:37 +0800 Subject: [PATCH] Init commit --- alembic.ini | 0 alumni.db | Bin 0 -> 16384 bytes app/__init__.py | 0 app/api/__init__.py | 0 app/api/deps.py | 40 +++++++++++++++++++++++++ app/api/v1/__init__.py | 0 app/api/v1/api.py | 9 ++++++ app/api/v1/endpoints/auth.py | 0 app/api/v1/endpoints/messages.py | 0 app/api/v1/endpoints/users.py | 43 +++++++++++++++++++++++++++ app/api/v1/endpoints/verification.py | 0 app/core/__init__.py | 0 app/core/config.py | 11 +++++++ app/core/security.py | 23 ++++++++++++++ app/crud/__init__.py | 0 app/crud/message.py | 0 app/crud/user.py | 23 ++++++++++++++ app/crud/verification.py | 0 app/db/__init__.py | 0 app/db/init_db.py | 4 +++ app/db/session.py | 19 ++++++++++++ app/main.py | 22 ++++++++++++++ app/models/__init__.py | 3 ++ app/models/message.py | 0 app/models/user.py | 13 ++++++++ app/models/verification.py | 1 + app/schemas/__init__.py | 0 app/schemas/message.py | 0 app/schemas/user.py | 17 +++++++++++ app/schemas/verification.py | 0 requirements.txt | 13 ++++++++ run.py | 7 +++++ tests/__init__.py | 0 tests/test_auth.py | 0 34 files changed, 248 insertions(+) create mode 100644 alembic.ini create mode 100644 alumni.db create mode 100644 app/__init__.py create mode 100644 app/api/__init__.py create mode 100644 app/api/deps.py create mode 100644 app/api/v1/__init__.py create mode 100644 app/api/v1/api.py create mode 100644 app/api/v1/endpoints/auth.py create mode 100644 app/api/v1/endpoints/messages.py create mode 100644 app/api/v1/endpoints/users.py create mode 100644 app/api/v1/endpoints/verification.py create mode 100644 app/core/__init__.py create mode 100644 app/core/config.py create mode 100644 app/core/security.py create mode 100644 app/crud/__init__.py create mode 100644 app/crud/message.py create mode 100644 app/crud/user.py create mode 100644 app/crud/verification.py create mode 100644 app/db/__init__.py create mode 100644 app/db/init_db.py create mode 100644 app/db/session.py create mode 100644 app/main.py create mode 100644 app/models/__init__.py create mode 100644 app/models/message.py create mode 100644 app/models/user.py create mode 100644 app/models/verification.py create mode 100644 app/schemas/__init__.py create mode 100644 app/schemas/message.py create mode 100644 app/schemas/user.py create mode 100644 app/schemas/verification.py create mode 100644 requirements.txt create mode 100644 run.py create mode 100644 tests/__init__.py create mode 100644 tests/test_auth.py diff --git a/alembic.ini b/alembic.ini new file mode 100644 index 0000000..e69de29 diff --git a/alumni.db b/alumni.db new file mode 100644 index 0000000000000000000000000000000000000000..952fa72f36a37e2a333ce92694af29cabbc48461 GIT binary patch literal 16384 zcmeI#&q~8E90%}p9g`KD-g@w0z>5X(;tSZ#pu^g_+KIbZqRCuff28f^Jc>Ik~8gcH^pwqqOV_Z)J<)mR6at2BMT+w1?zrsFCJ_1t;6 zH8yP9HePo!em9mnkABov$s)JyvGNdBQ;GbP5j$le^@E7|ea}0fR`V} z@pL3b{K(VvX_g3D*&5Er<9Ny^vby+?a53kZnoZ+p$&>OWMJkoX3z?{)l7hB_z+ Generator: + try: + db = SessionLocal() + yield db + finally: + db.close() + +def get_current_user( + db: Session = Depends(get_db), + token: str = Depends(oauth2_scheme) +) -> User: + try: + payload = jwt.decode( + token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM] + ) + token_data = schemas.TokenPayload(**payload) + except (JWTError, ValidationError): + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Could not validate credentials", + ) + user = crud.user.get_user(db, user_id=token_data.sub) + if not user: + raise HTTPException(status_code=404, detail="User not found") + return user diff --git a/app/api/v1/__init__.py b/app/api/v1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/api/v1/api.py b/app/api/v1/api.py new file mode 100644 index 0000000..0cfc1d9 --- /dev/null +++ b/app/api/v1/api.py @@ -0,0 +1,9 @@ +from fastapi import APIRouter +from app.api.v1.endpoints import auth, users, messages, verification + +api_router = APIRouter() + +# api_router.include_router(auth.router, prefix="/auth", tags=["authentication"]) +api_router.include_router(users.router, prefix="/users", tags=["users"]) +# api_router.include_router(messages.router, prefix="/messages", tags=["messages"]) +# api_router.include_router(verification.router, prefix="/verification", tags=["verification"]) diff --git a/app/api/v1/endpoints/auth.py b/app/api/v1/endpoints/auth.py new file mode 100644 index 0000000..e69de29 diff --git a/app/api/v1/endpoints/messages.py b/app/api/v1/endpoints/messages.py new file mode 100644 index 0000000..e69de29 diff --git a/app/api/v1/endpoints/users.py b/app/api/v1/endpoints/users.py new file mode 100644 index 0000000..f77aae0 --- /dev/null +++ b/app/api/v1/endpoints/users.py @@ -0,0 +1,43 @@ +from typing import Any +from fastapi import APIRouter, Depends, HTTPException +from sqlalchemy.orm import Session +from app import crud +from app.api import deps +from app.schemas.user import User, UserCreate + +router = APIRouter() + +@router.get("/{user_id}", response_model=User) +def read_user( + user_id: int, + db: Session = Depends(deps.get_db), + current_user: User = Depends(deps.get_current_user) +) -> Any: + """ + Get user by ID. + """ + user = crud.user.get_user(db, user_id=user_id) + if user is None: + raise HTTPException( + status_code=404, + detail="User not found" + ) + return user + +@router.post("/", response_model=User) +def create_user( + *, + db: Session = Depends(deps.get_db), + user_in: UserCreate, +) -> Any: + """ + Create new user. + """ + user = crud.user.get_user_by_email(db, email=user_in.email) + if user: + raise HTTPException( + status_code=400, + detail="Email already registered" + ) + user = crud.user.create_user(db, user=user_in) + return user diff --git a/app/api/v1/endpoints/verification.py b/app/api/v1/endpoints/verification.py new file mode 100644 index 0000000..e69de29 diff --git a/app/core/__init__.py b/app/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/core/config.py b/app/core/config.py new file mode 100644 index 0000000..540c292 --- /dev/null +++ b/app/core/config.py @@ -0,0 +1,11 @@ +from pydantic_settings import BaseSettings + +class Settings(BaseSettings): + SECRET_KEY: str = "your-secret-key-here" # Change in production! + ALGORITHM: str = "HS256" + ACCESS_TOKEN_EXPIRE_MINUTES: int = 30 + + class Config: + env_file = ".env" + +settings = Settings() diff --git a/app/core/security.py b/app/core/security.py new file mode 100644 index 0000000..078a3bb --- /dev/null +++ b/app/core/security.py @@ -0,0 +1,23 @@ +from datetime import datetime, timedelta +from typing import Optional +from jose import JWTError, jwt +from passlib.context import CryptContext +from app.core.config import settings + +pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") + +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) + +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}) + encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM) + return encoded_jwt diff --git a/app/crud/__init__.py b/app/crud/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/crud/message.py b/app/crud/message.py new file mode 100644 index 0000000..e69de29 diff --git a/app/crud/user.py b/app/crud/user.py new file mode 100644 index 0000000..ddb0077 --- /dev/null +++ b/app/crud/user.py @@ -0,0 +1,23 @@ +from sqlalchemy.orm import Session +from app.models.user import User +from app.schemas.user import UserCreate +from app.core.security import get_password_hash + +def get_user(db: Session, user_id: int): + return db.query(User).filter(User.id == user_id).first() + +def get_user_by_email(db: Session, email: str): + return db.query(User).filter(User.email == email).first() + +def create_user(db: Session, user: UserCreate): + hashed_password = get_password_hash(user.password) + db_user = User( + email=user.email, + hashed_password=hashed_password, + full_name=user.full_name, + graduation_year=user.graduation_year + ) + db.add(db_user) + db.commit() + db.refresh(db_user) + return db_user diff --git a/app/crud/verification.py b/app/crud/verification.py new file mode 100644 index 0000000..e69de29 diff --git a/app/db/__init__.py b/app/db/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/db/init_db.py b/app/db/init_db.py new file mode 100644 index 0000000..e5fee27 --- /dev/null +++ b/app/db/init_db.py @@ -0,0 +1,4 @@ +from app.db.session import Base, engine + +def init_db(): + Base.metadata.create_all(bind=engine) diff --git a/app/db/session.py b/app/db/session.py new file mode 100644 index 0000000..d0b7184 --- /dev/null +++ b/app/db/session.py @@ -0,0 +1,19 @@ +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker, declarative_base + +SQLALCHEMY_DATABASE_URL = "sqlite:///./alumni.db" + +engine = create_engine( + SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} +) +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + +Base = declarative_base() + +# Dependency +def get_db(): + db = SessionLocal() + try: + yield db + finally: + db.close() diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..c565425 --- /dev/null +++ b/app/main.py @@ -0,0 +1,22 @@ +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +from app.api.v1.api import api_router +from app.core.config import settings + +app = FastAPI( + title="Alumni API", + description="API for Alumni Network", + version="1.0.0" +) + +# CORS middleware +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], # Modify in production + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# Include API router +app.include_router(api_router, prefix="/api/v1") diff --git a/app/models/__init__.py b/app/models/__init__.py new file mode 100644 index 0000000..b2e47e8 --- /dev/null +++ b/app/models/__init__.py @@ -0,0 +1,3 @@ +from app.models.user import User + +__all__ = ["User"] diff --git a/app/models/message.py b/app/models/message.py new file mode 100644 index 0000000..e69de29 diff --git a/app/models/user.py b/app/models/user.py new file mode 100644 index 0000000..98f3ea9 --- /dev/null +++ b/app/models/user.py @@ -0,0 +1,13 @@ +from sqlalchemy import Boolean, Column, Integer, String +from app.db.session import Base + +class User(Base): + __tablename__ = "users" + + id = Column(Integer, primary_key=True, index=True) + email = Column(String, unique=True, index=True) + hashed_password = Column(String) + full_name = Column(String) + graduation_year = Column(Integer) + is_verified = Column(Boolean, default=False) + is_active = Column(Boolean, default=True) diff --git a/app/models/verification.py b/app/models/verification.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/app/models/verification.py @@ -0,0 +1 @@ + diff --git a/app/schemas/__init__.py b/app/schemas/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/schemas/message.py b/app/schemas/message.py new file mode 100644 index 0000000..e69de29 diff --git a/app/schemas/user.py b/app/schemas/user.py new file mode 100644 index 0000000..ad575c9 --- /dev/null +++ b/app/schemas/user.py @@ -0,0 +1,17 @@ +from pydantic import BaseModel, EmailStr + +class UserBase(BaseModel): + email: EmailStr + full_name: str + graduation_year: int + +class UserCreate(UserBase): + password: str + +class User(UserBase): + id: int + is_verified: bool + is_active: bool + + class Config: + from_attributes = True diff --git a/app/schemas/verification.py b/app/schemas/verification.py new file mode 100644 index 0000000..e69de29 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..81e94ad --- /dev/null +++ b/requirements.txt @@ -0,0 +1,13 @@ +fastapi>=0.68.0 +uvicorn>=0.15.0 +sqlalchemy>=1.4.23 +pydantic>=1.8.2 +python-jose[cryptography]>=3.3.0 +passlib[bcrypt]>=1.7.4 +python-multipart>=0.0.5 +alembic>=1.7.1 +pytest>=6.2.5 +httpx>=0.19.0 +python-dotenv>=0.19.0 +pydantic-settings +pydantic[email] \ No newline at end of file diff --git a/run.py b/run.py new file mode 100644 index 0000000..2a957af --- /dev/null +++ b/run.py @@ -0,0 +1,7 @@ +import uvicorn +from app.main import app +from app.db.init_db import init_db + +if __name__ == "__main__": + init_db() # Initialize database tables + uvicorn.run("run:app", host="0.0.0.0", port=8000, reload=True) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_auth.py b/tests/test_auth.py new file mode 100644 index 0000000..e69de29