This commit is contained in:
Xander 2025-03-25 21:36:47 +02:00
parent ab4454320a
commit a3723bcdd6
12 changed files with 33673 additions and 2005 deletions

18519
logs/app.log

File diff suppressed because it is too large Load Diff

13
main.py
View File

@ -3,7 +3,8 @@ from fastapi.responses import RedirectResponse
from fastapi.templating import Jinja2Templates
from fastapi.staticfiles import StaticFiles
from model.database import init_db
from routers import index, logins, users, product, profile, jobs, client
from routers import index, logins, users, product, profile, jobs, client, auth1
from fastapi.middleware.cors import CORSMiddleware
import logging
@ -23,6 +24,14 @@ app = FastAPI(title="API для turboapply",
# on_startup=[start_workers]
)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # Разрешить запросы отовсюду (для тестов)
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Настройка шаблонов
templates = Jinja2Templates(directory="templates")
@ -35,6 +44,8 @@ app.include_router(logins.router, tags=["login"], include_in_schema=False)
app.include_router(users.router, tags=["users"], include_in_schema=False)
app.include_router(product.router, tags=["product"], include_in_schema=False)
app.include_router(client.router, tags=["client"])
app.include_router(auth1.router, tags=["auth1"], include_in_schema=False)
# Подключение роутеров

View File

@ -1,7 +1,8 @@
from sqlalchemy import Column, Integer, String, Text, DateTime, Float, Boolean, BigInteger, ForeignKey, DateTime
from sqlalchemy.dialects.mysql import JSON
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from typing import AsyncGenerator
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from sqlalchemy.orm import sessionmaker, declarative_base
@ -50,9 +51,14 @@ async def init_db():
await session.commit()
# Зависимость FastAPI для работы с БД
async def get_async_session() -> AsyncSession:
# async def get_async_session() -> AsyncSession:
# async with async_session() as session:
# yield session
async def get_async_session() -> AsyncGenerator[AsyncSession, None]:
async with async_session() as session:
yield session
yield session # Сессия остается открытой во время запроса
# Функции для хеширования паролей
def hash_password(password: str) -> str:
@ -115,6 +121,7 @@ class Job(Base):
link = Column(String(2083), nullable=True) # URL вакансии
link_company = Column(String(2083), nullable=True) # URL компании
active = Column(Boolean, default=True) # Вакансия активна?
text = Column(Text, nullable=True)
# Связь с AppliedJobs
@ -129,6 +136,9 @@ class Client(Base):
user_login = Column(String(55), unique=True, index=True, nullable=False) # ✅ Правильно
user_nicename = Column(String(55), unique=True, index=True, nullable=True) # ✅ Правильно
user_email = Column(String(55), unique=True, index=True, nullable=True) # ✅ Правильно
phone = Column(String(55), unique=True, index=True, nullable=True) # ✅ Правильно
json_data = Column(Text, nullable=True)
# user_registered = Column(String(2083), nullable=True) # URL вакансии
user_status = Column(Boolean, default=True)

68
routers/auth1.py Normal file
View File

@ -0,0 +1,68 @@
from fastapi import FastAPI, Depends, HTTPException, status, Form, Response, Request, APIRouter
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select
from passlib.context import CryptContext
import jwt
from datetime import datetime, timedelta
from model.database import get_async_session, User
SECRET_KEY = "your_secret_key"
REFRESH_SECRET_KEY = "your_refresh_secret_key"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 1
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def create_access_token(data: dict, expires_delta: timedelta | None = None):
to_encode = data.copy()
expire = datetime.utcnow() + (expires_delta or timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES))
to_encode.update({"exp": expire})
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
async def authenticate_user(db: AsyncSession, username: str, password: str):
result = await db.execute(select(User).where(User.username == username))
user = result.scalars().first()
return user if user and pwd_context.verify(password, user.hashed_password) else None
router = APIRouter(prefix="/auth", tags=["auth"])
@router.post("/logins")
async def logins(username: str = Form(...), password: str = Form(...), db: AsyncSession = Depends(get_async_session)):
user = await authenticate_user(db, username, password)
if not user:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Неправильный логин или пароль")
access_token = create_access_token({"sub": user.username, "role": user.role})
refresh_token = create_access_token({"sub": user.username}, expires_delta=timedelta(days=7))
response = Response()
response.set_cookie(key="access_token", value=access_token, httponly=True)
response.set_cookie(key="refresh_token", value=refresh_token, httponly=True)
return {"message": "Успешный вход"}
@router.post("/logout")
async def logout(response: Response):
response.delete_cookie("access_token")
response.delete_cookie("refresh_token")
return {"message": "Вы вышли"}
@router.post("/refresh")
async def refresh_token(request: Request, response: Response):
refresh_token = request.cookies.get("refresh_token")
if not refresh_token:
raise HTTPException(status_code=401, detail="❌ Refresh token отсутствует")
try:
payload = jwt.decode(refresh_token, REFRESH_SECRET_KEY, algorithms=[ALGORITHM])
username = payload.get("sub")
if not username:
raise HTTPException(status_code=401, detail="❌ Ошибка токена")
except jwt.ExpiredSignatureError:
raise HTTPException(status_code=401, detail="❌ Refresh token истёк")
except jwt.InvalidTokenError:
raise HTTPException(status_code=401, detail="❌ Ошибка refresh токена")
new_access_token = create_access_token({"sub": username})
response.set_cookie(key="access_token", value=new_access_token, httponly=True)
return {"access_token": new_access_token}

