Skip to content
This repository was archived by the owner on Feb 22, 2026. It is now read-only.
Merged
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
50 changes: 50 additions & 0 deletions py-be/app/api/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from datetime import timedelta
from fastapi import APIRouter
from app.core.config import Settings
from app.core.security import (create_access_token, verify_password)
from app.db.crud import (create_user, get_user_by_email,
get_user_by_username)
from app.services.base import get_db
from app.models.user import UserCreate
from app.models.user import UserResponse, Token
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
from sqlalchemy.orm import Session


router = APIRouter()


@router.post("/token", response_model=Token)
async def login_for_access_token(
form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)
):
user = get_user_by_username(db, form_data.username)
if not user or not verify_password(form_data.password, user.password):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)

access_token_expires = timedelta(minutes=Settings.ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user.username}, expires_delta=access_token_expires
)
return {"access_token": access_token, "token_type": "bearer"}


@router.post("/users", response_model=UserResponse)
async def create_new_user(user: UserCreate, db: Session = Depends(get_db)):
"""
Create a new user (registration endpoint)
"""
db_user = get_user_by_username(db, username=user.username)
if db_user:
raise HTTPException(status_code=400, detail="Username already registered")

db_user = get_user_by_email(db, email=user.email)
if db_user:
raise HTTPException(status_code=400, detail="Email already registered")

return create_user(db=db, user=user)
63 changes: 46 additions & 17 deletions py-be/app/api/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,25 @@

from datetime import datetime

from fastapi import Depends, FastAPI, Header, HTTPException, status
from pydantic import BaseModel, ConfigDict, Field, constr, field_validator

from fastapi import Depends, HTTPException, status , APIRouter
from typing import Annotated
from pydantic import BaseModel, ConfigDict, Field, field_validator, constr


from sqlalchemy import or_
from sqlalchemy.orm import Session

from ..models.base import init_db
from ..models.deployed_contracts import DeployedContract
from ..models.generated_contract import GeneratedContract
from ..models.user import User
from ..models.user import UserCreate , UserResponse
from ..models.base import User
from ..services.base import get_db
from app.core.security import get_current_user
from app.db.crud import get_user


app = FastAPI()


