the middle of the idiots
This commit is contained in:
116
qwen/python/merchants_of_hope/services/application_service.py
Normal file
116
qwen/python/merchants_of_hope/services/application_service.py
Normal file
@@ -0,0 +1,116 @@
|
||||
"""
|
||||
Job application management service
|
||||
"""
|
||||
from typing import List, Optional
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ..models import Application, JobPosting, Resume
|
||||
from ..config.settings import settings
|
||||
|
||||
|
||||
def create_application(db: Session, user_id: int, job_posting_id: int, resume_id: int,
|
||||
cover_letter: str = None, tenant_id: int = None) -> Application:
|
||||
"""
|
||||
Create a new job application
|
||||
"""
|
||||
# Verify that the resume belongs to the user
|
||||
resume = db.query(Resume).filter(
|
||||
Resume.id == resume_id,
|
||||
Resume.user_id == user_id
|
||||
).first()
|
||||
if not resume:
|
||||
raise ValueError("Resume does not belong to user")
|
||||
|
||||
# In a multi-tenant setup, we should verify tenant access to the job posting
|
||||
if settings.MULTI_TENANT_ENABLED and tenant_id:
|
||||
job_posting = db.query(JobPosting).filter(
|
||||
JobPosting.id == job_posting_id,
|
||||
JobPosting.tenant_id == tenant_id
|
||||
).first()
|
||||
if not job_posting:
|
||||
raise ValueError("Job posting not found in tenant")
|
||||
|
||||
db_application = Application(
|
||||
user_id=user_id,
|
||||
job_posting_id=job_posting_id,
|
||||
resume_id=resume_id,
|
||||
cover_letter=cover_letter
|
||||
)
|
||||
db.add(db_application)
|
||||
db.commit()
|
||||
db.refresh(db_application)
|
||||
return db_application
|
||||
|
||||
|
||||
def get_user_applications(db: Session, user_id: int) -> List[Application]:
|
||||
"""
|
||||
Get all applications for a specific user
|
||||
"""
|
||||
return db.query(Application).filter(Application.user_id == user_id).all()
|
||||
|
||||
|
||||
def get_job_applications(db: Session, job_posting_id: int, tenant_id: int = None) -> List[Application]:
|
||||
"""
|
||||
Get all applications for a specific job posting within a tenant
|
||||
"""
|
||||
query = db.query(Application).join(JobPosting).filter(Application.job_posting_id == job_posting_id)
|
||||
|
||||
if settings.MULTI_TENANT_ENABLED and tenant_id:
|
||||
query = query.filter(JobPosting.tenant_id == tenant_id)
|
||||
|
||||
return query.all()
|
||||
|
||||
|
||||
def get_application_by_id(db: Session, application_id: int, user_id: int = None,
|
||||
job_posting_id: int = None) -> Optional[Application]:
|
||||
"""
|
||||
Get a specific application by ID with optional user or job constraints
|
||||
"""
|
||||
query = db.query(Application).filter(Application.id == application_id)
|
||||
|
||||
if user_id:
|
||||
query = query.filter(Application.user_id == user_id)
|
||||
if job_posting_id:
|
||||
query = query.filter(Application.job_posting_id == job_posting_id)
|
||||
|
||||
return query.first()
|
||||
|
||||
|
||||
def update_application_status(db: Session, application_id: int, status: str,
|
||||
user_id: int = None, job_posting_id: int = None) -> Optional[Application]:
|
||||
"""
|
||||
Update application status
|
||||
"""
|
||||
query = db.query(Application).filter(Application.id == application_id)
|
||||
|
||||
if user_id:
|
||||
query = query.filter(Application.user_id == user_id)
|
||||
if job_posting_id:
|
||||
query = query.filter(Application.job_posting_id == job_posting_id)
|
||||
|
||||
db_application = query.first()
|
||||
if not db_application:
|
||||
return None
|
||||
|
||||
db_application.status = status
|
||||
db.commit()
|
||||
db.refresh(db_application)
|
||||
return db_application
|
||||
|
||||
|
||||
def delete_application(db: Session, application_id: int, user_id: int = None) -> bool:
|
||||
"""
|
||||
Delete an application
|
||||
"""
|
||||
query = db.query(Application).filter(Application.id == application_id)
|
||||
|
||||
if user_id:
|
||||
query = query.filter(Application.user_id == user_id)
|
||||
|
||||
db_application = query.first()
|
||||
if not db_application:
|
||||
return False
|
||||
|
||||
db.delete(db_application)
|
||||
db.commit()
|
||||
return True
|
||||
108
qwen/python/merchants_of_hope/services/auth_service.py
Normal file
108
qwen/python/merchants_of_hope/services/auth_service.py
Normal file
@@ -0,0 +1,108 @@
|
||||
"""
|
||||
Authentication service with OIDC and social media support
|
||||
"""
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional
|
||||
from jose import jwt
|
||||
from fastapi import HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ..config.settings import settings
|
||||
from ..models import User
|
||||
from ..database import SessionLocal
|
||||
|
||||
|
||||
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
|
||||
"""Create a JWT access token"""
|
||||
to_encode = data.copy()
|
||||
if expires_delta:
|
||||
expire = datetime.utcnow() + expires_delta
|
||||
else:
|
||||
expire = datetime.utcnow() + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
to_encode.update({"exp": expire})
|
||||
encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
|
||||
return encoded_jwt
|
||||
|
||||
|
||||
def verify_token(token: str) -> dict:
|
||||
"""Verify a JWT token and return the payload"""
|
||||
try:
|
||||
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
|
||||
return payload
|
||||
except jwt.JWTError:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Could not validate credentials",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
|
||||
def authenticate_user(db: Session, username: str, password: str) -> Optional[User]:
|
||||
"""Authenticate a user with username and password"""
|
||||
user = db.query(User).filter(User.username == username).first()
|
||||
if not user or not verify_password_correct(password, user.hashed_password):
|
||||
return None
|
||||
if not user.is_active:
|
||||
return None
|
||||
return user
|
||||
|
||||
|
||||
async def get_oidc_config():
|
||||
"""Get OIDC configuration"""
|
||||
return {
|
||||
"issuer": settings.OIDC_ISSUER,
|
||||
"authorization_endpoint": f"{settings.OIDC_ISSUER}/authorize" if settings.OIDC_ISSUER else None,
|
||||
"token_endpoint": f"{settings.OIDC_ISSUER}/token" if settings.OIDC_ISSUER else None,
|
||||
"userinfo_endpoint": f"{settings.OIDC_ISSUER}/userinfo" if settings.OIDC_ISSUER else None,
|
||||
"jwks_uri": f"{settings.OIDC_ISSUER}/.well-known/jwks.json" if settings.OIDC_ISSUER else None,
|
||||
"client_id": settings.OIDC_CLIENT_ID,
|
||||
"redirect_uri": settings.OIDC_REDIRECT_URI,
|
||||
}
|
||||
|
||||
|
||||
async def handle_oidc_callback(code: str) -> User:
|
||||
"""
|
||||
Handle OIDC callback and return or create a user
|
||||
This is a simplified implementation - in a real app you'd exchange the code
|
||||
for tokens and fetch user info from the OIDC provider
|
||||
"""
|
||||
# In a real implementation, you would:
|
||||
# 1. Exchange the authorization code for access/id tokens
|
||||
# 2. Validate the ID token
|
||||
# 3. Fetch user info from the userinfo endpoint
|
||||
# 4. Create or update the user in your database
|
||||
# 5. Return the user object
|
||||
|
||||
# For now, return a placeholder - this would be replaced with real OIDC logic
|
||||
raise NotImplementedError("OIDC callback handling not fully implemented")
|
||||
|
||||
|
||||
async def handle_social_login(provider: str, access_token: str) -> User:
|
||||
"""
|
||||
Handle social media login (Google, Facebook, etc.)
|
||||
This is a simplified implementation
|
||||
"""
|
||||
# In a real implementation, you would:
|
||||
# 1. Validate the access token with the social provider
|
||||
# 2. Fetch user info from the provider's API
|
||||
# 3. Create or update the user in your database
|
||||
# 4. Return the user object
|
||||
|
||||
# For now, return a placeholder - this would be replaced with real social login logic
|
||||
raise NotImplementedError("Social login handling not fully implemented")
|
||||
|
||||
|
||||
def hash_password(password: str) -> str:
|
||||
"""
|
||||
Hash a password using bcrypt
|
||||
"""
|
||||
from ..utils.security import get_password_hash
|
||||
return get_password_hash(password)
|
||||
|
||||
|
||||
def verify_password_correct(plain_password: str, hashed_password: str) -> bool:
|
||||
"""
|
||||
Verify a plain password against its hash
|
||||
"""
|
||||
from ..utils.security import verify_password as verify_password_util
|
||||
return verify_password_util(plain_password, hashed_password)
|
||||
100
qwen/python/merchants_of_hope/services/resume_service.py
Normal file
100
qwen/python/merchants_of_hope/services/resume_service.py
Normal file
@@ -0,0 +1,100 @@
|
||||
"""
|
||||
Resume management service
|
||||
"""
|
||||
from typing import List, Optional
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ..models import Resume, User
|
||||
from ..config.settings import settings
|
||||
|
||||
|
||||
def create_resume(db: Session, user_id: int, title: str, content: str, tenant_id: int) -> Resume:
|
||||
"""
|
||||
Create a new resume for a user
|
||||
"""
|
||||
db_resume = Resume(
|
||||
user_id=user_id,
|
||||
title=title,
|
||||
content=content,
|
||||
tenant_id=tenant_id
|
||||
)
|
||||
db.add(db_resume)
|
||||
db.commit()
|
||||
db.refresh(db_resume)
|
||||
return db_resume
|
||||
|
||||
|
||||
def get_user_resumes(db: Session, user_id: int, tenant_id: int = None) -> List[Resume]:
|
||||
"""
|
||||
Get all resumes for a specific user
|
||||
"""
|
||||
query = db.query(Resume).filter(Resume.user_id == user_id)
|
||||
|
||||
if settings.MULTI_TENANT_ENABLED and tenant_id:
|
||||
query = query.filter(Resume.tenant_id == tenant_id)
|
||||
|
||||
return query.all()
|
||||
|
||||
|
||||
def get_resume_by_id(db: Session, resume_id: int, user_id: int, tenant_id: int = None) -> Optional[Resume]:
|
||||
"""
|
||||
Get a specific resume by ID for a user
|
||||
"""
|
||||
query = db.query(Resume).filter(
|
||||
Resume.id == resume_id,
|
||||
Resume.user_id == user_id
|
||||
)
|
||||
|
||||
if settings.MULTI_TENANT_ENABLED and tenant_id:
|
||||
query = query.filter(Resume.tenant_id == tenant_id)
|
||||
|
||||
return query.first()
|
||||
|
||||
|
||||
def update_resume(db: Session, resume_id: int, user_id: int, tenant_id: int = None, title: str = None, content: str = None) -> Optional[Resume]:
|
||||
"""
|
||||
Update a resume
|
||||
"""
|
||||
query = db.query(Resume).filter(
|
||||
Resume.id == resume_id,
|
||||
Resume.user_id == user_id
|
||||
)
|
||||
|
||||
if settings.MULTI_TENANT_ENABLED and tenant_id:
|
||||
query = query.filter(Resume.tenant_id == tenant_id)
|
||||
|
||||
db_resume = query.first()
|
||||
|
||||
if not db_resume:
|
||||
return None
|
||||
|
||||
if title is not None:
|
||||
db_resume.title = title
|
||||
if content is not None:
|
||||
db_resume.content = content
|
||||
|
||||
db.commit()
|
||||
db.refresh(db_resume)
|
||||
return db_resume
|
||||
|
||||
|
||||
def delete_resume(db: Session, resume_id: int, user_id: int, tenant_id: int = None) -> bool:
|
||||
"""
|
||||
Delete a resume
|
||||
"""
|
||||
query = db.query(Resume).filter(
|
||||
Resume.id == resume_id,
|
||||
Resume.user_id == user_id
|
||||
)
|
||||
|
||||
if settings.MULTI_TENANT_ENABLED and tenant_id:
|
||||
query = query.filter(Resume.tenant_id == tenant_id)
|
||||
|
||||
db_resume = query.first()
|
||||
|
||||
if not db_resume:
|
||||
return False
|
||||
|
||||
db.delete(db_resume)
|
||||
db.commit()
|
||||
return True
|
||||
49
qwen/python/merchants_of_hope/services/tenant_service.py
Normal file
49
qwen/python/merchants_of_hope/services/tenant_service.py
Normal file
@@ -0,0 +1,49 @@
|
||||
"""
|
||||
Multi-tenant service for managing tenant-specific operations
|
||||
"""
|
||||
from typing import Optional
|
||||
from fastapi import Request
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ..models import Tenant
|
||||
from ..config.settings import settings
|
||||
|
||||
|
||||
def get_current_tenant_id(request: Request) -> Optional[int]:
|
||||
"""
|
||||
Get the current tenant ID from the request
|
||||
"""
|
||||
return getattr(request.state, 'tenant_id', None)
|
||||
|
||||
|
||||
def verify_tenant_access(request: Request, db: Session, resource_tenant_id: int) -> bool:
|
||||
"""
|
||||
Verify that the current tenant has access to a resource
|
||||
"""
|
||||
if not settings.MULTI_TENANT_ENABLED:
|
||||
return True # If multi-tenancy is disabled, allow access
|
||||
|
||||
current_tenant_id = get_current_tenant_id(request)
|
||||
return current_tenant_id == resource_tenant_id
|
||||
|
||||
|
||||
def check_tenant_isolation(request: Request, db: Session, model_class, id: int) -> bool:
|
||||
"""
|
||||
Check if a specific instance of a model belongs to the current tenant
|
||||
"""
|
||||
if not settings.MULTI_TENANT_ENABLED:
|
||||
return True
|
||||
|
||||
current_tenant_id = get_current_tenant_id(request)
|
||||
|
||||
# Assuming the model has a tenant_id attribute
|
||||
instance = db.query(model_class).filter(model_class.id == id).first()
|
||||
if not instance:
|
||||
return False
|
||||
|
||||
# This is a generic approach - in practice you'd need to handle specific model types
|
||||
if hasattr(instance, 'tenant_id'):
|
||||
return instance.tenant_id == current_tenant_id
|
||||
else:
|
||||
# For models that aren't tenant-specific, allow access
|
||||
return True
|
||||
Reference in New Issue
Block a user