View File

@ -1,9 +1,13 @@
from fastapi import FastAPI, HTTPException, APIRouter, Request, Header
from fastapi import FastAPI, HTTPException, APIRouter, Request, Header, Depends
from fastapi.responses import JSONResponse
from fastapi.templating import Jinja2Templates
from typing import Dict
from pydantic import BaseModel
from sqlalchemy.orm import Session
import json
from model.database import get_async_session, Client
from utils.clients import upsert_client
from typing import Union
router = APIRouter()
templates = Jinja2Templates(directory="templates")
@ -11,7 +15,7 @@ API_KEY = "4545454"
# Пример данных
clients = {
27: {"username": "John Doe", "email": "john@example.com", "phone": "+123456781"},
1: {"username": "John Doe", "email": "john@example.com", "phone": "+123456781"},
44: {"username": "Jane Smith", "email": "jane@example.com", "phone": "+987654321"}
}
@router.get("/get_client/{client_id}", include_in_schema=False)
@ -22,17 +26,36 @@ async def get_client_modal(client_id: int):
return JSONResponse(content=client)
class JsonData(BaseModel):
json_data: str
json_data: Union[dict, str]
@router.post("/client/")
async def client(data: JsonData, x_api_key: str = Header(None)):
# Проверяем API-ключ
async def client(data: JsonData, x_api_key: str = Header(...), db: Session = Depends(get_async_session)):
if x_api_key != "4545454":
raise HTTPException(status_code=403, detail="Invalid API Key")
# Если json_data строка — декодируем
if isinstance(data.json_data, str):
try:
data.json_data = json.loads(data.json_data)
except json.JSONDecodeError:
raise HTTPException(status_code=400, detail="Invalid JSON format")
print("Полученные данные:", data.json_data)
return {"message": "JSON получен", "data": "ok"}
try:
parsed_data = json.loads(data.json_data) # Декодируем JSON
print("Полученные данные:", parsed_data)
return {"message": "JSON получен", "data": parsed_data}
except json.JSONDecodeError as e:
raise HTTPException(status_code=400, detail=f"Invalid JSON: {str(e)}")
# first_name = parsed_data['first_name']
# last_name = parsed_data['last_name']
# email_addr = parsed_data['email_addr']
# user_id = parsed_data['user_id']
# phone_num = parsed_data['phone_num']
# print(first_name)
# # data = await upsert_client(db, first_name, last_name, email_addr, parsed_data)
# client = await upsert_client(db, user_id, first_name, last_name, email_addr, phone_num, str(parsed_data))
# print(f"Received data: data={client}")

View File

