the beginning of the idiots

This commit is contained in:
2025-10-24 14:51:13 -05:00
parent 0b377030c6
commit cb06217ef7
123 changed files with 10279 additions and 0 deletions

View File

@@ -0,0 +1,135 @@
"""
Applications API routes
"""
from fastapi import APIRouter, Depends, HTTPException, status
from typing import List
from pydantic import BaseModel
from sqlalchemy.orm import Session
from ..database import SessionLocal
from ..models import Application, ApplicationStatus, User, JobPosting, Resume
from ..config.settings import settings
router = APIRouter()
# Pydantic models for applications
class ApplicationCreate(BaseModel):
job_posting_id: int
resume_id: int
cover_letter: str = None
class ApplicationUpdate(BaseModel):
status: ApplicationStatus = None
cover_letter: str = None
class ApplicationResponse(BaseModel):
id: int
user_id: int
job_posting_id: int
resume_id: int
cover_letter: str
status: str
created_at: str
class Config:
from_attributes = True
@router.get("/", response_model=List[ApplicationResponse])
async def get_applications(skip: int = 0, limit: int = 100):
"""Get all applications"""
db = SessionLocal()
try:
applications = db.query(Application).offset(skip).limit(limit).all()
return applications
finally:
db.close()
@router.get("/{application_id}", response_model=ApplicationResponse)
async def get_application(application_id: int):
"""Get a specific application"""
db = SessionLocal()
try:
application = db.query(Application).filter(Application.id == application_id).first()
if not application:
raise HTTPException(status_code=404, detail="Application not found")
return application
finally:
db.close()
@router.post("/", response_model=ApplicationResponse)
async def create_application(application: ApplicationCreate, user_id: int = 1): # In real app, get from auth context
"""Create a new job application"""
db = SessionLocal()
try:
# Verify user exists and has permission to apply
user = db.query(User).filter(User.id == user_id).first()
if not user or user.role != "job_seeker":
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Only job seekers can apply for jobs"
)
# Verify job posting exists and is active
job_posting = db.query(JobPosting).filter(
JobPosting.id == application.job_posting_id,
JobPosting.is_active == True
).first()
if not job_posting:
raise HTTPException(status_code=404, detail="Job posting not found or inactive")
# Verify resume exists and belongs to user
resume = db.query(Resume).filter(
Resume.id == application.resume_id,
Resume.user_id == user_id
).first()
if not resume:
raise HTTPException(status_code=404, detail="Resume not found")
db_application = Application(
user_id=user_id,
job_posting_id=application.job_posting_id,
resume_id=application.resume_id,
cover_letter=application.cover_letter
)
db.add(db_application)
db.commit()
db.refresh(db_application)
return db_application
finally:
db.close()
@router.put("/{application_id}", response_model=ApplicationResponse)
async def update_application(application_id: int, app_update: ApplicationUpdate):
"""Update an application"""
db = SessionLocal()
try:
db_application = db.query(Application).filter(Application.id == application_id).first()
if not db_application:
raise HTTPException(status_code=404, detail="Application not found")
# Update fields if provided
if app_update.status is not None:
db_application.status = app_update.status.value
if app_update.cover_letter is not None:
db_application.cover_letter = app_update.cover_letter
db.commit()
db.refresh(db_application)
return db_application
finally:
db.close()
@router.delete("/{application_id}")
async def delete_application(application_id: int):
"""Delete an application"""
db = SessionLocal()
try:
db_application = db.query(Application).filter(Application.id == application_id).first()
if not db_application:
raise HTTPException(status_code=404, detail="Application not found")
db.delete(db_application)
db.commit()
return {"message": "Application deleted successfully"}
finally:
db.close()

View File

@@ -0,0 +1,73 @@
"""
Authentication API routes
"""
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import HTTPBearer
from datetime import datetime, timedelta
from typing import Optional
import jwt
from pydantic import BaseModel
from sqlalchemy.orm import Session
from ..config.settings import settings
from ..database import SessionLocal
from ..models import User
router = APIRouter()
security = HTTPBearer()
# Pydantic models for auth
class Token(BaseModel):
access_token: str
token_type: str
class TokenData(BaseModel):
username: Optional[str] = None
tenant_id: Optional[str] = None
class UserLogin(BaseModel):
username: str
password: str
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
@router.post("/token", response_model=Token)
async def login_for_access_token(form_data: UserLogin, db: Session = Depends(SessionLocal)):
# This is a simplified version - in a real app, you'd hash passwords
user = db.query(User).filter(User.username == form_data.username).first()
if not user or user.hashed_password != form_data.password: # Simplified check
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
if not user.is_active:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Inactive user",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user.username, "tenant_id": user.tenant_id},
expires_delta=access_token_expires
)
return {"access_token": access_token, "token_type": "bearer"}
@router.get("/oidc-config")
async def get_oidc_config():
"""Get OIDC configuration"""
return {
"issuer": settings.OIDC_ISSUER,
"client_id": settings.OIDC_CLIENT_ID,
"redirect_uri": settings.OIDC_REDIRECT_URI
}

