copied the code from the working repo

This commit is contained in:
2024-11-30 16:00:48 +03:00
parent f22b92869b
commit 15ac0cb9b8
148 changed files with 23342 additions and 0 deletions

View File

View File

@@ -0,0 +1,21 @@
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
database_hostname: str
database_port: str
database_password: str
database_name: str
database_username: str
access_key: str
secret_key: str
endpoint: str
secret_key2: str
algorithm: str
access_token_expire_minutes: int
refresh_token_expire_days: int
x_api_key: str
class Config:
env_file = ".env"
settings = Settings()

View File

@@ -0,0 +1,150 @@
from fastapi import status, HTTPException, Depends, BackgroundTasks
from sqlalchemy.orm import Session
from typing import List
from sqlalchemy import func
from . import models
from typing import List, Dict
from datetime import date
import requests
def get_group_pattern(year):
today = date.today()
current_year = int(today.strftime("%Y"))
current_month = int(today.strftime("%m"))
if current_month > 5:
year -= 1
if year <= 0:
return "_"
pattern = "^[:alpha:]"
for i in range(year):
pattern += "|" + str(current_year % 100 - i)
return pattern
def send_webhook_background(event_type: str, data: Dict):
webhook_url = "http://bot:3006/webhook"
payload = {
"event": event_type,
"data": data
}
try:
response = requests.post(webhook_url, json=payload)
response.raise_for_status()
except requests.RequestException as e:
print(f"Failed to send webhook: {e}")
def send_webhook_response_updated(data: Dict):
event_type = "response_updated"
send_webhook_background(event_type, data)
def update_hardskills(entity_id: int, hardskills: List[str], is_job: bool, db: Session):
model = models.JobsHard_skills if is_job else models.StudentsHard_skills
id_field = 'JobID' if is_job else 'StudentID'
# Удаление существующих навыков
db.query(model).filter(getattr(model, id_field) == entity_id).delete()
db.flush()
# Добавление новых навыков
hardskills_query = db.query(models.Hard_skills).filter(models.Hard_skills.Title.in_(hardskills))
new_hardskills = [model(**{id_field: entity_id, 'Hard_skillID': hardskill.Hard_skillID}) for hardskill in hardskills_query]
db.add_all(new_hardskills)
db.flush()
def update_matches(entity_id: int, is_job: bool, db: Session, background_tasks: BackgroundTasks) -> List[Dict]:
if is_job:
matches = db.query(
models.JobsHard_skills.JobID,
models.StudentsHard_skills.StudentID,
func.count(models.StudentsHard_skills.Hard_skillID).label("count_students_skills")
).join(models.JobsHard_skills, models.JobsHard_skills.Hard_skillID == models.StudentsHard_skills.Hard_skillID).\
filter(models.JobsHard_skills.JobID == entity_id).\
group_by(models.JobsHard_skills.JobID, models.StudentsHard_skills.StudentID).\
all()
count_jobs_skills = db.query(func.count(models.JobsHard_skills.Hard_skillID)).filter(models.JobsHard_skills.JobID == entity_id).scalar()
db.query(models.Matches).filter(models.Matches.JobID == entity_id).delete()
else:
matches = db.query(
models.StudentsHard_skills.StudentID,
models.JobsHard_skills.JobID,
func.count(models.StudentsHard_skills.Hard_skillID).label("count_students_skills")
).join(models.Jobs, models.Jobs.JobID == models.JobsHard_skills.JobID).\
join(models.StudentsHard_skills, models.JobsHard_skills.Hard_skillID == models.StudentsHard_skills.Hard_skillID).\
filter(models.Jobs.Archive == False, models.StudentsHard_skills.StudentID == entity_id).\
group_by(models.StudentsHard_skills.StudentID, models.JobsHard_skills.JobID).\
all()
db.query(models.Matches).filter(models.Matches.StudentID == entity_id).delete()
db.flush()
updated_matches = []
for match in matches:
if is_job:
MATCH = match.count_students_skills / count_jobs_skills * 100
updated_matches.append({
"job_id": match.JobID,
"student_id": match.StudentID,
"match_percentage": MATCH
})
else:
count_jobs_skills = db.query(func.count(models.JobsHard_skills.Hard_skillID)).filter(models.JobsHard_skills.JobID == match.JobID).scalar()
MATCH = match.count_students_skills / count_jobs_skills * 100
updated_matches.append({
"job_id": match.JobID,
"student_id": match.StudentID,
"match_percentage": MATCH
})
new_match = models.Matches(StudentID=match.StudentID, JobID=match.JobID, Match=MATCH)
db.add(new_match)
db.flush()
if updated_matches:
background_tasks.add_task(send_webhook_background, "matches_updated", {
"entity_type": "job" if is_job else "student",
"entity_id": entity_id,
"updated_matches": updated_matches
})
return updated_matches
def update_record(model, model_id: int, updated_data: dict, db: Session, background_tasks: BackgroundTasks):
query = db.query(model).filter(model.ResponseID == model_id)
record = query.first()
if not record:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
query.update(updated_data, synchronize_session=False)
db.flush()
commit_transaction(db)
post_update_data = {
"response_id": record.ResponseID,
"student_id": record.StudentID,
"job_id": record.JobID,
"status": record.Status,
"comment": record.Comment,
"link": record.Link
}
if post_update_data:
background_tasks.add_task(send_webhook_response_updated, post_update_data)
return query.first()
def commit_transaction(db):
try:
db.commit()
except:
db.rollback()
raise HTTPException(status_code=status.HTTP_409_CONFLICT)

