diff --git a/certbot/.well-known/acme-challenge/index.html b/certbot/.well-known/acme-challenge/index.html new file mode 100755 index 0000000..06efa73 --- /dev/null +++ b/certbot/.well-known/acme-challenge/index.html @@ -0,0 +1,10 @@ + + +
+ +Создайте файл templates/login.html
") +@app.get("/register", response_class=HTMLResponse) +async def get_register_page(): + """Отдельная страница регистрации""" + file_path = TEMPLATES_DIR / "register.html" + if not file_path.exists(): + return HTMLResponse(content="Создайте файл templates/register.html
") + return FileResponse(file_path) + + @app.get("/register", response_class=HTMLResponse) async def get_register(): """Страница регистрации""" @@ -476,6 +672,25 @@ async def get_resume_detail(resume_id: int): content=f"Создайте файл templates/resume_detail.html
") +@app.get("/favorites", response_class=HTMLResponse) +async def get_favorites_page(): + """Страница избранного""" + file_path = TEMPLATES_DIR / "favorites.html" + return FileResponse(file_path) if file_path.exists() else HTMLResponse(content="Создайте файл templates/favorites.html
") + + +@app.get("/applications", response_class=HTMLResponse) +async def get_applications_page(): + """Страница со списком откликов""" + file_path = TEMPLATES_DIR / "applications.html" + return FileResponse(file_path) if file_path.exists() else HTMLResponse(content="Создайте файл templates/applications.html
") + +@app.get("/application/{application_id}", response_class=HTMLResponse) +async def get_application_page(application_id: int): + """Детальная страница отклика""" + file_path = TEMPLATES_DIR / "application_detail.html" + return FileResponse(file_path) if file_path.exists() else HTMLResponse(content=f"Создайте файл templates/application_detail.html
") + @app.get("/admin", response_class=HTMLResponse) async def get_admin(): """Страница админки""" @@ -615,22 +830,41 @@ async def create_vacancy( @app.get("/api/vacancies", response_model=List[VacancyResponse]) async def get_my_vacancies(user_id: int = Depends(get_current_user)): - """Получение всех вакансий текущего пользователя""" + """Получение всех вакансий текущего пользователя с данными компании""" with get_db() as conn: cursor = conn.cursor() + + # Получаем компанию пользователя + cursor.execute("SELECT name FROM companies WHERE user_id = ?", (user_id,)) + company = cursor.fetchone() + company_name = company["name"] if company else None + cursor.execute(""" - SELECT v.*, u.full_name as company_name - FROM vacancies v - JOIN users u ON v.user_id = u.id - WHERE v.user_id = ? AND v.is_active = 1 - ORDER BY v.created_at DESC + SELECT * FROM vacancies + WHERE user_id = ? AND is_active = 1 + ORDER BY created_at DESC """, (user_id,)) vacancies = cursor.fetchall() result = [] for v in vacancies: vacancy_dict = dict(v) - vacancy_dict["tags"] = get_tags_for_vacancy(cursor, v["id"]) + # Добавляем название компании (либо из таблицы companies, либо имя пользователя) + if company_name: + vacancy_dict["company_name"] = company_name + else: + cursor.execute("SELECT full_name FROM users WHERE id = ?", (user_id,)) + user = cursor.fetchone() + vacancy_dict["company_name"] = user["full_name"] if user else None + + # Добавляем теги + cursor.execute(""" + SELECT t.* FROM tags t + JOIN vacancy_tags vt ON t.id = vt.tag_id + WHERE vt.vacancy_id = ? + """, (v["id"],)) + vacancy_dict["tags"] = [dict(tag) for tag in cursor.fetchall()] + result.append(vacancy_dict) return result @@ -640,18 +874,21 @@ async def get_my_vacancies(user_id: int = Depends(get_current_user)): async def get_all_vacancies( page: int = 1, search: str = None, - tags: str = None, # теги через запятую + tags: str = None, min_salary: int = None, sort: str = "newest" ): - """Получение всех вакансий с фильтрацией по тегам""" + """Получение всех вакансий с фильтрацией по тегам и данными компаний""" with get_db() as conn: cursor = conn.cursor() query = """ - SELECT DISTINCT v.*, u.full_name as company_name + SELECT DISTINCT + v.*, + COALESCE(c.name, u.full_name) as company_name FROM vacancies v JOIN users u ON v.user_id = u.id + LEFT JOIN companies c ON v.user_id = c.user_id WHERE v.is_active = 1 """ params = [] @@ -698,7 +935,15 @@ async def get_all_vacancies( result = [] for v in vacancies: vacancy_dict = dict(v) - vacancy_dict["tags"] = get_tags_for_vacancy(cursor, v["id"]) + + # Добавляем теги для каждой вакансии + cursor.execute(""" + SELECT t.* FROM tags t + JOIN vacancy_tags vt ON t.id = vt.tag_id + WHERE vt.vacancy_id = ? + """, (v["id"],)) + vacancy_dict["tags"] = [dict(tag) for tag in cursor.fetchall()] + result.append(vacancy_dict) # Получаем общее количество @@ -714,7 +959,7 @@ async def get_all_vacancies( @app.get("/api/vacancies/{vacancy_id}") async def get_vacancy(vacancy_id: int, token: Optional[str] = None): - """Получение конкретной вакансии""" + """Получение конкретной вакансии с данными компании""" user_id = get_current_user_optional(token) with get_db() as conn: @@ -723,10 +968,22 @@ async def get_vacancy(vacancy_id: int, token: Optional[str] = None): # Увеличиваем счетчик просмотров cursor.execute("UPDATE vacancies SET views = views + 1 WHERE id = ?", (vacancy_id,)) + # Получаем вакансию с данными компании cursor.execute(""" - SELECT v.*, u.full_name as company_name, u.email, u.telegram + SELECT + v.*, + COALESCE(c.name, u.full_name) as company_name, + c.description as company_description, + c.website as company_website, + c.logo as company_logo, + c.address as company_address, + c.phone as company_phone, + c.email as company_email, + u.email as user_email, + u.telegram as user_telegram FROM vacancies v JOIN users u ON v.user_id = u.id + LEFT JOIN companies c ON v.user_id = c.user_id WHERE v.id = ? """, (vacancy_id,)) @@ -735,7 +992,14 @@ async def get_vacancy(vacancy_id: int, token: Optional[str] = None): raise HTTPException(status_code=404, detail="Вакансия не найдена") result = dict(vacancy) - result["tags"] = get_tags_for_vacancy(cursor, vacancy_id) + + # Добавляем теги + cursor.execute(""" + SELECT t.* FROM tags t + JOIN vacancy_tags vt ON t.id = vt.tag_id + WHERE vt.vacancy_id = ? + """, (vacancy_id,)) + result["tags"] = [dict(tag) for tag in cursor.fetchall()] # Проверяем, откликался ли пользователь if user_id: @@ -981,6 +1245,656 @@ async def get_resume(resume_id: int): return resume_data +@app.get("/api/company", response_model=CompanyResponse) +async def get_my_company(user_id: int = Depends(get_current_user)): + """Получение информации о компании текущего пользователя""" + with get_db() as conn: + cursor = conn.cursor() + + # Проверяем, что пользователь - работодатель + cursor.execute("SELECT role FROM users WHERE id = ?", (user_id,)) + user = cursor.fetchone() + if not user or user["role"] != "employer": + raise HTTPException(status_code=403, detail="Только работодатели могут иметь компанию") + + cursor.execute("SELECT * FROM companies WHERE user_id = ?", (user_id,)) + company = cursor.fetchone() + + if not company: + # Если компании нет, возвращаем пустой объект с 404 + raise HTTPException(status_code=404, detail="Компания не найдена") + + return dict(company) + + +@app.post("/api/company", response_model=CompanyResponse) +async def create_or_update_company( + company: CompanyCreate, + user_id: int = Depends(get_current_user) +): + """Создание или обновление информации о компании""" + with get_db() as conn: + cursor = conn.cursor() + + # Проверяем, что пользователь - работодатель + cursor.execute("SELECT role FROM users WHERE id = ?", (user_id,)) + user = cursor.fetchone() + if not user or user["role"] != "employer": + raise HTTPException(status_code=403, detail="Только работодатели могут создавать компанию") + + # Проверяем существование компании + cursor.execute("SELECT id FROM companies WHERE user_id = ?", (user_id,)) + existing = cursor.fetchone() + + if existing: + # Обновление существующей компании + cursor.execute(""" + UPDATE companies + SET name = ?, description = ?, website = ?, logo = ?, + address = ?, phone = ?, email = ?, updated_at = CURRENT_TIMESTAMP + WHERE user_id = ? + """, (company.name, company.description, company.website, company.logo, + company.address, company.phone, company.email, user_id)) + + company_id = existing["id"] + else: + # Создание новой компании + cursor.execute(""" + INSERT INTO companies (user_id, name, description, website, logo, address, phone, email) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + """, (user_id, company.name, company.description, company.website, company.logo, + company.address, company.phone, company.email)) + + company_id = cursor.lastrowid + + conn.commit() + + cursor.execute("SELECT * FROM companies WHERE id = ?", (company_id,)) + return dict(cursor.fetchone()) + + +@app.get("/api/companies/{company_id}") +async def get_company_by_id(company_id: int): + """Получение информации о компании по ID (публичный эндпоинт)""" + with get_db() as conn: + cursor = conn.cursor() + cursor.execute(""" + SELECT c.*, u.full_name as owner_name + FROM companies c + JOIN users u ON c.user_id = u.id + WHERE c.id = ? + """, (company_id,)) + + company = cursor.fetchone() + if not company: + raise HTTPException(status_code=404, detail="Компания не найдена") + + return dict(company) + + +@app.get("/api/resumes/{resume_id}") +async def get_resume(resume_id: int): + """Получение конкретного резюме""" + with get_db() as conn: + cursor = conn.cursor() + + # Увеличиваем счетчик просмотров + cursor.execute("UPDATE resumes SET views = views + 1 WHERE id = ?", (resume_id,)) + + cursor.execute(""" + SELECT r.*, u.full_name, u.email, u.phone, u.telegram + FROM resumes r + JOIN users u ON r.user_id = u.id + WHERE r.id = ? + """, (resume_id,)) + + resume = cursor.fetchone() + if not resume: + raise HTTPException(status_code=404, detail="Резюме не найдено") + + resume_data = dict(resume) + + # Получаем опыт работы + cursor.execute("SELECT position, company, period FROM work_experience WHERE resume_id = ?", (resume_id,)) + resume_data["work_experience"] = [dict(exp) for exp in cursor.fetchall()] + + # Получаем образование + cursor.execute("SELECT institution, specialty, graduation_year FROM education WHERE resume_id = ?", + (resume_id,)) + resume_data["education"] = [dict(edu) for edu in cursor.fetchall()] + + # Получаем теги + cursor.execute(""" + SELECT t.* FROM tags t + JOIN resume_tags rt ON t.id = rt.tag_id + WHERE rt.resume_id = ? + """, (resume_id,)) + resume_data["tags"] = [dict(tag) for tag in cursor.fetchall()] + + conn.commit() + return resume_data + + +# ========== ЭНДПОИНТЫ ДЛЯ ИЗБРАННОГО ========== + +@app.post("/api/favorites") +async def add_to_favorites( + favorite: FavoriteCreate, + user_id: int = Depends(get_current_user) +): + """Добавление в избранное""" + with get_db() as conn: + cursor = conn.cursor() + + # Проверяем существование элемента + if favorite.item_type == 'vacancy': + cursor.execute("SELECT id FROM vacancies WHERE id = ? AND is_active = 1", (favorite.item_id,)) + else: # resume + cursor.execute("SELECT id FROM resumes WHERE id = ?", (favorite.item_id,)) + + if not cursor.fetchone(): + raise HTTPException(status_code=404, detail=f"{favorite.item_type} не найден") + + # Добавляем в избранное + try: + cursor.execute(""" + INSERT INTO favorites (user_id, item_type, item_id) + VALUES (?, ?, ?) + """, (user_id, favorite.item_type, favorite.item_id)) + conn.commit() + return {"message": "Добавлено в избранное"} + except sqlite3.IntegrityError: + raise HTTPException(status_code=400, detail="Уже в избранном") + + +@app.delete("/api/favorites") +async def remove_from_favorites( + favorite: FavoriteCreate, + user_id: int = Depends(get_current_user) +): + """Удаление из избранного""" + with get_db() as conn: + cursor = conn.cursor() + + cursor.execute(""" + DELETE FROM favorites + WHERE user_id = ? AND item_type = ? AND item_id = ? + """, (user_id, favorite.item_type, favorite.item_id)) + + if cursor.rowcount == 0: + raise HTTPException(status_code=404, detail="Не найдено в избранном") + + conn.commit() + return {"message": "Удалено из избранного"} + + +@app.get("/api/favorites") +async def get_favorites( + user_id: int = Depends(get_current_user), + item_type: Optional[str] = None +): + """Получение списка избранного""" + with get_db() as conn: + cursor = conn.cursor() + + query = "SELECT * FROM favorites WHERE user_id = ?" + params = [user_id] + + if item_type: + query += " AND item_type = ?" + params.append(item_type) + + query += " ORDER BY created_at DESC" + + cursor.execute(query, params) + favorites = cursor.fetchall() + + result = [] + for fav in favorites: + fav_dict = dict(fav) + + # Получаем дополнительные данные о элементе + if fav["item_type"] == 'vacancy': + cursor.execute(""" + SELECT v.*, COALESCE(c.name, u.full_name) as company_name + FROM vacancies v + JOIN users u ON v.user_id = u.id + LEFT JOIN companies c ON v.user_id = c.user_id + WHERE v.id = ? + """, (fav["item_id"],)) + item = cursor.fetchone() + + if item: + item_dict = dict(item) + # Получаем теги вакансии + cursor.execute(""" + SELECT t.name FROM tags t + JOIN vacancy_tags vt ON t.id = vt.tag_id + WHERE vt.vacancy_id = ? + """, (fav["item_id"],)) + tags = [t["name"] for t in cursor.fetchall()] + + fav_dict["item_data"] = { + "id": item["id"], + "title": item["title"], + "company": item_dict.get("company_name"), + "salary": item["salary"], + "tags": tags, + "url": f"/vacancy/{item['id']}" + } + + else: # resume + cursor.execute(""" + SELECT r.*, u.full_name + FROM resumes r + JOIN users u ON r.user_id = u.id + WHERE r.id = ? + """, (fav["item_id"],)) + item = cursor.fetchone() + + if item: + item_dict = dict(item) + # Получаем теги резюме + cursor.execute(""" + SELECT t.name FROM tags t + JOIN resume_tags rt ON t.id = rt.tag_id + WHERE rt.resume_id = ? + """, (fav["item_id"],)) + tags = [t["name"] for t in cursor.fetchall()] + + fav_dict["item_data"] = { + "id": item["id"], + "title": item_dict.get("desired_position", "Резюме"), + "name": item["full_name"], + "salary": item["desired_salary"], + "tags": tags, + "url": f"/resume/{item['id']}" + } + + result.append(fav_dict) + + return result + + +@app.get("/api/favorites/check/{item_type}/{item_id}") +async def check_favorite( + item_type: str, + item_id: int, + user_id: int = Depends(get_current_user) +): + """Проверка, находится ли элемент в избранном""" + with get_db() as conn: + cursor = conn.cursor() + cursor.execute(""" + SELECT id FROM favorites + WHERE user_id = ? AND item_type = ? AND item_id = ? + """, (user_id, item_type, item_id)) + + return {"is_favorite": cursor.fetchone() is not None} + + +@app.get("/api/user/stats") +async def get_user_stats(user_id: int = Depends(get_current_user)): + """Получение статистики пользователя (количество избранного, просмотров и т.д.)""" + with get_db() as conn: + cursor = conn.cursor() + + stats = {} + + # Количество избранных вакансий + cursor.execute(""" + SELECT COUNT(*) FROM favorites + WHERE user_id = ? AND item_type = 'vacancy' + """, (user_id,)) + stats["favorite_vacancies"] = cursor.fetchone()[0] + + # Количество избранных резюме + cursor.execute(""" + SELECT COUNT(*) FROM favorites + WHERE user_id = ? AND item_type = 'resume' + """, (user_id,)) + stats["favorite_resumes"] = cursor.fetchone()[0] + + # Общее количество избранного + stats["total_favorites"] = stats["favorite_vacancies"] + stats["favorite_resumes"] + + # Количество просмотров профиля (если есть резюме) + cursor.execute(""" + SELECT SUM(views) FROM resumes WHERE user_id = ? + """, (user_id,)) + stats["profile_views"] = cursor.fetchone()[0] or 0 + + # Количество созданных вакансий (для работодателя) + cursor.execute(""" + SELECT COUNT(*) FROM vacancies + WHERE user_id = ? AND is_active = 1 + """, (user_id,)) + stats["active_vacancies"] = cursor.fetchone()[0] + + return stats + + +# ========== ЭНДПОИНТЫ ДЛЯ ОТКЛИКОВ ========== + +@app.post("/api/applications") +async def create_application( + application: ApplicationCreate, + user_id: int = Depends(get_current_user) +): + """Создание отклика на вакансию""" + with get_db() as conn: + cursor = conn.cursor() + + # Проверяем, что пользователь - соискатель + cursor.execute("SELECT role FROM users WHERE id = ?", (user_id,)) + user = cursor.fetchone() + if not user or user["role"] != "employee": + raise HTTPException(status_code=403, detail="Только соискатели могут откликаться на вакансии") + + # Проверяем существование вакансии + cursor.execute(""" + SELECT v.*, u.id as employer_id + FROM vacancies v + JOIN users u ON v.user_id = u.id + WHERE v.id = ? AND v.is_active = 1 + """, (application.vacancy_id,)) + + vacancy = cursor.fetchone() + if not vacancy: + raise HTTPException(status_code=404, detail="Вакансия не найдена") + + # Проверяем, не откликался ли уже + cursor.execute(""" + SELECT id FROM applications + WHERE vacancy_id = ? AND user_id = ? + """, (application.vacancy_id, user_id)) + + if cursor.fetchone(): + raise HTTPException(status_code=400, detail="Вы уже откликались на эту вакансию") + + # Создаем отклик + cursor.execute(""" + INSERT INTO applications (vacancy_id, user_id, message) + VALUES (?, ?, ?) + """, (application.vacancy_id, user_id, application.message)) + + application_id = cursor.lastrowid + conn.commit() + + # Получаем созданный отклик + cursor.execute("SELECT * FROM applications WHERE id = ?", (application_id,)) + new_application = dict(cursor.fetchone()) + + return new_application + + +@app.get("/api/applications/received") +async def get_received_applications( + user_id: int = Depends(get_current_user), + status: Optional[str] = None, + page: int = 1, + limit: int = 20 +): + """Получение откликов на вакансии работодателя (входящие)""" + with get_db() as conn: + cursor = conn.cursor() + + # Проверяем, что пользователь - работодатель + cursor.execute("SELECT role FROM users WHERE id = ?", (user_id,)) + user = cursor.fetchone() + if not user or user["role"] != "employer": + raise HTTPException(status_code=403, detail="Только работодатели могут просматривать отклики") + + query = """ + SELECT + a.*, + v.title as vacancy_title, + v.salary as vacancy_salary, + u.full_name as user_name, + u.email as user_email, + u.phone as user_phone, + u.telegram as user_telegram + FROM applications a + JOIN vacancies v ON a.vacancy_id = v.id + JOIN users u ON a.user_id = u.id + WHERE v.user_id = ? + """ + params = [user_id] + + if status: + query += " AND a.status = ?" + params.append(status) + + # Пагинация + offset = (page - 1) * limit + query += " ORDER BY a.created_at DESC LIMIT ? OFFSET ?" + params.extend([limit, offset]) + + cursor.execute(query, params) + applications = cursor.fetchall() + + # Получаем общее количество + count_query = "SELECT COUNT(*) FROM applications a JOIN vacancies v ON a.vacancy_id = v.id WHERE v.user_id = ?" + count_params = [user_id] + + if status: + count_query += " AND a.status = ?" + count_params.append(status) + + cursor.execute(count_query, count_params) + total = cursor.fetchone()[0] + + result = [] + for app in applications: + app_dict = dict(app) + result.append(app_dict) + + return { + "applications": result, + "total": total, + "page": page, + "total_pages": (total + limit - 1) // limit + } + + +@app.get("/api/applications/sent") +async def get_sent_applications( + user_id: int = Depends(get_current_user), + status: Optional[str] = None, + page: int = 1, + limit: int = 20 +): + """Получение отправленных откликов (для соискателя)""" + with get_db() as conn: + cursor = conn.cursor() + + # Проверяем, что пользователь - соискатель + cursor.execute("SELECT role FROM users WHERE id = ?", (user_id,)) + user = cursor.fetchone() + if not user or user["role"] != "employee": + raise HTTPException(status_code=403, detail="Только соискатели могут просматривать свои отклики") + + query = """ + SELECT + a.*, + v.title as vacancy_title, + v.salary as vacancy_salary, + COALESCE(c.name, u_emp.full_name) as company_name, + u_emp.email as employer_email, + u_emp.telegram as employer_telegram + FROM applications a + JOIN vacancies v ON a.vacancy_id = v.id + JOIN users u_emp ON v.user_id = u_emp.id + LEFT JOIN companies c ON u_emp.id = c.user_id + WHERE a.user_id = ? + """ + params = [user_id] + + if status: + query += " AND a.status = ?" + params.append(status) + + # Пагинация + offset = (page - 1) * limit + query += " ORDER BY a.created_at DESC LIMIT ? OFFSET ?" + params.extend([limit, offset]) + + cursor.execute(query, params) + applications = cursor.fetchall() + + # Получаем общее количество + cursor.execute("SELECT COUNT(*) FROM applications WHERE user_id = ?", (user_id,)) + total = cursor.fetchone()[0] + + result = [] + for app in applications: + app_dict = dict(app) + result.append(app_dict) + + return { + "applications": result, + "total": total, + "page": page, + "total_pages": (total + limit - 1) // limit + } + + +@app.get("/api/applications/{application_id}") +async def get_application( + application_id: int, + user_id: int = Depends(get_current_user) +): + """Получение детальной информации об отклике""" + with get_db() as conn: + cursor = conn.cursor() + + # Получаем отклик с данными + cursor.execute(""" + SELECT + a.*, + v.title as vacancy_title, + v.description as vacancy_description, + v.salary as vacancy_salary, + v.contact as vacancy_contact, + u_applicant.full_name as applicant_name, + u_applicant.email as applicant_email, + u_applicant.phone as applicant_phone, + u_applicant.telegram as applicant_telegram, + u_employer.full_name as employer_name, + u_employer.email as employer_email, + u_employer.telegram as employer_telegram, + c.name as company_name, + c.description as company_description + FROM applications a + JOIN vacancies v ON a.vacancy_id = v.id + JOIN users u_applicant ON a.user_id = u_applicant.id + JOIN users u_employer ON v.user_id = u_employer.id + LEFT JOIN companies c ON u_employer.id = c.user_id + WHERE a.id = ? + """, (application_id,)) + + application = cursor.fetchone() + if not application: + raise HTTPException(status_code=404, detail="Отклик не найден") + + # Проверяем права доступа (либо автор отклика, либо владелец вакансии) + if application["user_id"] != user_id and application["v_user_id"] != user_id: + raise HTTPException(status_code=403, detail="Нет доступа к этому отклику") + + # Если просматривает работодатель и статус 'pending', меняем на 'viewed' + if application["v_user_id"] == user_id and application["status"] == 'pending': + cursor.execute(""" + UPDATE applications + SET status = 'viewed', viewed_at = CURRENT_TIMESTAMP + WHERE id = ? + """, (application_id,)) + conn.commit() + application = dict(application) + application["status"] = 'viewed' + + return dict(application) + + +@app.put("/api/applications/{application_id}/status") +async def update_application_status( + application_id: int, + status_update: ApplicationStatusUpdate, + user_id: int = Depends(get_current_user) +): + """Обновление статуса отклика (для работодателя)""" + with get_db() as conn: + cursor = conn.cursor() + + # Проверяем, что отклик существует и пользователь - владелец вакансии + cursor.execute(""" + SELECT a.*, v.user_id as employer_id + FROM applications a + JOIN vacancies v ON a.vacancy_id = v.id + WHERE a.id = ? + """, (application_id,)) + + application = cursor.fetchone() + if not application: + raise HTTPException(status_code=404, detail="Отклик не найден") + + if application["employer_id"] != user_id: + raise HTTPException(status_code=403, detail="Только владелец вакансии может изменять статус") + + # Обновляем статус + cursor.execute(""" + UPDATE applications + SET status = ?, response_message = ?, response_at = CURRENT_TIMESTAMP + WHERE id = ? + """, (status_update.status, status_update.response_message, application_id)) + + conn.commit() + + return {"message": "Статус обновлен"} + + +@app.get("/api/applications/stats") +async def get_application_stats(user_id: int = Depends(get_current_user)): + """Получение статистики по откликам""" + with get_db() as conn: + cursor = conn.cursor() + + # Проверяем роль пользователя + cursor.execute("SELECT role FROM users WHERE id = ?", (user_id,)) + user = cursor.fetchone() + + stats = {} + + if user["role"] == "employer": + # Статистика для работодателя (полученные отклики) + cursor.execute(""" + SELECT + COUNT(*) as total, + SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending, + SUM(CASE WHEN status = 'viewed' THEN 1 ELSE 0 END) as viewed, + SUM(CASE WHEN status = 'accepted' THEN 1 ELSE 0 END) as accepted, + SUM(CASE WHEN status = 'rejected' THEN 1 ELSE 0 END) as rejected + FROM applications a + JOIN vacancies v ON a.vacancy_id = v.id + WHERE v.user_id = ? + """, (user_id,)) + + else: + # Статистика для соискателя (отправленные отклики) + cursor.execute(""" + SELECT + COUNT(*) as total, + SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending, + SUM(CASE WHEN status = 'viewed' THEN 1 ELSE 0 END) as viewed, + SUM(CASE WHEN status = 'accepted' THEN 1 ELSE 0 END) as accepted, + SUM(CASE WHEN status = 'rejected' THEN 1 ELSE 0 END) as rejected + FROM applications + WHERE user_id = ? + """, (user_id,)) + + stats = dict(cursor.fetchone()) + return stats + + # ========== АДМИНСКИЕ ЭНДПОИНТЫ ========== @app.get("/api/admin/stats") @@ -1160,6 +2074,7 @@ async def get_user_contact( # Инициализация базы данных init_db() +set_initial_sequence_ids() if __name__ == "__main__": print("🚀 Запуск сервера на http://localhost:8000") diff --git a/templates/application_detail.html b/templates/application_detail.html new file mode 100644 index 0000000..c7aa237 --- /dev/null +++ b/templates/application_detail.html @@ -0,0 +1,358 @@ + + + + + +