View File

@@ -0,0 +1,121 @@
"""
Jobs API routes
"""
from fastapi import APIRouter, Depends, HTTPException, status
from typing import List
from pydantic import BaseModel
from sqlalchemy.orm import Session
from ..database import SessionLocal
from ..models import JobPosting, User
from ..config.settings import settings
router = APIRouter()
# Pydantic models for jobs
class JobCreate(BaseModel):
title: str
description: str
requirements: str
location: str = None
salary_min: int = None
salary_max: int = None
is_remote: bool = False
class JobUpdate(BaseModel):
title: str = None
description: str = None
requirements: str = None
location: str = None
salary_min: int = None
salary_max: int = None
is_active: bool = None
is_remote: bool = None
class JobResponse(BaseModel):
id: int
title: str
description: str
requirements: str
location: str
salary_min: int
salary_max: int
is_active: bool
is_remote: bool
tenant_id: int
created_by_user_id: int
class Config:
from_attributes = True
@router.get("/", response_model=List[JobResponse])
async def get_jobs(skip: int = 0, limit: int = 100, is_active: bool = True, db: Session = Depends(SessionLocal)):
"""Get all jobs"""
query = db.query(JobPosting)
if is_active is not None:
query = query.filter(JobPosting.is_active == is_active)
jobs = query.offset(skip).limit(limit).all()
return jobs
@router.get("/{job_id}", response_model=JobResponse)
async def get_job(job_id: int, db: Session = Depends(SessionLocal)):
"""Get a specific job"""
job = db.query(JobPosting).filter(JobPosting.id == job_id).first()
if not job:
raise HTTPException(status_code=404, detail="Job not found")
if not job.is_active:
raise HTTPException(status_code=404, detail="Job not found")
return job
@router.post("/", response_model=JobResponse)
async def create_job(job: JobCreate, db: Session = Depends(SessionLocal), user_id: int = 1): # In real app, get from auth context
"""Create a new job posting"""
# Verify user exists and has permission to create job postings
user = db.query(User).filter(User.id == user_id).first()
if not user or user.role not in ["job_provider", "admin"]:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Not authorized to create job postings"
)
db_job = JobPosting(
title=job.title,
description=job.description,
requirements=job.requirements,
location=job.location,
salary_min=job.salary_min,
salary_max=job.salary_max,
is_remote=job.is_remote,
tenant_id=user.tenant_id, # Use user's tenant
created_by_user_id=user_id
)
db.add(db_job)
db.commit()
db.refresh(db_job)
return db_job
@router.put("/{job_id}", response_model=JobResponse)
async def update_job(job_id: int, job_update: JobUpdate, db: Session = Depends(SessionLocal)):
"""Update a job posting"""
db_job = db.query(JobPosting).filter(JobPosting.id == job_id).first()
if not db_job:
raise HTTPException(status_code=404, detail="Job not found")
# Update fields if provided
for field, value in job_update.model_dump(exclude_unset=True).items():
setattr(db_job, field, value)
db.commit()
db.refresh(db_job)
return db_job
@router.delete("/{job_id}")
async def delete_job(job_id: int, db: Session = Depends(SessionLocal)):
"""Delete a job posting (soft delete by setting is_active to False)"""
db_job = db.query(JobPosting).filter(JobPosting.id == job_id).first()
if not db_job:
raise HTTPException(status_code=404, detail="Job not found")
db_job.is_active = False
db.commit()
return {"message": "Job deactivated successfully"}

View File

@@ -0,0 +1,18 @@
"""
API v1 router
"""
from fastapi import APIRouter
from .auth import router as auth_router
from .tenants import router as tenants_router
from .users import router as users_router
from .jobs import router as jobs_router
from .applications import router as applications_router
api_router = APIRouter()
# Include all API routes
api_router.include_router(auth_router, prefix="/auth", tags=["Authentication"])
api_router.include_router(tenants_router, prefix="/tenants", tags=["Tenants"])
api_router.include_router(users_router, prefix="/users", tags=["Users"])
api_router.include_router(jobs_router, prefix="/jobs", tags=["Jobs"])
api_router.include_router(applications_router, prefix="/applications", tags=["Applications"])

