View edits
This commit is contained in:
		
							parent
							
								
									1a3f5b7c1a
								
							
						
					
					
						commit
						5c207b4771
					
				
							
								
								
									
										12562
									
								
								logs/app.log
								
								
								
								
							
							
						
						
									
										12562
									
								
								logs/app.log
								
								
								
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -20,7 +20,9 @@ load_dotenv()
 | 
			
		|||
DATABASE_URL = os.getenv('DATABASE_URL')  # Должно быть "mysql+aiomysql://..." или "postgresql+asyncpg://..."
 | 
			
		||||
 | 
			
		||||
# Создаём асинхронный движок
 | 
			
		||||
async_engine = create_async_engine(DATABASE_URL, echo=True)
 | 
			
		||||
# async_engine = create_async_engine(DATABASE_URL, echo=True)
 | 
			
		||||
async_engine = create_async_engine(DATABASE_URL, echo=True, pool_recycle=1800, pool_size=10, pool_pre_ping=True )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Создаём фабрику сессий
 | 
			
		||||
async_session = sessionmaker(async_engine, class_=AsyncSession, expire_on_commit=False)
 | 
			
		||||
| 
						 | 
				
			
			@ -124,6 +126,7 @@ class Job(Base):
 | 
			
		|||
    active = Column(Boolean, default=True)  # Вакансия активна?
 | 
			
		||||
    about = Column(Text, nullable=True)
 | 
			
		||||
    date_posted = Column(DateTime) 
 | 
			
		||||
    data_requested = Column(DateTime, default=datetime.utcnow) 
 | 
			
		||||
    
 | 
			
		||||
    text = Column(Text, nullable=True)
 | 
			
		||||
    
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,6 +8,7 @@ dependencies = [
 | 
			
		|||
    "aiomysql>=0.2.0",
 | 
			
		||||
    "aiosqlite>=0.21.0",
 | 
			
		||||
    "alembic>=1.14.1",
 | 
			
		||||
    "beautifulsoup4>=4.13.3",
 | 
			
		||||
    "fastapi>=0.115.8",
 | 
			
		||||
    "jinja2>=3.1.5",
 | 
			
		||||
    "linkedin-api>=2.3.1",
 | 
			
		||||
| 
						 | 
				
			
			@ -16,6 +17,7 @@ dependencies = [
 | 
			
		|||
    "pymysql>=1.1.1",
 | 
			
		||||
    "python-dotenv>=1.0.1",
 | 
			
		||||
    "python-multipart>=0.0.20",
 | 
			
		||||
    "requests>=2.32.3",
 | 
			
		||||
    "schedule>=1.2.2",
 | 
			
		||||
    "sqlalchemy>=2.0.38",
 | 
			
		||||
    "uvicorn>=0.34.0",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,7 +7,7 @@ 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, del_jobs, add_jobs, get_applied_jobs
 | 
			
		||||
from utils.clients import upsert_client, del_jobs, add_jobs, get_applied_jobs, get_filtered_jobs
 | 
			
		||||
from typing import Union
 | 
			
		||||
 | 
			
		||||
import asyncio
 | 
			
		||||
| 
						 | 
				
			
			@ -87,6 +87,23 @@ async def client(data: JsonData, x_api_key: str = Header(...), db: Session = Dep
 | 
			
		|||
            print("Job Level:", ", ".join(job_level_values))
 | 
			
		||||
            print("Job Type:", ", ".join(job_type_values))
 | 
			
		||||
            print("Location Type:", ", ".join(location_type_values))
 | 
			
		||||
            
 | 
			
		||||
            # Пример использования функции
 | 
			
		||||
            jobs = await get_filtered_jobs(
 | 
			
		||||
                db,
 | 
			
		||||
                user_job_titles=["Electronics Engineer", "Hardware Engineer"],
 | 
			
		||||
                minimum_annual_salary=None,
 | 
			
		||||
                salary_currency=None,
 | 
			
		||||
                user_location_type=None,
 | 
			
		||||
                user_locations=["Burnaby, Canada", "Vancouver, Canada", "Toronto, Canada"],
 | 
			
		||||
                user_levels=["Mid", "Senior", "Manager"],
 | 
			
		||||
                user_job_types=["Full-time", "Permanent"]
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            # Выводим вакансии
 | 
			
		||||
            for job in jobs:
 | 
			
		||||
                print(job.job_title, job.location, job.job_level, job.job_type)
 | 
			
		||||
 | 
			
		||||
            
 | 
			
		||||
            # ads = await add_jobs(db, user_id)
 | 
			
		||||
| 
						 | 
				
			
			@ -101,7 +118,7 @@ async def client(data: JsonData, x_api_key: str = Header(...), db: Session = Dep
 | 
			
		|||
        # print(f"Неверный формат данных: {type(data.json_data)}")
 | 
			
		||||
        raise HTTPException(status_code=400, detail="Invalid data format")
 | 
			
		||||
 | 
			
		||||
    return {"message": "JSON получен", "data": 'get_jobs'}
 | 
			
		||||
    return {"message": "JSON получен", "data": jobs }
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										3356
									
								
								search_jobes2.json
								
								
								
								
							
							
						
						
									
										3356
									
								
								search_jobes2.json
								
								
								
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -6429,10 +6429,11 @@ github.com style (c) Vasily Polovnyov <vast@whiteants.net>
 | 
			
		|||
  background-color: #fff;
 | 
			
		||||
  background-color: rgba(255, 255, 255, var(--bg-opacity));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* background: linear-gradient(to bottom, #1C3FAA, #2B51B4);  7b52fc*/
 | 
			
		||||
@media (max-width: 1279px) {
 | 
			
		||||
  .login {
 | 
			
		||||
    background: linear-gradient(to bottom, #1C3FAA, #2B51B4);
 | 
			
		||||
    background: linear-gradient(to bottom, #7b52fc, #7b52fc);
 | 
			
		||||
    
 | 
			
		||||
    background-repeat: no-repeat;
 | 
			
		||||
    background-attachment: fixed;
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -6951,7 +6952,7 @@ select.input.input--lg {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
html {
 | 
			
		||||
  background: #1C3FAA;
 | 
			
		||||
  background: #7a61fe;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
body {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 26 KiB  | 
| 
						 | 
				
			
			@ -4,8 +4,8 @@
 | 
			
		|||
      <rect id="Rectangle_73" data-name="Rectangle 73" width="6395" height="1079" transform="translate(-5391)" fill="#fff"/>
 | 
			
		||||
    </clipPath>
 | 
			
		||||
    <linearGradient id="linear-gradient" x1="0.747" y1="0.222" x2="0.973" y2="0.807" gradientUnits="objectBoundingBox">
 | 
			
		||||
      <stop offset="0" stop-color="#2b51b4"/>
 | 
			
		||||
      <stop offset="1" stop-color="#1c3faa"/>
 | 
			
		||||
      <stop offset="0" stop-color="#7a61fe"/>
 | 
			
		||||
      <stop offset="1" stop-color="#7a61fe"/>
 | 
			
		||||
    </linearGradient>
 | 
			
		||||
  </defs>
 | 
			
		||||
  <g id="Mask_Group_1" data-name="Mask Group 1" transform="translate(5391)" clip-path="url(#clip-path)">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
		 Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB  | 
| 
						 | 
				
			
			@ -24,7 +24,7 @@
 | 
			
		|||
            <nav class="side-nav">
 | 
			
		||||
                <a href="/" class="intro-x flex items-center pl-5 pt-4">
 | 
			
		||||
                    <img alt="Midone Tailwind HTML Admin Template" class="w-6" src="/static/dist/images/logo.svg">
 | 
			
		||||
                    <span class="hidden xl:block text-white text-lg ml-3"> LI<span class="font-medium">NK</span> </span>
 | 
			
		||||
                    <span class="hidden xl:block text-white text-lg ml-3"> Turbo<span class="font-medium">Аpply</span> </span>
 | 
			
		||||
                </a>
 | 
			
		||||
                <div class="side-nav__devider my-6"></div>
 | 
			
		||||
                <ul>
 | 
			
		||||
| 
						 | 
				
			
			@ -39,7 +39,7 @@
 | 
			
		|||
                    <li>
 | 
			
		||||
                         <a href="/users"  class="side-menu {% if current_path.startswith('/users') %}side-menu--active{% endif %}">
 | 
			
		||||
                            <div class="side-menu__icon"> <i data-feather="users"></i> </div>
 | 
			
		||||
                            <div class="side-menu__title"> Users <i data-feather="chevron-down" class="side-menu__sub-icon"></i> </div>
 | 
			
		||||
                            <div class="side-menu__title"> Users </div>
 | 
			
		||||
                        </a>
 | 
			
		||||
                        <ul class="">
 | 
			
		||||
                    </li>
 | 
			
		||||
| 
						 | 
				
			
			@ -53,14 +53,14 @@
 | 
			
		|||
            <div class="content">
 | 
			
		||||
                <div class="top-bar">
 | 
			
		||||
                 <!-- BEGIN: Breadcrumb -->
 | 
			
		||||
                    <div class="-intro-x breadcrumb mr-auto hidden sm:flex"> <a href="" class="">Application</a> <i data-feather="chevron-right" class="breadcrumb__icon"></i> <a href="" class="breadcrumb--active">{{size}}</a> </div>
 | 
			
		||||
                    <div class="-intro-x breadcrumb mr-auto hidden sm:flex"> </div>
 | 
			
		||||
                    <!-- END: Breadcrumb -->
 | 
			
		||||
                    
 | 
			
		||||
                    
 | 
			
		||||
                    <!-- BEGIN: Account Menu -->
 | 
			
		||||
                    <div class="intro-x dropdown w-8 h-8 relative">
 | 
			
		||||
                        <div class="dropdown-toggle w-8 h-8 rounded-full overflow-hidden shadow-lg image-fit zoom-in">
 | 
			
		||||
                            <img alt="Midone Tailwind HTML Admin Template" src="/static/dist/images/profile-12.jpg">
 | 
			
		||||
                            <img alt="Midone Tailwind HTML Admin Template" src="/static/dist/images/333.png">
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="dropdown-box mt-10 absolute w-56 top-0 right-0 z-20">
 | 
			
		||||
                            <div class="dropdown-box__content box bg-theme-38 text-white">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -50,16 +50,16 @@ License: You must have a valid license purchased only from themeforest(the above
 | 
			
		|||
                <div class="hidden xl:flex flex-col min-h-screen">
 | 
			
		||||
                    <a href="" class="-intro-x flex items-center pt-5">
 | 
			
		||||
                        <img alt="Midone Tailwind HTML Admin Template" class="w-6" src="/static/dist/images/logo.svg">
 | 
			
		||||
                        <span class="text-white text-lg ml-3"> LI<span class="font-medium">NK</span> </span>
 | 
			
		||||
                        <span class="text-white text-lg ml-3"> Turbo<span class="font-medium">Аpply</span> </span>
 | 
			
		||||
                    </a>
 | 
			
		||||
                    <div class="my-auto">
 | 
			
		||||
                        <img alt="Midone Tailwind HTML Admin Template" class="-intro-x w-1/2 -mt-16" src="/static/dist/images/illustration.svg">
 | 
			
		||||
                        <div class="-intro-x text-white font-medium text-4xl leading-tight mt-10">
 | 
			
		||||
                            Admin panel
 | 
			
		||||
                            <br>
 | 
			
		||||
                            LINK.
 | 
			
		||||
                            TurboАpply
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="-intro-x mt-5 text-lg text-white">Manage all your e-commerce accounts in one place</div>
 | 
			
		||||
                        <div class="-intro-x mt-5 text-lg text-white">Data Handler Portal</div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <!-- END: Login Info -->
 | 
			
		||||
| 
						 | 
				
			
			@ -72,7 +72,7 @@ License: You must have a valid license purchased only from themeforest(the above
 | 
			
		|||
                        <div class="intro-x mt-2 text-gray-500 xl:hidden text-center">A few more clicks to sign in to your account. Manage all your e-commerce accounts in one place</div>
 | 
			
		||||
                        <div class="intro-x mt-8">
 | 
			
		||||
                            <form method="post" action="/login">
 | 
			
		||||
                            <input type="text" name="username" class="intro-x login__input input input--lg border border-gray-300 block" placeholder="Email">
 | 
			
		||||
                            <input type="text" name="username" class="intro-x login__input input input--lg border border-gray-300 block" placeholder="Login">
 | 
			
		||||
                            <input type="password" name="password"  class="intro-x login__input input input--lg border border-gray-300 block mt-4" placeholder="Password">
 | 
			
		||||
                         <div class="intro-x mt-5 xl:mt-8 text-center xl:text-left">
 | 
			
		||||
                            <button class="button button--lg w-full xl:w-32 text-white bg-theme-1 xl:mr-3" type="submit">Login</button>    
 | 
			
		||||
| 
						 | 
				
			
			@ -84,16 +84,14 @@ License: You must have a valid license purchased only from themeforest(the above
 | 
			
		|||
                                <input type="checkbox" class="input border mr-2" id="remember-me">
 | 
			
		||||
                                <label class="cursor-pointer select-none" for="remember-me">Remember me</label>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <a href="#">Forgot Password?</a> 
 | 
			
		||||
                            
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <!--<div class="intro-x mt-5 xl:mt-8 text-center xl:text-left">
 | 
			
		||||
                            <button class="button button--lg w-full xl:w-32 text-white bg-theme-1 xl:mr-3">Login</button>
 | 
			
		||||
                            <button class="button button--lg w-full xl:w-32 text-gray-700 border border-gray-300 mt-3 xl:mt-0">Sign up</button>
 | 
			
		||||
                        </div>-->
 | 
			
		||||
                        <div class="intro-x mt-10 xl:mt-24 text-gray-700 text-center xl:text-left">
 | 
			
		||||
                            By signin up, you agree to our 
 | 
			
		||||
                            <br>
 | 
			
		||||
                            <a class="text-theme-1" href="">Terms and Conditions</a> & <a class="text-theme-1" href="">Privacy Policy</a> 
 | 
			
		||||
                            
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,22 +6,26 @@
 | 
			
		|||
 | 
			
		||||
 <div class="intro-y flex flex-col sm:flex-row items-center mt-8">
 | 
			
		||||
                    <h2 class="text-lg font-medium mr-auto">
 | 
			
		||||
                        Datatable
 | 
			
		||||
                        Tasks
 | 
			
		||||
                    </h2>
 | 
			
		||||
                    
 | 
			
		||||
                    <button class="button text-white bg-theme-1 shadow-md mr-2" onclick="window.location.href='/productprom'">Unasigned Jobs</button>
 | 
			
		||||
                <button class="button text-white bg-theme-1 shadow-md mr-2" onclick="window.location.href='/productnoprom'">My Jobs</button>
 | 
			
		||||
 | 
			
		||||
                <button class="button text-white bg-theme-1 shadow-md mr-2" onclick="window.location.href='/productrozetka'">All Jobs</button>
 | 
			
		||||
                <button class="button text-white bg-theme-1 shadow-md mr-2" onclick="window.location.href='/productnorozetka'">Outstanding Jobs</button>
 | 
			
		||||
                </div>
 | 
			
		||||
                <!-- BEGIN: Datatable -->
 | 
			
		||||
                <div class="intro-y datatable-wrapper box p-5 mt-5">
 | 
			
		||||
                    <table class="table table-report table-report--bordered display datatable w-full">
 | 
			
		||||
                        <thead>
 | 
			
		||||
                            <tr>
 | 
			
		||||
                                <th class="border-b-2 whitespace-no-wrap">TITLE</th>
 | 
			
		||||
                                <th class="border-b-2 text-center whitespace-no-wrap">COMPANY</th>
 | 
			
		||||
                                <th class="border-b-2 text-center whitespace-no-wrap">CLIENT</th>
 | 
			
		||||
                                <th class="border-b-2 text-center whitespace-no-wrap">Requested on</th>
 | 
			
		||||
                                <th class="border-b-2 text-center whitespace-no-wrap">Posted on</th>
 | 
			
		||||
                                <th class="border-b-2 text-center whitespace-no-wrap">STATUS</th>
 | 
			
		||||
                                <th class="border-b-2 text-center whitespace-no-wrap">Assignee</th>
 | 
			
		||||
                                <th class="border-b-2 whitespace-no-wrap">TITLE ⨈</th>
 | 
			
		||||
                                <th class="border-b-2 text-center whitespace-no-wrap">COMPANY ⨈</th>
 | 
			
		||||
                                <th class="border-b-2 text-center whitespace-no-wrap">CLIENT ⨈</th>
 | 
			
		||||
                                <th class="border-b-2 text-center whitespace-no-wrap">Requested on ⨈</th>
 | 
			
		||||
                                <th class="border-b-2 text-center whitespace-no-wrap">Posted on ⨈</th>
 | 
			
		||||
                                <th class="border-b-2 text-center whitespace-no-wrap">STATUS ⨈</th>
 | 
			
		||||
                                <th class="border-b-2 text-center whitespace-no-wrap">Assignee ⨈</th>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                                <th class="border-b-2 text-center whitespace-no-wrap">Applied on</th>
 | 
			
		||||
| 
						 | 
				
			
			@ -42,7 +46,7 @@
 | 
			
		|||
                                </td>
 | 
			
		||||
 | 
			
		||||
                                <td class="text-center border-b">Requested on</td>
 | 
			
		||||
                                <td class="text-center border-b">Posted on</td>
 | 
			
		||||
                                <td class="text-center border-b">{{job.job.date_posted}}</td>
 | 
			
		||||
                                <td class="text-center border-b">
 | 
			
		||||
                                    <select class="select2" onchange="updateStatus(this, '{{ job.id }}')">
 | 
			
		||||
                                        <option value="Scheduled" {% if job.status == "Scheduled" %}selected{% endif %}>Scheduled</option>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -40,7 +40,7 @@
 | 
			
		|||
        <option value="admin">Admin</option>
 | 
			
		||||
    </select></div>
 | 
			
		||||
 
 | 
			
		||||
 <button type="submit" class="button bg-theme-1 text-white mt-5">Login</button>
 | 
			
		||||
 <button type="submit" class="button bg-theme-1 text-white mt-5">Register</button>
 | 
			
		||||
</form> 
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										125
									
								
								utils/app.py
								
								
								
								
							
							
						
						
									
										125
									
								
								utils/app.py
								
								
								
								
							| 
						 | 
				
			
			@ -10,7 +10,8 @@ import time
 | 
			
		|||
from utils.logging_setup import configure_global_logging
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import requests
 | 
			
		||||
from bs4 import BeautifulSoup
 | 
			
		||||
import asyncio
 | 
			
		||||
import json
 | 
			
		||||
from sqlalchemy.ext.asyncio import AsyncSession
 | 
			
		||||
| 
						 | 
				
			
			@ -26,6 +27,10 @@ load_dotenv()
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
headers = {
 | 
			
		||||
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
 | 
			
		||||
    "Accept-Language": "en-US,en;q=0.9",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# # Ваши учетные данные LinkedIn
 | 
			
		||||
| 
						 | 
				
			
			@ -49,13 +54,13 @@ def pars_jobs(geo):
 | 
			
		|||
    ]
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
    # 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"
 | 
			
		||||
    file_path = "search_jobes2.json"
 | 
			
		||||
    with open(file_path, "w", encoding="utf-8") as json_file:
 | 
			
		||||
        json.dump(search_jobs, json_file, indent=4, ensure_ascii=False)
 | 
			
		||||
        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_jobs, json_file, indent=4, ensure_ascii=False)
 | 
			
		||||
 | 
			
		||||
    print(f"Результаты успешно сохранены в {file_path}")
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -64,6 +69,25 @@ def add_to_bd():
 | 
			
		|||
    #[ ]: Написать функцию записи в БД
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def typess(url):
 | 
			
		||||
    response = requests.get(url, headers=headers)
 | 
			
		||||
    soup = BeautifulSoup(response.text, "html.parser")
 | 
			
		||||
 | 
			
		||||
    # Находим все элементы с описанием критериев вакансии
 | 
			
		||||
    criteria = soup.find_all("span", class_="description__job-criteria-text")
 | 
			
		||||
 | 
			
		||||
    if len(criteria) >= 2:
 | 
			
		||||
        level = criteria[0].get_text(strip=True)  # Уровень должности
 | 
			
		||||
        job_type = criteria[1].get_text(strip=True)  # Тип занятости
 | 
			
		||||
    else:
 | 
			
		||||
        level = "Не найдено"
 | 
			
		||||
        job_type = "Не найдено"
 | 
			
		||||
 | 
			
		||||
    # print(f"Job Level: {level}")
 | 
			
		||||
    # print(f"Type of Employment: {job_type}")
 | 
			
		||||
    return level, job_type
 | 
			
		||||
 | 
			
		||||
async def get_job(db: AsyncSession, job_id: str):
 | 
			
		||||
    try:
 | 
			
		||||
        jobs = api.get_job(job_id)
 | 
			
		||||
| 
						 | 
				
			
			@ -107,6 +131,9 @@ async def get_job(db: AsyncSession, job_id: str):
 | 
			
		|||
        # job.location_type = localized_name
 | 
			
		||||
        # job.entity_urn = entity_urn
 | 
			
		||||
        
 | 
			
		||||
        
 | 
			
		||||
        
 | 
			
		||||
        level, job_type = await typess(link)
 | 
			
		||||
        # Проверка, есть ли вакансия в базе
 | 
			
		||||
        query = select(Job).filter(Job.job_id == job_id)
 | 
			
		||||
        result = await db.execute(query)
 | 
			
		||||
| 
						 | 
				
			
			@ -123,6 +150,8 @@ async def get_job(db: AsyncSession, job_id: str):
 | 
			
		|||
            job.date_posted = listed_ats
 | 
			
		||||
            job.link_company = company_url
 | 
			
		||||
            job.location_type = localized_name
 | 
			
		||||
            job.job_level = level
 | 
			
		||||
            job.job_type = job_type
 | 
			
		||||
        else:
 | 
			
		||||
            logging.info(f"🆕 Добавление вакансии {job_id} в базу...")
 | 
			
		||||
            job = Job(
 | 
			
		||||
| 
						 | 
				
			
			@ -200,31 +229,19 @@ async def get_vakansi():
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# geo = '100025096'
 | 
			
		||||
geo = '100025096'
 | 
			
		||||
# pars_jobs(geo)
 | 
			
		||||
 | 
			
		||||
# pars_jobs(geo)
 | 
			
		||||
# get_vakansi()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# 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  # Выход после первого использования
 | 
			
		||||
#     await get_vakansi()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# # pars_jobs(geo)
 | 
			
		||||
 | 
			
		||||
# if __name__ == "__main__":
 | 
			
		||||
#     asyncio.run(main())
 | 
			
		||||
| 
						 | 
				
			
			@ -267,7 +284,7 @@ async def get_vakansi():
 | 
			
		|||
 | 
			
		||||
async def main():
 | 
			
		||||
    async for db in get_async_session():  # Асинхронный генератор сессий
 | 
			
		||||
        query = select(Job).filter(Job.active == 2)
 | 
			
		||||
        query = select(Job).filter(Job.active == 6)
 | 
			
		||||
        result = await db.execute(query)
 | 
			
		||||
        jobs = result.scalars().all()  # Получаем ВСЕ записи в виде списка
 | 
			
		||||
        
 | 
			
		||||
| 
						 | 
				
			
			@ -276,4 +293,60 @@ async def main():
 | 
			
		|||
            await get_job(db, job.job_id)
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    asyncio.run(main())
 | 
			
		||||
    asyncio.run(main())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# async def get_or_create_jobs(db: AsyncSession, job_id: int, titles: str):
 | 
			
		||||
#     """ Проверяет, существует ли запись, если нет — создаёт """
 | 
			
		||||
#     try:
 | 
			
		||||
#         query = select(Job).filter(Job.job_id == job_id)
 | 
			
		||||
#         result = await db.execute(query)
 | 
			
		||||
#         job = result.scalars().first()
 | 
			
		||||
 | 
			
		||||
#         if not job:
 | 
			
		||||
#             job = Job(
 | 
			
		||||
#                 job_id=job_id,
 | 
			
		||||
#                 job_title=titles
 | 
			
		||||
#             )
 | 
			
		||||
#             db.add(job)
 | 
			
		||||
#             await db.commit()
 | 
			
		||||
#             await db.refresh(job)
 | 
			
		||||
 | 
			
		||||
#         return job  # Возвращаем объект
 | 
			
		||||
 | 
			
		||||
#     except Exception as e:
 | 
			
		||||
#         await db.rollback()  # Откатываем транзакцию в случае ошибки
 | 
			
		||||
#         print(f"Ошибка при добавлении вакансии {job_id}: {e}")
 | 
			
		||||
#         return None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# async def get_vakansi():
 | 
			
		||||
#     """ Читает данные из JSON и записывает их в БД """
 | 
			
		||||
#     file_path = "search_jobes2.json"
 | 
			
		||||
    
 | 
			
		||||
#     try:
 | 
			
		||||
#         with open(file_path, "r", encoding="utf-8") as json_file:
 | 
			
		||||
#             data = json.load(json_file)
 | 
			
		||||
#     except Exception as e:
 | 
			
		||||
#         print(f"Ошибка чтения JSON: {e}")
 | 
			
		||||
#         return
 | 
			
		||||
    
 | 
			
		||||
#     async for session in get_async_session():  # Создаём сессию здесь!
 | 
			
		||||
#         for d in data:
 | 
			
		||||
#             title = d.get("title", "")
 | 
			
		||||
#             job_id = d.get("entityUrn", "")
 | 
			
		||||
 | 
			
		||||
#             if job_id:
 | 
			
		||||
#                 await get_or_create_jobs(session, int(job_id), title)  # Сохраняем в БД
 | 
			
		||||
#                 print(f"{title} {job_id}")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# async def main():
 | 
			
		||||
#     await get_vakansi()  # Здесь должен быть await!
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# if __name__ == "__main__":
 | 
			
		||||
#     asyncio.run(main())
 | 
			
		||||
| 
						 | 
				
			
			@ -120,4 +120,67 @@ async def get_applied_jobs(db, client_id: int):
 | 
			
		|||
            "jobLink": job.link
 | 
			
		||||
        })
 | 
			
		||||
    print(jobs_list)
 | 
			
		||||
    return jobs_list
 | 
			
		||||
    return jobs_list
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def get_filtered_jobs(db: AsyncSession, user_job_titles, minimum_annual_salary, salary_currency,
 | 
			
		||||
                            user_location_type, user_locations, user_levels, user_job_types): #
 | 
			
		||||
    # Строим фильтры для каждого из параметров пользователя
 | 
			
		||||
    filters = []
 | 
			
		||||
 | 
			
		||||
    # Фильтрация по job_title (примерное совпадение)
 | 
			
		||||
    if user_job_titles:
 | 
			
		||||
        title_filters = [Job.job_title.ilike(f"%{title}%") for title in user_job_titles]
 | 
			
		||||
        filters.append(or_(*title_filters))
 | 
			
		||||
 | 
			
		||||
    # Фильтрация по minimum_annual_salary (если указано)
 | 
			
		||||
    if minimum_annual_salary is not None:
 | 
			
		||||
        filters.append(Job.minimum_annual_salary >= minimum_annual_salary)
 | 
			
		||||
 | 
			
		||||
    # Фильтрация по salary_currency (если указано)
 | 
			
		||||
    if salary_currency is not None:
 | 
			
		||||
        filters.append(Job.salary_currency == salary_currency)
 | 
			
		||||
 | 
			
		||||
    # Фильтрация по location_type (если указано)
 | 
			
		||||
    if user_location_type:
 | 
			
		||||
        filters.append(Job.location_type == user_location_type)
 | 
			
		||||
 | 
			
		||||
    # Фильтрация по location (разделяем по каждому городу)
 | 
			
		||||
    if user_locations:
 | 
			
		||||
        location_filters = [Job.location.ilike(location) for location in user_locations]
 | 
			
		||||
        filters.append(or_(*location_filters))
 | 
			
		||||
 | 
			
		||||
    # Фильтрация по job_level (если указаны)
 | 
			
		||||
    if user_levels:
 | 
			
		||||
        filters.append(Job.job_level.in_(user_levels))
 | 
			
		||||
 | 
			
		||||
    # Фильтрация по job_type (если указаны)
 | 
			
		||||
    if user_job_types:
 | 
			
		||||
        filters.append(Job.job_type.in_(user_job_types))
 | 
			
		||||
 | 
			
		||||
    # Выполняем запрос с применением всех фильтров
 | 
			
		||||
    query = select(Job).filter(*filters)
 | 
			
		||||
 | 
			
		||||
    # Выполняем асинхронный запрос
 | 
			
		||||
    result = await db.execute(query)
 | 
			
		||||
 | 
			
		||||
    # Получаем все результаты
 | 
			
		||||
    jobs = result.scalars().all()
 | 
			
		||||
 | 
			
		||||
    return jobs
 | 
			
		||||
# # Пример использования функции
 | 
			
		||||
# jobs = get_filtered_jobs(
 | 
			
		||||
#     session=session,
 | 
			
		||||
#     user_job_titles=["Electronics Engineer", "Hardware Engineer"],
 | 
			
		||||
#     minimum_annual_salary=None,
 | 
			
		||||
#     salary_currency=None,
 | 
			
		||||
#     user_location_type=None,
 | 
			
		||||
#     user_locations=["Burnaby, Canada", "Vancouver, Canada", "Toronto, Canada"],
 | 
			
		||||
#     user_levels=["Mid", "Senior", "Manager"],
 | 
			
		||||
#     user_job_types=["Full-time", "Permanent"]
 | 
			
		||||
# )
 | 
			
		||||
 | 
			
		||||
# # Выводим вакансии
 | 
			
		||||
# for job in jobs:
 | 
			
		||||
#     print(job.job_title, job.location, job.job_level, job.job_type)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,69 @@
 | 
			
		|||
import sys
 | 
			
		||||
import os
 | 
			
		||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'model')))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
from sqlalchemy.ext.asyncio import AsyncSession
 | 
			
		||||
from sqlalchemy.future import select
 | 
			
		||||
from sqlalchemy.orm import joinedload
 | 
			
		||||
from sqlalchemy import or_
 | 
			
		||||
from sqlalchemy.exc import IntegrityError
 | 
			
		||||
from model.database import Client, AppliedJob, Job
 | 
			
		||||
 | 
			
		||||
def get_filtered_jobs(session: AsyncSession, user_job_titles, minimum_annual_salary, salary_currency,
 | 
			
		||||
                      user_location_type, user_locations, user_levels, user_job_types):
 | 
			
		||||
    # Строим фильтры для каждого из параметров пользователя
 | 
			
		||||
    filters = []
 | 
			
		||||
 | 
			
		||||
    # Фильтрация по job_title (примерное совпадение)
 | 
			
		||||
    if user_job_titles:
 | 
			
		||||
        filters.append(Job.job_title.in_(user_job_titles))
 | 
			
		||||
 | 
			
		||||
    # Фильтрация по minimum_annual_salary (если указано)
 | 
			
		||||
    if minimum_annual_salary is not None:
 | 
			
		||||
        filters.append(Job.minimum_annual_salary >= minimum_annual_salary)
 | 
			
		||||
 | 
			
		||||
    # Фильтрация по salary_currency (если указано)
 | 
			
		||||
    if salary_currency is not None:
 | 
			
		||||
        filters.append(Job.salary_currency == salary_currency)
 | 
			
		||||
 | 
			
		||||
    # Фильтрация по location_type (если указано)
 | 
			
		||||
    if user_location_type:
 | 
			
		||||
        filters.append(Job.location_type == user_location_type)
 | 
			
		||||
 | 
			
		||||
    # Фильтрация по location (разделяем по каждому городу)
 | 
			
		||||
    if user_locations:
 | 
			
		||||
        location_filters = [Job.location.ilike(location) for location in user_locations]
 | 
			
		||||
        filters.append(or_(*location_filters))
 | 
			
		||||
 | 
			
		||||
    # Фильтрация по job_level (если указаны)
 | 
			
		||||
    if user_levels:
 | 
			
		||||
        filters.append(Job.job_level.in_(user_levels))
 | 
			
		||||
 | 
			
		||||
    # Фильтрация по job_type (если указаны)
 | 
			
		||||
    if user_job_types:
 | 
			
		||||
        filters.append(Job.job_type.in_(user_job_types))
 | 
			
		||||
 | 
			
		||||
    # Выполняем запрос с применением всех фильтров
 | 
			
		||||
    query = session.query(Job).filter(*filters)
 | 
			
		||||
 | 
			
		||||
    # Получаем все результаты
 | 
			
		||||
    jobs = query.all()
 | 
			
		||||
 | 
			
		||||
    return jobs
 | 
			
		||||
 | 
			
		||||
# Пример использования функции
 | 
			
		||||
jobs = get_filtered_jobs(
 | 
			
		||||
    session=session,
 | 
			
		||||
    user_job_titles=["Electronics Engineer", "Hardware Engineer"],
 | 
			
		||||
    minimum_annual_salary=None,
 | 
			
		||||
    salary_currency=None,
 | 
			
		||||
    user_location_type=None,
 | 
			
		||||
    user_locations=["Burnaby, Canada", "Vancouver, Canada", "Toronto, Canada"],
 | 
			
		||||
    user_levels=["Mid", "Senior", "Manager"],
 | 
			
		||||
    user_job_types=["Full-time", "Permanent"]
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
# Выводим вакансии
 | 
			
		||||
for job in jobs:
 | 
			
		||||
    print(job.job_title, job.location, job.job_level, job.job_type)
 | 
			
		||||
							
								
								
									
										4
									
								
								uv.lock
								
								
								
								
							
							
						
						
									
										4
									
								
								uv.lock
								
								
								
								
							| 
						 | 
				
			
			@ -9,6 +9,7 @@ dependencies = [
 | 
			
		|||
    { name = "aiomysql" },
 | 
			
		||||
    { name = "aiosqlite" },
 | 
			
		||||
    { name = "alembic" },
 | 
			
		||||
    { name = "beautifulsoup4" },
 | 
			
		||||
    { name = "fastapi" },
 | 
			
		||||
    { name = "jinja2" },
 | 
			
		||||
    { name = "linkedin-api" },
 | 
			
		||||
| 
						 | 
				
			
			@ -17,6 +18,7 @@ dependencies = [
 | 
			
		|||
    { name = "pymysql" },
 | 
			
		||||
    { name = "python-dotenv" },
 | 
			
		||||
    { name = "python-multipart" },
 | 
			
		||||
    { name = "requests" },
 | 
			
		||||
    { name = "schedule" },
 | 
			
		||||
    { name = "sqlalchemy" },
 | 
			
		||||
    { name = "uvicorn" },
 | 
			
		||||
| 
						 | 
				
			
			@ -27,6 +29,7 @@ requires-dist = [
 | 
			
		|||
    { name = "aiomysql", specifier = ">=0.2.0" },
 | 
			
		||||
    { name = "aiosqlite", specifier = ">=0.21.0" },
 | 
			
		||||
    { name = "alembic", specifier = ">=1.14.1" },
 | 
			
		||||
    { name = "beautifulsoup4", specifier = ">=4.13.3" },
 | 
			
		||||
    { name = "fastapi", specifier = ">=0.115.8" },
 | 
			
		||||
    { name = "jinja2", specifier = ">=3.1.5" },
 | 
			
		||||
    { name = "linkedin-api", specifier = ">=2.3.1" },
 | 
			
		||||
| 
						 | 
				
			
			@ -35,6 +38,7 @@ requires-dist = [
 | 
			
		|||
    { name = "pymysql", specifier = ">=1.1.1" },
 | 
			
		||||
    { name = "python-dotenv", specifier = ">=1.0.1" },
 | 
			
		||||
    { name = "python-multipart", specifier = ">=0.0.20" },
 | 
			
		||||
    { name = "requests", specifier = ">=2.32.3" },
 | 
			
		||||
    { name = "schedule", specifier = ">=1.2.2" },
 | 
			
		||||
    { name = "sqlalchemy", specifier = ">=2.0.38" },
 | 
			
		||||
    { name = "uvicorn", specifier = ">=0.34.0" },
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue