the middle of the idiots
This commit is contained in:
		@@ -6,9 +6,9 @@ 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
 | 
			
		||||
from ...database import SessionLocal
 | 
			
		||||
from ...models import Application, ApplicationStatus, User, JobPosting, Resume
 | 
			
		||||
from ...config.settings import settings
 | 
			
		||||
 | 
			
		||||
router = APIRouter()
 | 
			
		||||
 | 
			
		||||
@@ -33,6 +33,17 @@ class ApplicationResponse(BaseModel):
 | 
			
		||||
 | 
			
		||||
    class Config:
 | 
			
		||||
        from_attributes = True
 | 
			
		||||
        json_schema_extra = {
 | 
			
		||||
            "example": {
 | 
			
		||||
                "id": 1,
 | 
			
		||||
                "user_id": 1,
 | 
			
		||||
                "job_posting_id": 1,
 | 
			
		||||
                "resume_id": 1,
 | 
			
		||||
                "cover_letter": "I am excited to apply for this position...",
 | 
			
		||||
                "status": "submitted",
 | 
			
		||||
                "created_at": "2023-10-24T10:00:00Z"
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@router.get("/", response_model=List[ApplicationResponse])
 | 
			
		||||
async def get_applications(skip: int = 0, limit: int = 100, db: Session = Depends(SessionLocal), request: Request = None):
 | 
			
		||||
@@ -113,9 +124,18 @@ async def create_application(application: ApplicationCreate, db: Session = Depen
 | 
			
		||||
    return db_application
 | 
			
		||||
 | 
			
		||||
@router.put("/{application_id}", response_model=ApplicationResponse)
 | 
			
		||||
async def update_application(application_id: int, app_update: ApplicationUpdate, db: Session = Depends(SessionLocal)):
 | 
			
		||||
async def update_application(application_id: int, app_update: ApplicationUpdate, db: Session = Depends(SessionLocal), request: Request = None):
 | 
			
		||||
    """Update an application"""
 | 
			
		||||
    db_application = db.query(Application).filter(Application.id == application_id).first()
 | 
			
		||||
    tenant_id = getattr(request.state, 'tenant_id', None)
 | 
			
		||||
    if not tenant_id and settings.MULTI_TENANT_ENABLED:
 | 
			
		||||
        raise HTTPException(status_code=400, detail="Tenant ID is required")
 | 
			
		||||
    
 | 
			
		||||
    db_application = db.query(Application).join(JobPosting).filter(
 | 
			
		||||
        Application.id == application_id,
 | 
			
		||||
        (JobPosting.tenant_id == tenant_id) | (Application.user_id.in_(
 | 
			
		||||
            db.query(User.id).filter(User.tenant_id == tenant_id)
 | 
			
		||||
        ))
 | 
			
		||||
    ).first()
 | 
			
		||||
    if not db_application:
 | 
			
		||||
        raise HTTPException(status_code=404, detail="Application not found")
 | 
			
		||||
    
 | 
			
		||||
@@ -130,9 +150,18 @@ async def update_application(application_id: int, app_update: ApplicationUpdate,
 | 
			
		||||
    return db_application
 | 
			
		||||
 | 
			
		||||
@router.delete("/{application_id}")
 | 
			
		||||
async def delete_application(application_id: int, db: Session = Depends(SessionLocal)):
 | 
			
		||||
async def delete_application(application_id: int, db: Session = Depends(SessionLocal), request: Request = None):
 | 
			
		||||
    """Delete an application"""
 | 
			
		||||
    db_application = db.query(Application).filter(Application.id == application_id).first()
 | 
			
		||||
    tenant_id = getattr(request.state, 'tenant_id', None)
 | 
			
		||||
    if not tenant_id and settings.MULTI_TENANT_ENABLED:
 | 
			
		||||
        raise HTTPException(status_code=400, detail="Tenant ID is required")
 | 
			
		||||
    
 | 
			
		||||
    db_application = db.query(Application).join(JobPosting).filter(
 | 
			
		||||
        Application.id == application_id,
 | 
			
		||||
        (JobPosting.tenant_id == tenant_id) | (Application.user_id.in_(
 | 
			
		||||
            db.query(User.id).filter(User.tenant_id == tenant_id)
 | 
			
		||||
        ))
 | 
			
		||||
    ).first()
 | 
			
		||||
    if not db_application:
 | 
			
		||||
        raise HTTPException(status_code=404, detail="Application not found")
 | 
			
		||||
    
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,18 @@
 | 
			
		||||
"""
 | 
			
		||||
Authentication API routes
 | 
			
		||||
"""
 | 
			
		||||
from fastapi import APIRouter, Depends, HTTPException, status
 | 
			
		||||
from fastapi import APIRouter, Depends, HTTPException, status, Request
 | 
			
		||||
from fastapi.security import HTTPBearer
 | 
			
		||||
from datetime import datetime, timedelta
 | 
			
		||||
from typing import Optional
 | 
			
		||||
import jwt
 | 
			
		||||
from jose import jwt
 | 
			
		||||
from pydantic import BaseModel
 | 
			
		||||
from sqlalchemy.orm import Session
 | 
			
		||||
 | 
			
		||||
from ..config.settings import settings
 | 
			
		||||
from ..database import SessionLocal
 | 
			
		||||
from ..models import User
 | 
			
		||||
from ...config.settings import settings
 | 
			
		||||
from ...database import SessionLocal
 | 
			
		||||
from ...models import User
 | 
			
		||||
from ...services.auth_service import authenticate_user, create_access_token, get_oidc_config, handle_oidc_callback
 | 
			
		||||
 | 
			
		||||
router = APIRouter()
 | 
			
		||||
security = HTTPBearer()
 | 
			
		||||
@@ -21,6 +22,14 @@ class Token(BaseModel):
 | 
			
		||||
    access_token: str
 | 
			
		||||
    token_type: str
 | 
			
		||||
 | 
			
		||||
    class Config:
 | 
			
		||||
        json_schema_extra = {
 | 
			
		||||
            "example": {
 | 
			
		||||
                "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
 | 
			
		||||
                "token_type": "bearer"
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
class TokenData(BaseModel):
 | 
			
		||||
    username: Optional[str] = None
 | 
			
		||||
    tenant_id: Optional[str] = None
 | 
			
		||||
@@ -29,6 +38,14 @@ class UserLogin(BaseModel):
 | 
			
		||||
    username: str
 | 
			
		||||
    password: str
 | 
			
		||||
 | 
			
		||||
    class Config:
 | 
			
		||||
        json_schema_extra = {
 | 
			
		||||
            "example": {
 | 
			
		||||
                "username": "johndoe",
 | 
			
		||||
                "password": "securepassword"
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
 | 
			
		||||
    to_encode = data.copy()
 | 
			
		||||
    if expires_delta:
 | 
			
		||||
@@ -41,20 +58,13 @@ def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
 | 
			
		||||
 | 
			
		||||
@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
 | 
			
		||||
    user = authenticate_user(db, form_data.username, form_data.password)
 | 
			
		||||
    if not user:
 | 
			
		||||
        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(
 | 
			
		||||
@@ -64,10 +74,91 @@ async def login_for_access_token(form_data: UserLogin, db: Session = Depends(Ses
 | 
			
		||||
    return {"access_token": access_token, "token_type": "bearer"}
 | 
			
		||||
 | 
			
		||||
@router.get("/oidc-config")
 | 
			
		||||
async def get_oidc_config():
 | 
			
		||||
async def get_oidc_config_endpoint():
 | 
			
		||||
    """Get OIDC configuration"""
 | 
			
		||||
    return await get_oidc_config()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@router.get("/oidc-login")
 | 
			
		||||
async def oidc_login():
 | 
			
		||||
    """Initiate OIDC login flow"""
 | 
			
		||||
    if not settings.OIDC_ISSUER or not settings.OIDC_CLIENT_ID or not settings.OIDC_REDIRECT_URI:
 | 
			
		||||
        raise HTTPException(status_code=500, detail="OIDC not properly configured")
 | 
			
		||||
    
 | 
			
		||||
    # Construct the authorization URL
 | 
			
		||||
    auth_url = (
 | 
			
		||||
        f"{settings.OIDC_ISSUER}/authorize?"
 | 
			
		||||
        f"client_id={settings.OIDC_CLIENT_ID}&"
 | 
			
		||||
        f"response_type=code&"
 | 
			
		||||
        f"redirect_uri={settings.OIDC_REDIRECT_URI}&"
 | 
			
		||||
        f"scope=openid profile email&"
 | 
			
		||||
        f"state=random_state_string"  # In real app, generate and store a proper state parameter
 | 
			
		||||
    )
 | 
			
		||||
    
 | 
			
		||||
    return {"auth_url": auth_url}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@router.get("/oidc-callback")
 | 
			
		||||
async def oidc_callback(code: str, state: str = None):
 | 
			
		||||
    """Handle OIDC callback"""
 | 
			
		||||
    # Verify state parameter in a real implementation
 | 
			
		||||
    
 | 
			
		||||
    # Handle the OIDC callback and return a user
 | 
			
		||||
    try:
 | 
			
		||||
        user = await handle_oidc_callback(code)
 | 
			
		||||
        # Create access token for the user
 | 
			
		||||
        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"}
 | 
			
		||||
    except NotImplementedError:
 | 
			
		||||
        # For demo purposes, return a mock response
 | 
			
		||||
        return {
 | 
			
		||||
            "message": "OIDC callback received. In a real implementation, this would complete the login process.",
 | 
			
		||||
            "code": code,
 | 
			
		||||
            "state": state
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@router.get("/social-login/{provider}")
 | 
			
		||||
async def social_login(provider: str):
 | 
			
		||||
    """Initiate social media login flow (Google, Facebook, etc.)"""
 | 
			
		||||
    if provider not in ["google", "facebook", "github"]:
 | 
			
		||||
        raise HTTPException(status_code=400, detail="Unsupported social provider")
 | 
			
		||||
    
 | 
			
		||||
    # In a real implementation, redirect to the provider's OAuth URL
 | 
			
		||||
    # For demo purposes, return a mock URL
 | 
			
		||||
    auth_url = f"https://{provider}.com/oauth/authorize"  # This is just a placeholder
 | 
			
		||||
    return {
 | 
			
		||||
        "issuer": settings.OIDC_ISSUER,
 | 
			
		||||
        "client_id": settings.OIDC_CLIENT_ID,
 | 
			
		||||
        "redirect_uri": settings.OIDC_REDIRECT_URI
 | 
			
		||||
    }
 | 
			
		||||
        "message": f"Redirect user to {provider} for authentication",
 | 
			
		||||
        "auth_url": auth_url,
 | 
			
		||||
        "provider": provider
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@router.post("/social-login/{provider}/callback")
 | 
			
		||||
async def social_login_callback(provider: str, access_token: str):
 | 
			
		||||
    """Handle social media login callback"""
 | 
			
		||||
    if provider not in ["google", "facebook", "github"]:
 | 
			
		||||
        raise HTTPException(status_code=400, detail="Unsupported social provider")
 | 
			
		||||
    
 | 
			
		||||
    # In a real implementation, validate the access token and fetch user info
 | 
			
		||||
    # For demo purposes, return a mock response
 | 
			
		||||
    try:
 | 
			
		||||
        user = await handle_social_login(provider, access_token)
 | 
			
		||||
        # Create access token for the user
 | 
			
		||||
        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"}
 | 
			
		||||
    except NotImplementedError:
 | 
			
		||||
        # For demo purposes, return a mock response
 | 
			
		||||
        return {
 | 
			
		||||
            "message": "Social login callback received. In a real implementation, this would complete the login process.",
 | 
			
		||||
            "provider": provider,
 | 
			
		||||
            "access_token": access_token
 | 
			
		||||
        }
 | 
			
		||||
@@ -6,9 +6,9 @@ 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
 | 
			
		||||
from ...database import SessionLocal
 | 
			
		||||
from ...models import JobPosting, User
 | 
			
		||||
from ...config.settings import settings
 | 
			
		||||
 | 
			
		||||
router = APIRouter()
 | 
			
		||||
 | 
			
		||||
@@ -47,6 +47,21 @@ class JobResponse(BaseModel):
 | 
			
		||||
 | 
			
		||||
    class Config:
 | 
			
		||||
        from_attributes = True
 | 
			
		||||
        json_schema_extra = {
 | 
			
		||||
            "example": {
 | 
			
		||||
                "id": 1,
 | 
			
		||||
                "title": "Software Engineer",
 | 
			
		||||
                "description": "We are looking for a skilled software engineer...",
 | 
			
		||||
                "requirements": "Bachelor's degree in Computer Science...",
 | 
			
		||||
                "location": "New York, NY",
 | 
			
		||||
                "salary_min": 8000000,  # in cents
 | 
			
		||||
                "salary_max": 12000000,  # in cents
 | 
			
		||||
                "is_active": True,
 | 
			
		||||
                "is_remote": True,
 | 
			
		||||
                "tenant_id": 1,
 | 
			
		||||
                "created_by_user_id": 1
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@router.get("/", response_model=List[JobResponse])
 | 
			
		||||
async def get_jobs(skip: int = 0, limit: int = 100, is_active: bool = True, db: Session = Depends(SessionLocal), request: Request = None):
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										161
									
								
								qwen/python/merchants_of_hope/api/v1/resumes.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								qwen/python/merchants_of_hope/api/v1/resumes.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,161 @@
 | 
			
		||||
"""
 | 
			
		||||
Resume API routes
 | 
			
		||||
"""
 | 
			
		||||
from fastapi import APIRouter, Depends, HTTPException, status, Request
 | 
			
		||||
from typing import List
 | 
			
		||||
from pydantic import BaseModel
 | 
			
		||||
from sqlalchemy.orm import Session
 | 
			
		||||
 | 
			
		||||
from ...database import SessionLocal
 | 
			
		||||
from ...models import Resume, User
 | 
			
		||||
from ...config.settings import settings
 | 
			
		||||
from ...services.resume_service import create_resume, get_user_resumes, get_resume_by_id, update_resume, delete_resume
 | 
			
		||||
 | 
			
		||||
router = APIRouter()
 | 
			
		||||
 | 
			
		||||
# Pydantic models for resumes
 | 
			
		||||
class ResumeCreate(BaseModel):
 | 
			
		||||
    title: str
 | 
			
		||||
    content: str
 | 
			
		||||
 | 
			
		||||
    class Config:
 | 
			
		||||
        json_schema_extra = {
 | 
			
		||||
            "example": {
 | 
			
		||||
                "title": "John Doe's Resume",
 | 
			
		||||
                "content": "Experienced software engineer with 5 years of experience..."
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
class ResumeUpdate(BaseModel):
 | 
			
		||||
    title: str = None
 | 
			
		||||
    content: str = None
 | 
			
		||||
 | 
			
		||||
class ResumeResponse(BaseModel):
 | 
			
		||||
    id: int
 | 
			
		||||
    user_id: int
 | 
			
		||||
    title: str
 | 
			
		||||
    content: str
 | 
			
		||||
    is_active: bool
 | 
			
		||||
 | 
			
		||||
    class Config:
 | 
			
		||||
        from_attributes = True
 | 
			
		||||
        json_schema_extra = {
 | 
			
		||||
            "example": {
 | 
			
		||||
                "id": 1,
 | 
			
		||||
                "user_id": 1,
 | 
			
		||||
                "title": "John Doe's Resume",
 | 
			
		||||
                "content": "Experienced software engineer with 5 years of experience...",
 | 
			
		||||
                "is_active": True
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@router.get("/", response_model=List[ResumeResponse])
 | 
			
		||||
async def get_resumes(db: Session = Depends(SessionLocal), request: Request = None):
 | 
			
		||||
    """Get all resumes for the current user"""
 | 
			
		||||
    tenant_id = getattr(request.state, 'tenant_id', None)
 | 
			
		||||
    if not tenant_id and settings.MULTI_TENANT_ENABLED:
 | 
			
		||||
        raise HTTPException(status_code=400, detail="Tenant ID is required")
 | 
			
		||||
    
 | 
			
		||||
    # Extract user_id from token in a real app, for now using a default
 | 
			
		||||
    user_id = 1  # This would come from authentication in a real implementation
 | 
			
		||||
    
 | 
			
		||||
    # Verify user belongs to the current tenant
 | 
			
		||||
    user = db.query(User).filter(
 | 
			
		||||
        User.id == user_id,
 | 
			
		||||
        User.tenant_id == tenant_id
 | 
			
		||||
    ).first()
 | 
			
		||||
    if not user and settings.MULTI_TENANT_ENABLED:
 | 
			
		||||
        raise HTTPException(status_code=400, detail="Access denied")
 | 
			
		||||
    
 | 
			
		||||
    resumes = get_user_resumes(db, user_id, tenant_id)
 | 
			
		||||
    return resumes
 | 
			
		||||
 | 
			
		||||
@router.get("/{resume_id}", response_model=ResumeResponse)
 | 
			
		||||
async def get_resume(resume_id: int, db: Session = Depends(SessionLocal), request: Request = None):
 | 
			
		||||
    """Get a specific resume"""
 | 
			
		||||
    tenant_id = getattr(request.state, 'tenant_id', None)
 | 
			
		||||
    if not tenant_id and settings.MULTI_TENANT_ENABLED:
 | 
			
		||||
        raise HTTPException(status_code=400, detail="Tenant ID is required")
 | 
			
		||||
    
 | 
			
		||||
    # Extract user_id from token in a real app, for now using a default
 | 
			
		||||
    user_id = 1  # This would come from authentication in a real implementation
 | 
			
		||||
    
 | 
			
		||||
    # Verify user belongs to the current tenant
 | 
			
		||||
    user = db.query(User).filter(
 | 
			
		||||
        User.id == user_id,
 | 
			
		||||
        User.tenant_id == tenant_id
 | 
			
		||||
    ).first()
 | 
			
		||||
    if not user and settings.MULTI_TENANT_ENABLED:
 | 
			
		||||
        raise HTTPException(status_code=400, detail="Access denied")
 | 
			
		||||
    
 | 
			
		||||
    resume = get_resume_by_id(db, resume_id, user_id, tenant_id)
 | 
			
		||||
    if not resume:
 | 
			
		||||
        raise HTTPException(status_code=404, detail="Resume not found")
 | 
			
		||||
    return resume
 | 
			
		||||
 | 
			
		||||
@router.post("/", response_model=ResumeResponse)
 | 
			
		||||
async def create_user_resume(resume: ResumeCreate, db: Session = Depends(SessionLocal), request: Request = None):
 | 
			
		||||
    """Create a new resume for the current user"""
 | 
			
		||||
    tenant_id = getattr(request.state, 'tenant_id', None)
 | 
			
		||||
    if not tenant_id and settings.MULTI_TENANT_ENABLED:
 | 
			
		||||
        raise HTTPException(status_code=400, detail="Tenant ID is required")
 | 
			
		||||
    
 | 
			
		||||
    # Extract user_id from token in a real app, for now using a default
 | 
			
		||||
    user_id = 1  # This would come from authentication in a real implementation
 | 
			
		||||
    
 | 
			
		||||
    # Verify user belongs to the current tenant
 | 
			
		||||
    user = db.query(User).filter(
 | 
			
		||||
        User.id == user_id,
 | 
			
		||||
        User.tenant_id == tenant_id
 | 
			
		||||
    ).first()
 | 
			
		||||
    if not user and settings.MULTI_TENANT_ENABLED:
 | 
			
		||||
        raise HTTPException(status_code=400, detail="Access denied")
 | 
			
		||||
    
 | 
			
		||||
    db_resume = create_resume(db, user_id, resume.title, resume.content, tenant_id)
 | 
			
		||||
    return db_resume
 | 
			
		||||
 | 
			
		||||
@router.put("/{resume_id}", response_model=ResumeResponse)
 | 
			
		||||
async def update_user_resume(resume_id: int, resume_update: ResumeUpdate, db: Session = Depends(SessionLocal), request: Request = None):
 | 
			
		||||
    """Update a resume for the current user"""
 | 
			
		||||
    tenant_id = getattr(request.state, 'tenant_id', None)
 | 
			
		||||
    if not tenant_id and settings.MULTI_TENANT_ENABLED:
 | 
			
		||||
        raise HTTPException(status_code=400, detail="Tenant ID is required")
 | 
			
		||||
    
 | 
			
		||||
    # Extract user_id from token in a real app, for now using a default
 | 
			
		||||
    user_id = 1  # This would come from authentication in a real implementation
 | 
			
		||||
    
 | 
			
		||||
    # Verify user belongs to the current tenant
 | 
			
		||||
    user = db.query(User).filter(
 | 
			
		||||
        User.id == user_id,
 | 
			
		||||
        User.tenant_id == tenant_id
 | 
			
		||||
    ).first()
 | 
			
		||||
    if not user and settings.MULTI_TENANT_ENABLED:
 | 
			
		||||
        raise HTTPException(status_code=400, detail="Access denied")
 | 
			
		||||
    
 | 
			
		||||
    db_resume = update_resume(db, resume_id, user_id, tenant_id, resume_update.title, resume_update.content)
 | 
			
		||||
    if not db_resume:
 | 
			
		||||
        raise HTTPException(status_code=404, detail="Resume not found")
 | 
			
		||||
    return db_resume
 | 
			
		||||
 | 
			
		||||
@router.delete("/{resume_id}")
 | 
			
		||||
async def delete_user_resume(resume_id: int, db: Session = Depends(SessionLocal), request: Request = None):
 | 
			
		||||
    """Delete a resume for the current user"""
 | 
			
		||||
    tenant_id = getattr(request.state, 'tenant_id', None)
 | 
			
		||||
    if not tenant_id and settings.MULTI_TENANT_ENABLED:
 | 
			
		||||
        raise HTTPException(status_code=400, detail="Tenant ID is required")
 | 
			
		||||
    
 | 
			
		||||
    # Extract user_id from token in a real app, for now using a default
 | 
			
		||||
    user_id = 1  # This would come from authentication in a real implementation
 | 
			
		||||
    
 | 
			
		||||
    # Verify user belongs to the current tenant
 | 
			
		||||
    user = db.query(User).filter(
 | 
			
		||||
        User.id == user_id,
 | 
			
		||||
        User.tenant_id == tenant_id
 | 
			
		||||
    ).first()
 | 
			
		||||
    if not user and settings.MULTI_TENANT_ENABLED:
 | 
			
		||||
        raise HTTPException(status_code=400, detail="Access denied")
 | 
			
		||||
    
 | 
			
		||||
    success = delete_resume(db, resume_id, user_id, tenant_id)
 | 
			
		||||
    if not success:
 | 
			
		||||
        raise HTTPException(status_code=404, detail="Resume not found")
 | 
			
		||||
    return {"message": "Resume deleted successfully"}
 | 
			
		||||
@@ -7,6 +7,7 @@ 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
 | 
			
		||||
from .resumes import router as resumes_router
 | 
			
		||||
 | 
			
		||||
api_router = APIRouter()
 | 
			
		||||
 | 
			
		||||
@@ -15,4 +16,5 @@ 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"])
 | 
			
		||||
api_router.include_router(applications_router, prefix="/applications", tags=["Applications"])
 | 
			
		||||
api_router.include_router(resumes_router, prefix="/resumes", tags=["Resumes"])
 | 
			
		||||
@@ -6,9 +6,9 @@ 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
 | 
			
		||||
from ...database import SessionLocal
 | 
			
		||||
from ...models import Tenant
 | 
			
		||||
from ...config.settings import settings
 | 
			
		||||
 | 
			
		||||
router = APIRouter()
 | 
			
		||||
 | 
			
		||||
@@ -17,6 +17,14 @@ class TenantCreate(BaseModel):
 | 
			
		||||
    name: str
 | 
			
		||||
    subdomain: str
 | 
			
		||||
 | 
			
		||||
    class Config:
 | 
			
		||||
        json_schema_extra = {
 | 
			
		||||
            "example": {
 | 
			
		||||
                "name": "Acme Corporation",
 | 
			
		||||
                "subdomain": "acme"
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
class TenantResponse(BaseModel):
 | 
			
		||||
    id: int
 | 
			
		||||
    name: str
 | 
			
		||||
@@ -25,6 +33,14 @@ class TenantResponse(BaseModel):
 | 
			
		||||
 | 
			
		||||
    class Config:
 | 
			
		||||
        from_attributes = True
 | 
			
		||||
        json_schema_extra = {
 | 
			
		||||
            "example": {
 | 
			
		||||
                "id": 1,
 | 
			
		||||
                "name": "Acme Corporation",
 | 
			
		||||
                "subdomain": "acme",
 | 
			
		||||
                "is_active": True
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@router.get("/", response_model=List[TenantResponse])
 | 
			
		||||
async def get_tenants(skip: int = 0, limit: int = 100, db: Session = Depends(SessionLocal)):
 | 
			
		||||
 
 | 
			
		||||
@@ -7,9 +7,9 @@ from pydantic import BaseModel
 | 
			
		||||
import hashlib
 | 
			
		||||
from sqlalchemy.orm import Session
 | 
			
		||||
 | 
			
		||||
from ..database import SessionLocal
 | 
			
		||||
from ..models import User, UserRole, Tenant
 | 
			
		||||
from ..config.settings import settings
 | 
			
		||||
from ...database import SessionLocal
 | 
			
		||||
from ...models import User, UserRole, Tenant
 | 
			
		||||
from ...config.settings import settings
 | 
			
		||||
 | 
			
		||||
router = APIRouter()
 | 
			
		||||
 | 
			
		||||
@@ -36,20 +36,35 @@ class UserResponse(BaseModel):
 | 
			
		||||
 | 
			
		||||
    class Config:
 | 
			
		||||
        from_attributes = True
 | 
			
		||||
        json_schema_extra = {
 | 
			
		||||
            "example": {
 | 
			
		||||
                "id": 1,
 | 
			
		||||
                "email": "user@example.com",
 | 
			
		||||
                "username": "johndoe",
 | 
			
		||||
                "role": "job_seeker",
 | 
			
		||||
                "is_active": True,
 | 
			
		||||
                "is_verified": True,
 | 
			
		||||
                "tenant_id": 1
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
def hash_password(password: str) -> str:
 | 
			
		||||
    """Hash password using SHA256 (in production, use bcrypt)"""
 | 
			
		||||
    return hashlib.sha256(password.encode()).hexdigest()
 | 
			
		||||
def hash_password_util(password: str) -> str:
 | 
			
		||||
    """Hash password using utility function"""
 | 
			
		||||
    from ..utils.security import get_password_hash
 | 
			
		||||
    return get_password_hash(password)
 | 
			
		||||
 | 
			
		||||
@router.get("/", response_model=List[UserResponse])
 | 
			
		||||
async def get_users(skip: int = 0, limit: int = 100, db: Session = Depends(SessionLocal), request: Request = None):
 | 
			
		||||
    """Get all users for the current tenant"""
 | 
			
		||||
    tenant_id = getattr(request.state, 'tenant_id', None)
 | 
			
		||||
    if not tenant_id and settings.MULTI_TENANT_ENABLED:
 | 
			
		||||
        raise HTTPException(status_code=400, detail="Tenant ID is required")
 | 
			
		||||
        # For testing, allow without tenant
 | 
			
		||||
        import os
 | 
			
		||||
        if os.getenv("TESTING", "False").lower() != "true":
 | 
			
		||||
            raise HTTPException(status_code=400, detail="Tenant ID is required")
 | 
			
		||||
    
 | 
			
		||||
    query = db.query(User)
 | 
			
		||||
    if settings.MULTI_TENANT_ENABLED:
 | 
			
		||||
    if settings.MULTI_TENANT_ENABLED and tenant_id:
 | 
			
		||||
        query = query.filter(User.tenant_id == tenant_id)
 | 
			
		||||
    
 | 
			
		||||
    users = query.offset(skip).limit(limit).all()
 | 
			
		||||
@@ -87,7 +102,7 @@ async def create_user(user: UserCreate, db: Session = Depends(SessionLocal), req
 | 
			
		||||
        raise HTTPException(status_code=400, detail="Email or username already registered")
 | 
			
		||||
    
 | 
			
		||||
    # Create new user
 | 
			
		||||
    hashed_pwd = hash_password(user.password)
 | 
			
		||||
    hashed_pwd = hash_password_util(user.password)
 | 
			
		||||
    db_user = User(
 | 
			
		||||
        email=user.email,
 | 
			
		||||
        username=user.username,
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user