View File

@@ -0,0 +1,53 @@
"""
Tenants API routes
"""
from fastapi import APIRouter, Depends, HTTPException, status
from typing import List
from pydantic import BaseModel
from sqlalchemy.orm import Session
from ..database import SessionLocal
from ..models import Tenant
from ..config.settings import settings
router = APIRouter()
# Pydantic models for tenants
class TenantCreate(BaseModel):
name: str
subdomain: str
class TenantResponse(BaseModel):
id: int
name: str
subdomain: str
is_active: bool
class Config:
from_attributes = True
@router.get("/", response_model=List[TenantResponse])
async def get_tenants(skip: int = 0, limit: int = 100, db: Session = Depends(SessionLocal)):
"""Get all tenants"""
tenants = db.query(Tenant).offset(skip).limit(limit).all()
return tenants
@router.get("/{tenant_id}", response_model=TenantResponse)
async def get_tenant(tenant_id: int, db: Session = Depends(SessionLocal)):
"""Get a specific tenant"""
tenant = db.query(Tenant).filter(Tenant.id == tenant_id).first()
if not tenant:
raise HTTPException(status_code=404, detail="Tenant not found")
return tenant
@router.post("/", response_model=TenantResponse)
async def create_tenant(tenant: TenantCreate, db: Session = Depends(SessionLocal)):
"""Create a new tenant"""
if not settings.MULTI_TENANT_ENABLED:
raise HTTPException(status_code=400, detail="Multi-tenant is not enabled")
db_tenant = Tenant(**tenant.model_dump())
db.add(db_tenant)
db.commit()
db.refresh(db_tenant)
return db_tenant

View File

@@ -0,0 +1,111 @@
"""
Users API routes
"""
from fastapi import APIRouter, Depends, HTTPException, status
from typing import List
from pydantic import BaseModel
import hashlib
from sqlalchemy.orm import Session
from ..database import SessionLocal
from ..models import User, UserRole
from ..config.settings import settings
router = APIRouter()
# Pydantic models for users
class UserCreate(BaseModel):
email: str
username: str
password: str
role: UserRole
class UserUpdate(BaseModel):
email: str = None
username: str = None
is_active: bool = None
class UserResponse(BaseModel):
id: int
email: str
username: str
role: str
is_active: bool
is_verified: bool
tenant_id: int
class Config:
from_attributes = True
def hash_password(password: str) -> str:
"""Hash password using SHA256 (in production, use bcrypt)"""
return hashlib.sha256(password.encode()).hexdigest()
@router.get("/", response_model=List[UserResponse])
async def get_users(skip: int = 0, limit: int = 100, db: Session = Depends(SessionLocal)):
"""Get all users"""
users = db.query(User).offset(skip).limit(limit).all()
return users
@router.get("/{user_id}", response_model=UserResponse)
async def get_user(user_id: int, db: Session = Depends(SessionLocal)):
"""Get a specific user"""
user = db.query(User).filter(User.id == user_id).first()
if not user:
raise HTTPException(status_code=404, detail="User not found")
return user
@router.post("/", response_model=UserResponse)
async def create_user(user: UserCreate, db: Session = Depends(SessionLocal)):
"""Create a new user"""
# Check if user already exists
existing_user = db.query(User).filter(
(User.email == user.email) | (User.username == user.username)
).first()
if existing_user:
raise HTTPException(status_code=400, detail="Email or username already registered")
# Create new user
hashed_pwd = hash_password(user.password)
db_user = User(
email=user.email,
username=user.username,
hashed_password=hashed_pwd,
role=user.role.value,
tenant_id=1 # Default tenant, in real app would come from context
)
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
@router.put("/{user_id}", response_model=UserResponse)
async def update_user(user_id: int, user_update: UserUpdate, db: Session = Depends(SessionLocal)):
"""Update a user"""
db_user = db.query(User).filter(User.id == user_id).first()
if not db_user:
raise HTTPException(status_code=404, detail="User not found")
# Update fields if provided
if user_update.email is not None:
db_user.email = user_update.email
if user_update.username is not None:
db_user.username = user_update.username
if user_update.is_active is not None:
db_user.is_active = user_update.is_active
db.commit()
db.refresh(db_user)
return db_user
@router.delete("/{user_id}")
async def delete_user(user_id: int, db: Session = Depends(SessionLocal)):
"""Delete a user"""
db_user = db.query(User).filter(User.id == user_id).first()
if not db_user:
raise HTTPException(status_code=404, detail="User not found")
db.delete(db_user)
db.commit()
return {"message": "User deleted successfully"}