View File

@@ -0,0 +1,47 @@
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session
from .config import settings
from .models import Hard_skills
DATABASE_URL = f'postgresql://{settings.database_username}:{settings.database_password}@{settings.database_hostname}:{settings.database_port}/{settings.database_name}'
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
def init_db(session: Session):
titles = [
'C', 'C++', 'C#', 'Java', 'Python', 'Ruby', 'Ruby on Rails', 'R', 'Matlab', 'Django', 'NetBeans',
'Scala', 'JavaScript', 'TypeScript', 'Go', 'Software Development', 'Application Development',
'Web Applications', 'Object Oriented Programming', 'Aspect Oriented Programming', 'Concurrent Programming',
'Mobile Development (iOS)', 'Mobile Development (Android)', 'Data Science', 'Data Analytics', 'Data Mining',
'Data Visualization', 'Machine Learning', 'TensorFlow', 'PyTorch', 'Keras', 'Theano', 'Statistical Analysis',
'Bayesian Analysis', 'Regression Analysis', 'Time Series Analysis', 'Clustering', 'K-means', 'KNN', 'Decision Trees',
'Random Forest', 'Dimensionality Reduction', 'PCA', 'SVD', 'Gradient Descent', 'Stochastic Gradient Descent',
'Outlier Detection', 'Frequent Itemset Mining', 'SQL', 'NoSQL', 'SQL Server', 'MS SQL Server', 'Apache Hadoop',
'Apache Spark', 'Apache Airflow', 'Apache Impala', 'Apache Drill', 'HTML', 'CSS', 'React', 'Angular', 'Vue.js',
'Node.js', 'Express.js', 'REST', 'SOAP', 'Web Platforms', 'System Architecture', 'Distributed Computing', 'AWS',
'AWS Glue', 'Azure', 'Google Cloud Platform', 'Docker', 'Kubernetes', 'UNIX', 'Linux', 'Windows', 'MacOS',
'Embedded Hardware', 'Debugging', 'Unit Testing', 'Integration Testing', 'System Testing', 'Code Review', 'Git',
'SVN', 'CI/CD', 'Software Documentation', 'IDE', 'CASE Tools', 'Computational Complexity', 'Algorithm Design',
'Data Structures', 'Mathematical Modeling', 'Statistics', 'Technical Writing', 'Technical Support', 'System Design',
'System Development', 'Technical Guidance', 'Client Interface', 'Vendor Interface', 'Emerging Technologies', 'Jira',
'Trello', 'Software Architecture', 'Word', 'Excel'
]
for title in titles:
exist_hardskill = session.query(Hard_skills).filter(Hard_skills.Title == title).first()
if not exist_hardskill:
hard_skills_object = Hard_skills(Title=title)
session.add(hard_skills_object)
session.commit()

View File

@@ -0,0 +1,32 @@
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from sqlalchemy.orm import Session
from .routers import auth, user, service, student, job
from .database import init_db, engine
from .models import Base
Base.metadata.create_all(engine)
with Session(engine) as session:
init_db(session)
app = FastAPI()
origins = ["*"]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
app.include_router(auth.router)
app.include_router(user.router)
app.include_router(student.router)
app.include_router(job.router)
app.include_router(service.router)
@app.get("/")
def root():
return {"message": "I'm ok!"}

View File