# Placeholder for authentication - In a real application, this would involve
Expand All @@ -31,13 +38,12 @@ async def verify_token(
class UserCreate(BaseModel):
"""Schema for incoming user registration data."""

username: constr(min_length=3)
email: str
password: constr(min_length=6)

@field_validator("email")
@classmethod
def validate_email(cls, v: str) -> str:
router = APIRouter()

@field_validator("email")
@classmethod
def validate_email(cls, v: str) -> str:
if "@" not in v or "." not in v.split("@")[-1]:
raise ValueError("Invalid email address")
return v
Expand All @@ -54,7 +60,7 @@ class UserRead(BaseModel):
# init_db() # Commented out to avoid database connection at import time


@app.post("/reg", response_model=UserRead, status_code=status.HTTP_201_CREATED)
@router.post("/reg", response_model=UserRead, status_code=status.HTTP_201_CREATED)
def register_user(user_in: UserCreate, db: Session = Depends(get_db)) -> User:
"""Register a new user."""

Expand All @@ -80,8 +86,8 @@ class GenerateContract(BaseModel):
"""Schema for contract generation requests."""

user_id: int
contract_type: constr(min_length=1)
contract_name: constr(min_length=1)
contract_type: Annotated[str, constr(min_length=1)]
contract_name: Annotated[str, constr(min_length=1)]
description: str | None = None
parameters: dict | None = None
template_id: str | None = None
Expand Down Expand Up @@ -117,11 +123,11 @@ class DeployedContractRead(BaseModel):
deployed_at: datetime


@app.post(

"/generate",
response_model=GeneratedContractRead,
status_code=status.HTTP_201_CREATED,
)

def generate_contract(
req: GenerateContract, db: Session = Depends(get_db)
) -> GeneratedContract:
Expand Down Expand Up @@ -176,7 +182,10 @@ def generate_contract(
return contract


@app.get("/generated_contracts", response_model=list[GeneratedContractRead])



@router.get("/generated_contracts", response_model=list[GeneratedContractRead])
def get_generated_contracts(
user_id: int | None = None,
skip: int = 0,
Expand All @@ -192,7 +201,8 @@ def get_generated_contracts(
return contracts


@app.get(
@router.get(
main
"/deployed_contracts",
response_model=list[DeployedContractRead],
status_code=status.HTTP_200_OK,
Expand Down Expand Up @@ -226,3 +236,22 @@ def get_deployed_contracts(
query = query.order_by(sort_column.asc())

return query.all()


@router.get("/user", response_model= UserResponse)
async def read_user_me(current_user: User = Depends(get_current_user)):
"""
Get current user details based on authentication token
"""
return current_user


@router.get("/user/{user_id}", response_model= UserResponse)
async def read_user(user_id: int, db: Session = Depends(get_db)):
"""
Get user by ID (admin functionality)
"""
db_user = get_user(db, user_id=user_id)
if db_user is None:
raise HTTPException(status_code=404, detail="User not found")
return db_user
11 changes: 11 additions & 0 deletions py-be/app/core/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from pydantic_settings import BaseSettings


class Settings(BaseSettings):
PROJECT_NAME: str = "User API"
SECRET_KEY: str = "your-secret-key-here" # Change in production
ALGORITHM: str = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30

class Config:
env_file = ".env"
55 changes: 55 additions & 0 deletions py-be/app/core/security.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from datetime import datetime, timedelta
from app.core.config import Settings
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
from passlib.context import CryptContext

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")


def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)


def get_password_hash(password):
return pwd_context.hash(password)


def create_access_token(data: dict, expires_delta: timedelta = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.now(datetime.timezone.utc) + expires_delta
else:
expire = datetime.now(datetime.timezone.utc) + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(
to_encode, Settings.SECRET_KEY, algorithm= Settings.ALGORITHM
)
return encoded_jwt


async def get_current_user(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(
token, Settings.SECRET_KEY, algorithms=[Settings.ALGORITHM]
)
username: str = payload.get("sub")
if username is None:
raise credentials_exception
except JWTError:
raise credentials_exception

# In a real app, you would fetch the user from the database here
from app.db.crud import get_user_by_username

user = get_user_by_username(username)
if user is None:
raise credentials_exception
return user
29 changes: 29 additions & 0 deletions py-be/app/db/crud.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from app.core.security import get_password_hash
from app.models.base import User
from app.models.user import UserCreate
from sqlalchemy.orm import Session


def get_user(db: Session, user_id: int):
return db.query(User).filter(User.id == user_id).first()


def get_user_by_username(db: Session, username: str):
return db.query(User).filter(User.username == username).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(
username=user.username,
email=user.email,
password=hashed_password,
)
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
24 changes: 22 additions & 2 deletions py-be/app/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,21 @@

import os

from dotenv import load_dotenv
from sqlalchemy import create_engine

from sqlalchemy import create_engine,Column, Integer, String
from sqlalchemy.orm import declarative_base, sessionmaker
from dotenv import load_dotenv


DATABASE_URL = os.environ.get(
"DATABASE_URL"
, "postgresql+psycopg2://postgres:test1234@localhost:5432/Starkfinder-test"
)


load_dotenv()


DATABASE_URL = os.getenv("DATABASE_URL")
if not DATABASE_URL:
raise ValueError("DATABASE_URL environment variable is not set.")
Expand All @@ -17,6 +26,17 @@

Base = declarative_base()

class User(Base):
"""SQLAlchemy model for a registered user."""

__tablename__ = "users"

id = Column(Integer, primary_key=True, index=True)
username = Column(String, unique=True, nullable=False, index=True)
email = Column(String, unique=True, nullable=False, index=True)
password = Column(String, nullable=False)



def init_db() -> None:
"""Create database tables."""
Expand Down
25 changes: 16 additions & 9 deletions py-be/app/models/user.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
"""User model definition."""
from pydantic import BaseModel, EmailStr

from sqlalchemy import Column, Integer, String

from .base import Base

class UserBase(BaseModel):
username: str
email: EmailStr

class User(Base):
"""SQLAlchemy model for a registered user."""
class UserCreate(UserBase):
password: str

__tablename__ = "users"
class UserResponse(UserBase):
id: int
class Config:
from_attributes = True



class Token(BaseModel):
access_token: str
token_type: str

id = Column(Integer, primary_key=True, index=True)
username = Column(String, unique=True, nullable=False, index=True)
email = Column(String, unique=True, nullable=False, index=True)
password = Column(String, nullable=False)
15 changes: 15 additions & 0 deletions py-be/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from fastapi import FastAPI
from app.api import routes
from app.api import routes, auth

app = FastAPI(title="StarkFinder_py-be")



@app.get("/")
def root():
return {"message": "πŸš€ FastAPI server is running!"}


app.include_router(routes.router, prefix="/api/routes", tags=["users"])
app.include_router(auth.router, prefix="/api/routes",)
Loading
Loading