@ -52,7 +52,7 @@ async def login(
return Response(content="Неправильный логин или пароль", media_type="text/html")
access_token = create_access_token(data={"sub": username, "role": user.role})
response = RedirectResponse(url="/", status_code=303)
response = RedirectResponse(url="/product", status_code=303)
response.set_cookie(key="access_token", value=access_token, httponly=True)
return response

File diff suppressed because it is too large Load Diff

12002
search_jobes3.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -28,12 +28,7 @@
</a>
<div class="side-nav__devider my-6"></div>
<ul>
<li>
<a href="/" class="side-menu {% if current_path == '/' %}side-menu--active{% endif %}">
<div class="side-menu__icon"> <i data-feather="home"></i> </div>
<div class="side-menu__title"> Dashboard </div>
</a>
</li>
<li>
<a href="/product" class="side-menu {% if current_path.startswith('/product') %}side-menu--active{% endif %}">
<div class="side-menu__icon"> <i data-feather="credit-card"></i> </div>

View File

@ -31,7 +31,7 @@
<h2 class="text-lg font-medium mr-auto">Add Users</h2>
<form action="/users/create" method="post">
<div class="mt-3"> <label>Email</label> <input type="text" class="input w-full border mt-2" name="username" placeholder="username"> </div>
<div class="mt-3"> <label>Username</label> <input type="text" class="input w-full border mt-2" name="username" placeholder="username"> </div>
<div class="mt-3"> <label>Password</label> <input type="password" name="password"class="input w-full border mt-2" placeholder="secret"> </div>
<label>Role:</label>

View File

@ -47,10 +47,15 @@ def pars_jobs(geo):
}
for item in search_jobs
]
# print(search_job)
file_path = "search_jobes2.json"
# file_path = "search_jobes2.json"
# with open(file_path, "w", encoding="utf-8") as json_file:
# json.dump(search_jobes, json_file, indent=4, ensure_ascii=False)
file_path = "search_jobes3.json"
with open(file_path, "w", encoding="utf-8") as json_file:
json.dump(search_jobes, json_file, indent=4, ensure_ascii=False)
json.dump(search_jobs, json_file, indent=4, ensure_ascii=False)
print(f"Результаты успешно сохранены в {file_path}")
@ -59,31 +64,90 @@ def add_to_bd():
#[ ]: Написать функцию записи в БД
pass
def get_job(job_id):
jobs = api.get_job(job_id)
text = jobs['description']['text']
location = jobs['formattedLocation']
title = jobs['title']
listedAt = jobs['listedAt']
async def get_job(db: AsyncSession, job_id: str):
try:
jobs = api.get_job(job_id)
# Проверка наличия всех необходимых данных в ответе
required_keys = ["description", "formattedLocation", "title", "listedAt", "companyDetails"]
for key in required_keys:
if key not in jobs:
logging.error(f"❌ Ошибка: Ключ {key} отсутствует в API-ответе")
return None
# Извлечение данных
text = jobs['description']['text']
location = jobs['formattedLocation']
title = jobs['title']
listed_at = jobs['listedAt']
company_info = jobs.get("companyDetails", {}).get("com.linkedin.voyager.deco.jobs.web.shared.WebCompactJobPostingCompany", {}).get("companyResolutionResult", {})
company_name = company_info.get("name", "Unknown")
company_url = company_info.get("url", "")
link = f'https://www.linkedin.com/jobs/view/{job_id}/'
workplace_types = jobs.get("workplaceTypesResolutionResults", {})
# Преобразование из миллисекунд в секунды и конвертация
future_date = datetime.fromtimestamp(listedAt / 1000)
# Текущая дата
current_date = datetime.now()
# Разница в днях
difference = abs((future_date - current_date).days)
jobPostingId = jobs['jobPostingId']
localizedName = jobs['workplaceTypesResolutionResults']
# Извлекаем все localizedName
localized_names = [value['localizedName'] for value in localizedName.values()]
localized_name = ", ".join(localized_names)
# localized_name = workplaceTypesResolutionResults['urn:li:fs_workplaceType:2']['localizedName']
link = f'https://www.linkedin.com/jobs/view/{job_id}/'
names = jobs['companyDetails']['com.linkedin.voyager.deco.jobs.web.shared.WebCompactJobPostingCompany']['companyResolutionResult']['name']
url = jobs['companyDetails']['com.linkedin.voyager.deco.jobs.web.shared.WebCompactJobPostingCompany']['companyResolutionResult']['url']
if workplace_types:
first_key = next(iter(workplace_types)) # Берём первый (и единственный) ключ
workplace_data = workplace_types[first_key]
localized_name = workplace_data.get("localizedName", "Unknown")
entity_urn = workplace_data.get("entityUrn", "")
logging.info(f"🏢 Тип работы: {localized_name} ({entity_urn})")
else:
localized_name = "Unknown"
entity_urn = ""
# Теперь можно добавить эти значения в БД
# job.location_type = localized_name
# job.entity_urn = entity_urn
# Проверка, есть ли вакансия в базе
query = select(Job).filter(Job.job_id == job_id)
result = await db.execute(query)
job = result.scalars().first()
if job:
logging.info(f"🔄 Обновление вакансии {job_id} в базе...")
job.text = json.dumps(jobs)
job.link = link
job.location = location
job.job_company = company_name
job.link_company = company_url
job.location_type = localized_name
else:
logging.info(f"🆕 Добавление вакансии {job_id} в базу...")
job = Job(
job_id=job_id,
text=json.dumps(jobs),
link=link,
location=location,
job_company=company_name,
link_company=company_url
)
db.add(job)
# Коммит и обновление внутри активной сессии
await db.commit()
await db.refresh(job)
logging.info(f"✅ Вакансия {job_id} успешно сохранена")
return job
except Exception as e:
# await db.rollback() # Откат транзакции при ошибке
logging.error(f"❌ Ошибка при обработке вакансии {job_id}: {e}")
# return None
# except Exception as e:
# await db.rollback()
# logging.error(f"❌ Ошибка при обработке вакансии {job_id}: {e}")
# return None
# [ ]: job_level job_type hourly_rate найти minimum_annual_salary и salary_currency добавил description names Компании url на компанию
logging.info(f"title: {title}, location: {location}, jobPostingId: {jobPostingId}, difference: {difference}, location_type: {localized_name}, link: {link} ===== {names} {url}") #text:{text},
# logging.info(f"title: {title}, location: {location}, jobPostingId: {jobPostingId}, difference: {difference}, location_type: {localized_name}, link: {link} ===== {url}") #text:{text},
@ -132,41 +196,53 @@ async def get_vakansi():
geo = '100025096'
# geo = '100025096'
# pars_jobs(geo)
# def main():
# get_vakansi()
# logging.info("WORK")
# get_job('4130181356')
# async def main():
# # get_vakansi()
# # logging.info("WORK")
# # jobs =
# # async def main():
# async for db in get_async_session(): # Асинхронный генератор сессий
# query = select(Job).filter(Job.active == 2)
# result = await db.execute(query)
# job = result.scalars().first()
# for j in job:
# ids = j.job_id
# print(ids)
# # await get_job(db, '4192842821')
# # break # Выход после первого использования
# pars_jobs(geo)
# # pars_jobs(geo)
# main()
# if __name__ == "__main__":
# asyncio.run(main())
from sqlalchemy.orm import sessionmaker, declarative_base
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
# from sqlalchemy.orm import sessionmaker, declarative_base
# from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
DATABASE_URL = os.getenv('DATABASE_URL')
engine = create_async_engine(DATABASE_URL, echo=True)
# DATABASE_URL = os.getenv('DATABASE_URL')
# engine = create_async_engine(DATABASE_URL, echo=True)
async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
# async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
async_session_maker = sessionmaker(
engine, class_=AsyncSession, expire_on_commit=False
)
async def main():
await get_vakansi()
await engine.dispose() # Корректно закрываем соединение перед завершением
# async_session_maker = sessionmaker(
# engine, class_=AsyncSession, expire_on_commit=False
# )
# async def main():
# await get_vakansi()
# await engine.dispose() # Корректно закрываем соединение перед завершением
if __name__ == "__main__":
asyncio.run(main()) # 🚀 Запускаем программу в единственном event loop
# if __name__ == "__main__":
# asyncio.run(main()) # 🚀 Запускаем программу в единственном event loop
# [x] Обмен по времени не удалять main() что бы при старте сразу отрабатывала)
# Запуск функции каждые 5 минут
@ -184,3 +260,16 @@ if __name__ == "__main__":
# time.sleep(1) # Пауза между проверками
async def main():
async for db in get_async_session(): # Асинхронный генератор сессий
query = select(Job).filter(Job.active == 2)
result = await db.execute(query)
jobs = result.scalars().all() # Получаем ВСЕ записи в виде списка
for job in jobs:
print(job.job_id)
await get_job(db, job.job_id)
if __name__ == "__main__":
asyncio.run(main())

43
utils/clients.py Normal file
View File

@ -0,0 +1,43 @@
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select
from sqlalchemy.exc import IntegrityError
from model.database import Client
async def upsert_client(db: AsyncSession, user_id: str, user_login: str, user_nicename: str, user_email: str, phone: str, json_data: str):
# Проверяем, существует ли клиент с таким логином или email
async with db.begin():
result = await db.execute(
select(Client).filter((Client.id == user_id))# | (Client.user_email == user_email))
)
client = result.scalars().first() # Получаем первый результат или None
if client:
# Если клиент существует, обновляем его данные
client.user_nicename = user_nicename
client.phone = phone
client.json_data = json_data
# Можно добавить другие поля для обновления
try:
await db.commit() # Применяем изменения в базе данных
await db.refresh(client) # Обновляем объект в Python
return client
except IntegrityError:
await db.rollback() # В случае ошибки откатываем изменения
raise
else:
# Если клиента нет, создаем нового
new_client = Client(
user_login=user_login,
user_nicename=user_nicename,
user_email=user_email,
json_data=json_data,
)
db.add(new_client)
try:
await db.commit() # Сохраняем нового клиента в базе данных
await db.refresh(new_client) # Обновляем объект в Python
return new_client
except IntegrityError:
await db.rollback() # В случае ошибки откатываем изменения
raise