@@ -0,0 +1,87 @@
from sqlalchemy import Column, Integer, String, Boolean, ForeignKey, ARRAY, UniqueConstraint
from sqlalchemy.orm import DeclarativeBase
class Base(DeclarativeBase): pass
class Users(Base):
__tablename__ = 'Users'
UserID = Column(Integer, primary_key=True, autoincrement=True)
Email = Column(String(166), nullable=False, unique=True)
Hashed_password = Column(String(200), nullable=False)
class Students(Base):
__tablename__ = 'Students'
StudentID = Column(Integer, primary_key=True, autoincrement=True)
Name = Column(String(155), nullable=False)
Type = Column(String(60), nullable=False)
Faculties = Column(String(70), nullable=False)
Group = Column(String(20), nullable=False)
Year = Column(Integer, nullable=False)
Experience_specialty = Column(Boolean, nullable=False)
Time = Column(ARRAY(String(3)), nullable=False)
Soft_skills = Column(String(155), nullable=False)
Link = Column(String(155), nullable=False)
Email = Column(String(166), nullable=False)
Phone_number = Column(String(16), nullable=False)
class Jobs(Base):
__tablename__ = 'Jobs'
JobID = Column(Integer, primary_key=True, autoincrement=True)
UserID = Column(Integer, ForeignKey(
"Users.UserID", ondelete="CASCADE"), nullable=False)
Company_name = Column(String(155), nullable=False)
Link_to_job = Column(String(155), nullable=True)
Job_name = Column(String(155), nullable=False)
Year = Column(String(1), nullable=False)
Qualification = Column(Boolean, nullable=False)
Salary_after_interview = Column(Boolean, nullable=False)
Salary = Column(Integer, nullable=False)
Email = Column(String(155), nullable=False)
Archive = Column(Boolean, nullable=False)
Responsibilities = Column(String(255), nullable=False)
Time = Column(ARRAY(String), nullable=False)
class Hard_skills(Base):
__tablename__ = 'Hard_skills'
Hard_skillID = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
Title = Column(String, nullable=False, unique=True)
class StudentsHard_skills(Base):
__tablename__ = 'StudentsHard_skills'
StudentID = Column(Integer, ForeignKey(
"Students.StudentID", onupdate="CASCADE", ondelete="CASCADE"), primary_key=True)
Hard_skillID = Column(Integer, ForeignKey(
"Hard_skills.Hard_skillID", onupdate="CASCADE", ondelete="CASCADE"), primary_key=True)
class JobsHard_skills(Base):
__tablename__ = 'JobsHard_skills'
JobID = Column(Integer, ForeignKey(
"Jobs.JobID", onupdate="CASCADE", ondelete="CASCADE"), primary_key=True)
Hard_skillID = Column(Integer, ForeignKey(
"Hard_skills.Hard_skillID", onupdate="CASCADE", ondelete="CASCADE"), primary_key=True)
class Responses(Base):
__tablename__ = 'Responses'
ResponseID = Column(Integer, primary_key=True, autoincrement=True)
StudentID = Column(Integer, ForeignKey(
"Students.StudentID", onupdate="CASCADE", ondelete="CASCADE"))
JobID = Column(Integer, ForeignKey(
"Jobs.JobID", onupdate="CASCADE", ondelete="CASCADE"))
Status = Column(String(50), nullable=True)
Comment = Column(String(700), nullable=True)
Link = Column(String(155), nullable=True)
__table_args__ = (
UniqueConstraint('StudentID', 'JobID', name='unique_student_job_for_responses'),
)
class Matches(Base):
__tablename__ = 'Matches'
StudentID = Column(Integer, ForeignKey(
"Students.StudentID", onupdate="CASCADE", ondelete="CASCADE"), primary_key=True)
JobID = Column(Integer, ForeignKey(
"Jobs.JobID", onupdate="CASCADE", ondelete="CASCADE"), primary_key=True)
Match = Column(Integer, nullable=False)

View File

@@ -0,0 +1,51 @@
from fastapi import APIRouter, Depends, status, HTTPException, Response
from fastapi.security.oauth2 import OAuth2PasswordRequestForm
from sqlalchemy.orm import Session
from .. import database, schemas, models, security, utils
router = APIRouter(tags=['Authentication'])
@router.post('/login', response_model=schemas.Token)
def login(user_credentials: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(database.get_db)):
user = db.query(models.Users).filter(
models.Users.Email == user_credentials.username).first()
if not user:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, detail="Invalid Credentials")
if not utils.verify(user_credentials.password, user.Hashed_password):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, detail="Invalid Credentials")
access_token = security.create_access_token(data={
"UserID": user.UserID,
"Email": user.Email
})
refresh_token = security.create_refresh_token(data={
"UserID": user.UserID,
})
return {"access_token": access_token, "refresh_token": refresh_token}
@router.post('/refresh', response_model=schemas.Token, response_model_exclude_none=True )
def refresh_access_token(refresh_token: str, db: Session = Depends(database.get_db)):
user_id = security.verify_refresh_token(refresh_token)
user = db.query(models.Users).filter(
models.Users.UserID == user_id).first()
if not user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
access_token = security.create_access_token(data={
"UserID": user.UserID,
"Email": user.Email
})
return {"access_token": access_token}

View File

@@ -0,0 +1,247 @@
from fastapi import status, HTTPException, Response, APIRouter, Depends, BackgroundTasks, Query
from sqlalchemy.orm import Session
from sqlalchemy import not_, func
from typing import List, Annotated
from ..database import get_db
from .. import models, schemas, security, crud
router = APIRouter(
prefix="/jobs",
dependencies=[Depends(security.verify_access_token)],
responses={401: {"description": "Unauthorized"}},
tags=['Jobs']
)
@router.get("/", response_model=List[schemas.JobGet])
def get_jobs(db: Session = Depends(get_db)):
jobs = db.query(models.Jobs).all()
if not jobs:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
response: List[schemas.JobGet] = []
for job in jobs:
hardskills = db.query(models.JobsHard_skills.Hard_skillID, models.Hard_skills.Title).\
join(models.Hard_skills, models.JobsHard_skills.Hard_skillID == models.Hard_skills.Hard_skillID).\
filter(models.JobsHard_skills.JobID == job.JobID).all()
Hardskills = [hardskill.Title for hardskill in hardskills]
response += [schemas.JobGet(JobID=job.JobID, UserID = job.UserID, Job_name=job.Job_name, Company_name=job.Company_name,
Link_to_job=job.Link_to_job, Year=job.Year, Qualification=job.Qualification, Time=job.Time,
Salary_after_interview=job.Salary_after_interview, Salary=job.Salary, Email=job.Email, Archive=job.Archive,
Responsibilities=job.Responsibilities, Hardskills=Hardskills)]
return response
@router.get("/{id}", response_model=schemas.JobGet)
def get_job(id: int, db: Session = Depends(get_db)):
job = db.query(models.Jobs).filter(models.Jobs.JobID == id).first()
if not job:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
hardskills = db.query(models.JobsHard_skills.Hard_skillID, models.Hard_skills.Title).\
join(models.Hard_skills, models.JobsHard_skills.Hard_skillID == models.Hard_skills.Hard_skillID).\
filter(models.JobsHard_skills.JobID == id).all()
Hardskills = [hardskill.Title for hardskill in hardskills]
response = schemas.JobGet(JobID=job.JobID, UserID = job.UserID, Job_name=job.Job_name, Company_name=job.Company_name,
Link_to_job=job.Link_to_job, Year=job.Year, Qualification=job.Qualification, Time=job.Time,
Salary_after_interview=job.Salary_after_interview, Salary=job.Salary, Email=job.Email, Archive=job.Archive,
Responsibilities=job.Responsibilities, Hardskills=Hardskills)
return response
@router.get("/matches/{id}", response_model=List[schemas.MatchJob])
def get_matches(id: int, db: Session = Depends(get_db)):
matches = db.query(models.Matches).filter(models.Matches.JobID == id).order_by(models.Matches.Match.desc()).all()
matches = db.query(
models.Students.StudentID,
models.Students.Name,
models.Students.Type,
models.Students.Faculties,
models.Students.Group,
models.Students.Year,
models.Students.Experience_specialty,
models.Students.Time,
models.Students.Soft_skills,
models.Students.Link,
models.Students.Email,
models.Students.Phone_number,
models.Matches.Match
).join(models.Matches, models.Students.StudentID == models.Matches.StudentID).\
filter(models.Matches.JobID == id).\
order_by(models.Matches.Match.desc()).\
all()
if not matches:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
return matches
@router.get("/hardskills/{id}", response_model=List[schemas.JobsHardskill])
def get_jobs_hardskills(id: int, db: Session = Depends(get_db)):
hardskills = db.query(models.JobsHard_skills.Hard_skillID, models.Hard_skills.Title).\
join(models.Hard_skills, models.JobsHard_skills.Hard_skillID == models.Hard_skills.Hard_skillID).\
filter(models.JobsHard_skills.JobID == id).all()
if not hardskills:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
return hardskills
@router.get("/students-search/", response_model=List[schemas.StudentUpdate])
def get_students(
year: int = None,
time: Annotated[list[str], Query()] = [],
hardskills: Annotated[list[str], Query()] = [],
faculties: str = None,
experience_specialty: bool = None,
db: Session = Depends(get_db)
):
if hardskills:
students = db.query(
models.Students.StudentID,
models.Students.Name,
models.Students.Type,
models.Students.Faculties,
models.Students.Group,
models.Students.Уear,
models.Students.Experience_specialty,
models.Students.Time,
models.Students.Soft_skills,
models.Students.Link,
models.Students.Email,
models.Students.Phone_number,
).join(models.StudentsHard_skills, models.Students.StudentID == models.StudentsHard_skills.StudentID).\
join(models.Hard_skills, models.StudentsHard_skills.Hard_skillID == models.Hard_skills.Hard_skillID).\
filter(models.Hard_skills.Title.in_(hardskills)).distinct(models.Students.StudentID)
else:
students = db.query(models.Students).filter(models.Students.Experience_specialty == experience_specialty)
if time:
students = students.filter(models.Students.Time.op("&&")(time))
if year:
students = students.filter(models.Students.Year >= year)
if faculties:
students = students.filter(models.Students.Faculties.ilike("%" + faculties + "%"))
response: List[schemas.StudentUpdate] = []
for student in students:
hardskills = db.query(models.StudentsHard_skills.Hard_skillID, models.Hard_skills.Title).\
join(models.Hard_skills, models.StudentsHard_skills.Hard_skillID == models.Hard_skills.Hard_skillID).\
filter(models.StudentsHard_skills.StudentID == student.StudentID).all()
Hardskills = [hardskill.Title for hardskill in hardskills]
response += [schemas.StudentUpdate(Name=student.Name, Type=student.Type, Faculties=student.Faculties,
Group=student.Group, Year=student.Year, Experience_specialty=student.Experience_specialty,
Time=student.Time, Soft_skills=student.Soft_skills, Link=student.Link, Email=student.Email,
Phone_number=student.Phone_number, Hardskills=Hardskills)]
return response
@router.get("/responses/{id}", response_model=List[schemas.Response])
def get_responses(id: int, db: Session = Depends(get_db)):
responses = db.query(models.Responses).filter(models.Responses.JobID == id).all()
if not responses:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
return responses
@router.get("/responses/", response_model=List[schemas.Response])
def get_responses_all_unprocessed(db: Session = Depends(get_db)):
responses = db.query(
models.Responses.ResponseID,
models.Responses.StudentID,
models.Responses.JobID,
models.Responses.Status,
models.Responses.Comment,
models.Responses.Link,
).join(models.Students, models.Students.StudentID == models.Responses.StudentID).\
filter(models.Responses.Status == "Ожидает рассмотрения").\
order_by(models.Students.Year.asc()).\
all() # order_by работате и на буквы в начале группы. Так, сейчас должен работать по идеи
if not responses:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
return responses
@router.post("/", status_code=status.HTTP_201_CREATED, response_model=schemas.Job)
def create_job(
job: schemas.JobCreate,
background_tasks: BackgroundTasks,
db: Session = Depends(get_db),
current_user: models.Users = Depends(security.get_current_user)
):
new_job = models.Jobs(UserID=current_user.UserID, **job.model_dump(exclude='Hardskills'))
db.add(new_job)
db.flush()
crud.update_hardskills(new_job.JobID, job.Hardskills, is_job=True, db=db)
if not new_job.Archive:
crud.update_matches(new_job.JobID, is_job=True, db=db, background_tasks=background_tasks)
crud.commit_transaction(db)
return new_job
@router.put("/{id}", response_model=schemas.Job)
def update_job(
id: int,
updated_job: schemas.JobUpdate,
background_tasks: BackgroundTasks,
db: Session = Depends(get_db),
current_user: models.Users = Depends(security.get_current_user)
):
job_query = db.query(models.Jobs).filter(models.Jobs.JobID == id)
job = job_query.first()
if not job:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
updated_data = updated_job.model_dump(exclude='Hardskills')
updated_data["UserID"] = current_user.UserID
job_query.update(updated_data, synchronize_session=False)
db.flush()
crud.update_hardskills(id, updated_job.Hardskills, is_job=True, db=db)
if not updated_job.Archive:
crud.update_matches(id, is_job=True, db=db, background_tasks=background_tasks)
else:
db.query(models.Matches).filter(models.Matches.JobID == id).delete()
db.flush()
crud.commit_transaction(db)
return job_query.first()
@router.put("/responses/{id}", status_code=status.HTTP_200_OK)
def update_response(id: int, updated_response: schemas.ResponseUpdate, background_tasks: BackgroundTasks, db: Session = Depends(get_db)):
updated_record = crud.update_record(models.Responses, id, updated_response.model_dump(), db, background_tasks)
return updated_record
@router.delete("/{id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_job(id: int, db: Session = Depends(get_db)):
job_query = db.query(models.Jobs).filter(models.Jobs.JobID == id)
job = job_query.first()
if not job:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
job_query.delete()
crud.commit_transaction(db)
return Response(status_code=status.HTTP_204_NO_CONTENT)

View File

@@ -0,0 +1,55 @@
from fastapi import status, HTTPException, APIRouter, Depends, UploadFile
from fastapi.responses import FileResponse
from sqlalchemy.orm import Session
from minio import Minio
from typing import List
from io import BytesIO
from os import remove
from starlette.background import BackgroundTask
from ..database import get_db
from ..storage import get_client
from .. import models, schemas, security
router = APIRouter(
prefix="/services",
dependencies=[Depends(security.verify_api_key)],
tags=['Services']
)
@router.get("/resume/{filename}")
def get_resume(filename: str, client: Minio = Depends(get_client)):
response = client.fget_object("tgjobs", filename, filename)
if not response:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
task = BackgroundTask(remove, filename)
return FileResponse(path=filename, filename=response.object_name, media_type=response.content_type, background=task)
@router.get("/hardskills/", response_model=List[schemas.Hard_skill])
def get_all_hardskills(db: Session = Depends(get_db)):
hardskills = db.query(models.Hard_skills).filter(models.Hard_skills.Title.ilike("%")).all()
if not hardskills:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
return hardskills
@router.get("/hardskills/{title}", response_model=List[schemas.Hard_skill])
def get_hardskills(title: str, db: Session = Depends(get_db)):
hardskills = db.query(models.Hard_skills).filter(models.Hard_skills.Title.ilike("%" + title + "%")).all()
if not hardskills:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
return hardskills
@router.post("/resume/")
async def upload_resume_to_cloud(file: UploadFile, client: Minio = Depends(get_client)):
response = client.put_object("tgjobs", file.filename, BytesIO(await file.read()), file.size, file.content_type)
if not response:
raise HTTPException(status_code=status.HTTP_409_CONFLICT)
return {"filename": response.object_name}

View File

@@ -0,0 +1,197 @@
from fastapi import status, HTTPException, Response, APIRouter, Depends, Query, BackgroundTasks
from sqlalchemy.orm import Session
from typing import List, Annotated
from ..database import get_db
from .. import models, schemas, security, crud
router = APIRouter(
prefix="/students",
dependencies=[Depends(security.verify_api_key)],
tags=['Students']
)
@router.get("/", response_model=List[schemas.Student])
def get_students(db: Session = Depends(get_db)):
students = db.query(models.Students)
return students
@router.get("/{id}", response_model=schemas.Student)
def get_student(id: int, db: Session = Depends(get_db)):
student = db.query(models.Students).filter(models.Students.StudentID == id).first()
if not student:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
return student
@router.get("/responses/{id}", response_model=List[schemas.Response])
def get_responses(id: int, db: Session = Depends(get_db)):
responses = db.query(models.Responses).filter(models.Responses.StudentID == id).all()
if not responses:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
return responses
@router.get("/matches/{id}", response_model=List[schemas.MatchStudent])
def get_matches(id: int, db: Session = Depends(get_db)):
matches = db.query(
models.Jobs.JobID,
models.Jobs.Company_name,
models.Jobs.Link_to_job,
models.Jobs.Job_name,
models.Jobs.Year,
models.Jobs.Qualification,
models.Jobs.Time,
models.Jobs.Salary_after_interview,
models.Jobs.Salary,
models.Jobs.Email,
models.Jobs.Responsibilities,
models.Matches.Match
).join(models.Matches, models.Jobs.JobID == models.Matches.JobID).\
filter(models.Matches.StudentID == id).\
order_by(models.Matches.Match.desc()).\
all()
if not matches:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
return matches
@router.get("/hardskills/{id}", response_model=List[schemas.StudentsHardskill])
def get_students_hardskills(id: int, db: Session = Depends(get_db)):
hardskills = db.query(models.StudentsHard_skills.Hard_skillID, models.Hard_skills.Title).\
join(models.Hard_skills, models.StudentsHard_skills.Hard_skillID == models.Hard_skills.Hard_skillID).\
filter(models.StudentsHard_skills.StudentID == id).all()
if not hardskills:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
return hardskills
@router.get("/jobs-search/", response_model=List[schemas.Jobs_search])
def get_jobs(year: str = None, qualification: bool = None, time: Annotated[list[str], Query()] = [], salary: int = None, hardskills: Annotated[list[str], Query()] = [], search: str = None, db: Session = Depends(get_db)):
if hardskills:
jobs = db.query(
models.Jobs.Job_name,
models.Jobs.Company_name,
models.Jobs.Link_to_job,
models.Jobs.Year,
models.Jobs.Qualification,
models.Jobs.Time,
models.Jobs.Salary,
models.Jobs.Salary_after_interview,
models.Jobs.Email,
models.Jobs.Responsibilities,
models.Jobs.JobID,
models.JobsHard_skills.JobID,
models.Hard_skills.Title
).join(models.JobsHard_skills, models.Jobs.JobID == models.JobsHard_skills.JobID).\
join(models.Hard_skills, models.JobsHard_skills.Hard_skillID == models.Hard_skills.Hard_skillID).\
filter(models.Hard_skills.Title.in_(hardskills), models.Jobs.Qualification == qualification, models.Jobs.Archive == False).distinct(models.Jobs.JobID)
else:
jobs = db.query(models.Jobs).filter(models.Jobs.Qualification == qualification, models.Jobs.Archive == False)
if year:
jobs = jobs.filter(models.Jobs.Year >= year)
if salary:
jobs = jobs.filter(models.Jobs.Salary >= salary)
if time:
jobs = jobs.filter(models.Jobs.Time.op("&&")(time))
if search:
jobs = jobs.filter(models.Jobs.Job_name.match(search))
return jobs.all()
@router.post("/", status_code=status.HTTP_201_CREATED, response_model=schemas.Student)
def create_student(student: schemas.StudentCreate, background_tasks: BackgroundTasks, db: Session = Depends(get_db)):
new_student = models.Students(**student.model_dump(exclude='Hardskills'))
db.add(new_student)
db.flush()
crud.update_hardskills(new_student.StudentID, student.Hardskills, is_job=False, db=db)
crud.update_matches(new_student.StudentID, is_job=False, db=db, background_tasks=background_tasks)
crud.commit_transaction(db)
return new_student
@router.post("/responses/", status_code=status.HTTP_201_CREATED, response_model=schemas.Response)
def add_to_responses(response: schemas.ResponseCreate, db: Session = Depends(get_db)):
new_response = models.Responses(**response.model_dump())
db.add(new_response)
crud.commit_transaction(db)
return new_response
@router.put("/{id}", response_model=schemas.Student)
def update_student(id: int, updated_student: schemas.StudentUpdate, background_tasks: BackgroundTasks, db: Session = Depends(get_db)):
student_query = db.query(models.Students).filter(models.Students.StudentID == id)
student = student_query.first()
if not student:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
student_query.update(updated_student.model_dump(exclude='Hardskills'))
db.flush()
crud.update_hardskills(id, updated_student.Hardskills, is_job=False, db=db)
crud.update_matches(id, is_job=False, db=db, background_tasks=background_tasks)
crud.commit_transaction(db)
return student_query.first()
@router.put("/responses/{id}", status_code=status.HTTP_200_OK)
def update_response(id: int, updated_response: schemas.ResponseUpdate, background_tasks: BackgroundTasks, db: Session = Depends(get_db)):
updated_record = crud.update_record(models.Responses, id, updated_response.model_dump(), db, background_tasks)
return updated_record
@router.patch("/{id}", status_code=status.HTTP_200_OK)
def update_students_link(id: int, Link: str, db: Session = Depends(get_db)):
student_query = db.query(models.Students).filter(models.Students.StudentID == id)
student = student_query.first()
if not student:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
student_query.update({models.Students.Link: Link})
db.flush()
crud.commit_transaction(db)
return student_query.first()
@router.delete("/{id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_student(id: int, db: Session = Depends(get_db)):
student_query = db.query(models.Students).filter(models.Students.StudentID == id)
student = student_query.first()
if not student:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
student_query.delete()
crud.commit_transaction(db)
return Response(status_code=status.HTTP_204_NO_CONTENT)
@router.delete("/responses/{id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_responses(id: int, db: Session = Depends(get_db)):
responses_query = db.query(models.Responses).filter(models.Responses.ResponseID == id)
response = responses_query.first()
if not response:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
responses_query.delete()
crud.commit_transaction(db)
return Response(status_code=status.HTTP_204_NO_CONTENT)

View File

@@ -0,0 +1,60 @@
from fastapi import FastAPI, status, HTTPException, Depends, APIRouter
from sqlalchemy.orm import Session
from .. import models, schemas, security, utils
from ..database import get_db
router = APIRouter(
prefix="/users",
tags=['Users']
)
@router.post("/", status_code=status.HTTP_201_CREATED, response_model=schemas.User)
def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
existing_user = db.query(models.Users).filter(models.Users.Email == user.Email).first()
if existing_user:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="User with this email already exists"
)
hashed_password = utils.hash(user.Hashed_password)
user_data = user.model_dump()
user_data["Hashed_password"] = hashed_password
new_user = models.Users(**user_data)
db.add(new_user)
db.commit()
db.refresh(new_user)
return new_user
@router.put("/{email}", response_model=schemas.User)
def update_user(email: str, updated_user: schemas.UserUpdate, db: Session = Depends(get_db), current_user: models.Users = Depends(security.get_current_user)):
if current_user.Email != email:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
existing_user = db.query(models.Users).filter(models.Users.Email == email).first()
if not existing_user:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
if updated_user.Email:
email_user = db.query(models.Users).filter(models.Users.Email == updated_user.Email).first()
if email_user and email_user.Email != email:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Email already in use")
if updated_user.Hashed_password:
hashed_password = utils.hash(updated_user.Hashed_password)
updated_user.Hashed_password = hashed_password
for key, value in updated_user.model_dump(exclude_unset=True).items():
setattr(existing_user, key, value)
db.commit()
db.refresh(existing_user)
return existing_user

View File

@@ -0,0 +1,146 @@
from pydantic import BaseModel
from datetime import datetime
from typing import List, Optional, Union
class StudentUpdate(BaseModel):
Name: str
Type: str
Faculties: str
Group: str
Course: Optional[int]
Experience_specialty: bool
Time: List[str]
Soft_skills: str
Link: str
Email: str
Phone_number: str
Hardskills: List[str]
class Student(BaseModel):
StudentID: int
Name: str
Type: str
Faculties: str
Group: str
Course: Optional[int]
Experience_specialty: bool
Time: List[str]
Soft_skills: str
Link: str
Email: str
Phone_number: str
class StudentCreate(Student):
Hardskills: List[str]
class MatchJob(Student):
Match: int
class Jobs_search(BaseModel):
JobID: int
Company_name: str
Link_to_job: Optional[str] = None
Job_name: str
Year: str
Qualification: bool
Time: List[str]
Salary_after_interview: bool
Salary: int
Email: str
Responsibilities: str
class MatchStudent(Jobs_search):
Match: int
class JobBase(BaseModel):
Company_name: str
Link_to_job: Optional[str] = None
Job_name: str
Year: str
Qualification: bool
Salary_after_interview: bool
Salary: int
Email: str
Archive: bool
Responsibilities: str
Time: List[str]
Hardskills: List[str]
class JobCreate(JobBase):
pass
class JobUpdate(JobBase):
pass
class Job(BaseModel):
JobID: Optional[int]
UserID: int
Company_name: Optional[str]
Link_to_job: Optional[str] = None
Job_name: str
Year: str
Qualification: bool
Time: List[str]
Salary_after_interview: bool
Salary: int
Email: str
Archive: bool
Responsibilities: str
class JobGet(Job):
Hardskills: List[str]
class ResponseCreate(BaseModel):
StudentID: int
JobID: int
Status: Optional[str] = "Ожидает рассмотрения"
Comment: Optional[str] = None
Link: Optional[str] = None
class ResponseUpdate(BaseModel):
Status: Optional[str] = "Ожидает рассмотрения"
Comment: Optional[str] = None
Link: Optional[str] = None
class Response(ResponseCreate):
ResponseID: int
class Hard_skill(BaseModel):
Hard_skillID: int
Title: str
class StudentsHardskillCreate(BaseModel):
Title: str
class StudentsHardskill(BaseModel):
Hard_skillID: int
Title: str
class JobsHardskillCreate(BaseModel):
Title: str
class JobsHardskill(BaseModel):
Hard_skillID: int
Title: str
class UserBase(BaseModel):
Email: Optional[str]
Hashed_password: Optional[str]
class UserCreate(UserBase):
pass
class UserUpdate(UserBase):
pass
class User(BaseModel):
Email: str
class Token(BaseModel):
access_token: str
refresh_token: Union[str, None] = None
token_type: str = "Bearer"
class TokenData(BaseModel):
UserID: int
Email: str

View File

@@ -0,0 +1,90 @@
from jose import JWTError, jwt
from datetime import datetime, timedelta
from . import schemas, database, models
from fastapi import Depends, status, HTTPException, Security
from fastapi.security import OAuth2PasswordBearer, APIKeyHeader
from sqlalchemy.orm import Session
from .config import settings
oauth2_scheme = OAuth2PasswordBearer(tokenUrl='login')
header_scheme = APIKeyHeader(name="X-API-KEY")
SECRET_KEY = settings.secret_key2
ALGORITHM = settings.algorithm
ACCESS_TOKEN_EXPIRE_MINUTES = settings.access_token_expire_minutes
REFRESH_TOKEN_EXPIRE_DAYS = settings.refresh_token_expire_days
X_API_KEY = settings.x_api_key
def verify_api_key(api_key_header: str = Security(header_scheme)):
if api_key_header == X_API_KEY:
return {"status": "OK"}
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
def create_access_token(data: dict):
to_encode = data.copy()
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode.update(
{"type": "access"})
to_encode.update(
{"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
def create_refresh_token(data: dict):
to_encode = data.copy()
expire = datetime.utcnow() + timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS)
to_encode.update(
{"type": "refresh"})
to_encode.update(
{"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
def decode_and_verify_token(token: str, expected_type: str):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
token_type: str = payload.get("type")
if token_type != expected_type:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=f"Invalid token type: expected {expected_type}, got {token_type}")
except JWTError:
raise credentials_exception
return payload
def verify_access_token(access_token: str = Depends(oauth2_scheme)):
payload = decode_and_verify_token(access_token, expected_type="access")
user_id: str = payload.get("UserID")
email: str = payload.get("Email")
token_data = schemas.TokenData(UserID=user_id, Email=email)
return token_data
def verify_refresh_token(refresh_token: str):
payload = decode_and_verify_token(refresh_token, expected_type="refresh")
user_id: str = payload.get("UserID")
return user_id
def get_current_user(token_data: schemas.TokenData = Depends(verify_access_token), db: Session = Depends(database.get_db)):
user = db.query(models.Users).filter(models.Users.UserID == token_data.UserID).first()
if user is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
return user

View File

@@ -0,0 +1,21 @@
from minio import Minio
from minio.error import S3Error
from .config import settings
def get_client():
try:
client = Minio(
endpoint=settings.endpoint,
secure=False,
access_key=settings.access_key,
secret_key=settings.secret_key
)
found = client.bucket_exists("tgjobs")
if not found:
client.make_bucket("tgjobs")
return client
except S3Error as exc:
print("error occurred.", exc)

View File

@@ -0,0 +1,8 @@
from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def hash(password: str):
return pwd_context.hash(password)
def verify(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)