233 lines
7.6 KiB
Python
233 lines
7.6 KiB
Python
from typing import List, Optional
|
|
from pydantic import BaseModel, EmailStr
|
|
from fastapi import FastAPI, File, UploadFile, Form, HTTPException, Depends
|
|
from fastapi.responses import RedirectResponse
|
|
from minio import Minio
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
import os
|
|
import uuid
|
|
from config import Config
|
|
from database.database import get_db, init_db, User, Profile as DBProfile
|
|
|
|
from routers import profile, vacancies, chat
|
|
MINIO_ENDPOINT = os.getenv("MINIO_ENDPOINT", "localhost:9000")
|
|
MINIO_ACCESS_KEY = os.getenv("MINIO_ACCESS_KEY", "minioadmin")
|
|
MINIO_SECRET_KEY = os.getenv("MINIO_SECRET_KEY", "minioadmin")
|
|
MINIO_BUCKET = os.getenv("MINIO_BUCKET", "resumes")
|
|
|
|
minio_client = None
|
|
|
|
def get_minio_client():
|
|
global minio_client
|
|
if minio_client is None:
|
|
try:
|
|
minio_client = Minio(
|
|
MINIO_ENDPOINT,
|
|
access_key=MINIO_ACCESS_KEY,
|
|
secret_key=MINIO_SECRET_KEY,
|
|
secure=False
|
|
)
|
|
if not minio_client.bucket_exists(MINIO_BUCKET):
|
|
minio_client.make_bucket(MINIO_BUCKET)
|
|
print(f"MinIO connected successfully to {MINIO_ENDPOINT}")
|
|
except Exception as e:
|
|
print(f"Warning: Could not connect to MinIO at {MINIO_ENDPOINT}: {e}")
|
|
print("MinIO features will be disabled. Start MinIO to enable file storage.")
|
|
minio_client = False
|
|
return minio_client if minio_client else None
|
|
|
|
|
|
class Profile(BaseModel):
|
|
name: str
|
|
email: EmailStr
|
|
position: str
|
|
competencies: Optional[str] = None
|
|
experience: Optional[str] = None
|
|
skills: Optional[str] = None
|
|
country: Optional[str] = None
|
|
languages: Optional[str] = None
|
|
employment_format: Optional[str] = None
|
|
rate: Optional[str] = None
|
|
relocation: Optional[str] = None
|
|
cv_url: Optional[str] = None
|
|
|
|
class Vacancy(BaseModel):
|
|
title: str
|
|
salary_range: str
|
|
employment_format: str
|
|
skills: str
|
|
link: str
|
|
|
|
vacancies_db = [
|
|
Vacancy(title="Python Developer", salary_range="2000-4000 USD", employment_format="remote", skills="Python, FastAPI", link="https://example.com/vacancy/1"),
|
|
Vacancy(title="Frontend Engineer", salary_range="1500-3000 USD", employment_format="office", skills="React, TypeScript", link="https://example.com/vacancy/2")
|
|
]
|
|
|
|
app = FastAPI()
|
|
app.include_router(profile.router)
|
|
app.include_router(vacancies.router)
|
|
app.include_router(chat.router)
|
|
|
|
@app.on_event("startup")
|
|
async def startup_event():
|
|
await init_db()
|
|
print("FastAPI application started successfully")
|
|
|
|
@app.post("/profile")
|
|
async def save_profile(
|
|
name: str = Form(...),
|
|
email: EmailStr = Form(...),
|
|
position: str = Form(...),
|
|
competencies: Optional[str] = Form(None),
|
|
experience: Optional[str] = Form(None),
|
|
skills: Optional[str] = Form(None),
|
|
country: Optional[str] = Form(None),
|
|
languages: Optional[str] = Form(None),
|
|
employment_format: Optional[str] = Form(None),
|
|
rate: Optional[str] = Form(None),
|
|
relocation: Optional[str] = Form(None),
|
|
cv: UploadFile = File(...),
|
|
db: AsyncSession = Depends(get_db)
|
|
):
|
|
if cv.content_type != "application/pdf":
|
|
raise HTTPException(status_code=400, detail="CV must be a PDF file")
|
|
|
|
client = get_minio_client()
|
|
if not client:
|
|
raise HTTPException(status_code=503, detail="File storage (MinIO) is not available. Please start MinIO service.")
|
|
|
|
file_id = f"{email.replace('@','_')}_{cv.filename}"
|
|
client.put_object(
|
|
MINIO_BUCKET,
|
|
file_id,
|
|
cv.file,
|
|
length=-1,
|
|
part_size=10*1024*1024,
|
|
content_type="application/pdf"
|
|
)
|
|
cv_url = f"/profile/cv/{file_id}"
|
|
|
|
result = await db.execute(select(DBProfile).where(DBProfile.email == email))
|
|
existing_profile = result.scalar_one_or_none()
|
|
|
|
if existing_profile:
|
|
existing_profile.name = name
|
|
existing_profile.position = position
|
|
existing_profile.competencies = competencies
|
|
existing_profile.experience = experience
|
|
existing_profile.skills = skills
|
|
existing_profile.country = country
|
|
existing_profile.languages = languages
|
|
existing_profile.employment_format = employment_format
|
|
existing_profile.rate = rate
|
|
existing_profile.relocation = relocation
|
|
existing_profile.cv_url = cv_url
|
|
else:
|
|
new_profile = DBProfile(
|
|
name=name,
|
|
email=email,
|
|
position=position,
|
|
competencies=competencies,
|
|
experience=experience,
|
|
skills=skills,
|
|
country=country,
|
|
languages=languages,
|
|
employment_format=employment_format,
|
|
rate=rate,
|
|
relocation=relocation,
|
|
cv_url=cv_url
|
|
)
|
|
db.add(new_profile)
|
|
|
|
await db.commit()
|
|
return {"message": "Profile saved", "cv_url": cv_url}
|
|
|
|
@app.get("/profile/{email}", response_model=Profile)
|
|
async def get_profile(email: str, db: AsyncSession = Depends(get_db)):
|
|
result = await db.execute(select(DBProfile).where(DBProfile.email == email))
|
|
profile = result.scalar_one_or_none()
|
|
|
|
if not profile:
|
|
raise HTTPException(status_code=404, detail="Profile not found")
|
|
|
|
return Profile(
|
|
name=profile.name,
|
|
email=profile.email,
|
|
position=profile.position,
|
|
competencies=profile.competencies,
|
|
experience=profile.experience,
|
|
skills=profile.skills,
|
|
country=profile.country,
|
|
languages=profile.languages,
|
|
employment_format=profile.employment_format,
|
|
rate=profile.rate,
|
|
relocation=profile.relocation,
|
|
cv_url=profile.cv_url
|
|
)
|
|
|
|
@app.get("/database/tokens")
|
|
async def get_tokens(db: AsyncSession = Depends(get_db)):
|
|
result = await db.execute(select(User))
|
|
users = result.scalars().all()
|
|
return [{"token": user.token, "status": user.status, "username": user.username} for user in users]
|
|
|
|
@app.get("/profile/cv/{file_id}")
|
|
async def download_cv(file_id: str):
|
|
client = get_minio_client()
|
|
if not client:
|
|
raise HTTPException(status_code=503, detail="File storage (MinIO) is not available. Please start MinIO service.")
|
|
try:
|
|
response = client.get_object(MINIO_BUCKET, file_id)
|
|
return RedirectResponse(response.geturl())
|
|
except Exception:
|
|
raise HTTPException(status_code=404, detail="CV not found")
|
|
|
|
@app.get("/vacancies", response_model=List[Vacancy])
|
|
async def get_vacancies():
|
|
return vacancies_db
|
|
|
|
@app.get("/login")
|
|
async def login_page(db: AsyncSession = Depends(get_db)):
|
|
auth_token = str(uuid.uuid4())
|
|
bot_name = Config.BOT_NAME
|
|
|
|
new_user = User(
|
|
telegram_id=None,
|
|
token=auth_token,
|
|
username=None,
|
|
status="pending"
|
|
)
|
|
db.add(new_user)
|
|
await db.commit()
|
|
|
|
bot_url = f"https://t.me/{bot_name}?start={auth_token}"
|
|
|
|
return {
|
|
"message": "Нажмите на кнопку для регистрации",
|
|
"url": bot_url,
|
|
"check_status_url": f"/check-auth/{auth_token}"
|
|
}
|
|
|
|
|
|
@app.get("/check-auth/{token}")
|
|
async def check_auth(token: str, db: AsyncSession = Depends(get_db)):
|
|
"""Check if user has completed authentication via Telegram bot"""
|
|
result = await db.execute(select(User).where(User.token == token))
|
|
user = result.scalar_one_or_none()
|
|
|
|
if not user:
|
|
return {"authorized": False, "message": "Invalid token"}
|
|
|
|
if user.status == "success":
|
|
return {
|
|
"authorized": True,
|
|
"user_data": {
|
|
"telegram_id": user.telegram_id,
|
|
"username": user.username,
|
|
"status": user.status
|
|
}
|
|
}
|
|
|
|
return {"authorized": False, "message": "Authentication pending"}
|