mirror of
https://gitlab.com/MoonTestUse1/AdministrationItDepartmens.git
synced 2025-08-14 00:25:46 +02:00
Все подряд
This commit is contained in:
@@ -1,9 +1,17 @@
|
||||
"""Bot-specific configuration"""
|
||||
from ..config import settings
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
BOT_TOKEN = settings.bot_token
|
||||
CHAT_ID = settings.chat_id
|
||||
|
||||
# Telegram API credentials
|
||||
API_ID = os.getenv('TELEGRAM_API_ID')
|
||||
API_HASH = os.getenv('TELEGRAM_API_HASH')
|
||||
|
||||
# Request status constants
|
||||
class RequestStatus:
|
||||
"""Constants for request statuses"""
|
||||
|
@@ -1,45 +1,45 @@
|
||||
"""Employee management database operations"""
|
||||
from sqlalchemy.orm import Session
|
||||
from ..models import employee as models
|
||||
from ..schemas import tables
|
||||
from ..utils.auth import get_password_hash
|
||||
|
||||
from ..utils.loggers import auth_logger
|
||||
|
||||
def get_employee(db: Session, employee_id: int):
|
||||
"""Get employee by ID"""
|
||||
return db.query(tables.Employee).filter(tables.Employee.id == employee_id).first()
|
||||
|
||||
|
||||
def get_employee_by_lastname(db: Session, last_name: str):
|
||||
"""Get employee by last name"""
|
||||
return (
|
||||
db.query(tables.Employee).filter(tables.Employee.last_name == last_name).first()
|
||||
db.query(tables.Employee)
|
||||
.filter(tables.Employee.last_name == last_name)
|
||||
.first()
|
||||
)
|
||||
|
||||
|
||||
def get_employees(db: Session, skip: int = 0, limit: int = 100):
|
||||
return db.query(tables.Employee).offset(skip).limit(limit).all()
|
||||
|
||||
|
||||
def create_employee(db: Session, employee: models.EmployeeCreate):
|
||||
hashed_password = get_password_hash(employee.password)
|
||||
db_employee = tables.Employee(
|
||||
first_name=employee.first_name,
|
||||
last_name=employee.last_name,
|
||||
department=employee.department,
|
||||
office=employee.office,
|
||||
password=hashed_password,
|
||||
)
|
||||
db.add(db_employee)
|
||||
db.commit()
|
||||
db.refresh(db_employee)
|
||||
return db_employee
|
||||
|
||||
|
||||
def update_employee(db: Session, employee_id: int, data: dict):
|
||||
db_employee = get_employee(db, employee_id)
|
||||
if db_employee:
|
||||
for key, value in data.items():
|
||||
if key == "password":
|
||||
value = get_password_hash(value)
|
||||
setattr(db_employee, key, value)
|
||||
"""Create new employee"""
|
||||
try:
|
||||
hashed_password = get_password_hash(employee.password)
|
||||
db_employee = tables.Employee(
|
||||
first_name=employee.first_name,
|
||||
last_name=employee.last_name,
|
||||
department=employee.department,
|
||||
office=employee.office,
|
||||
password=hashed_password,
|
||||
)
|
||||
db.add(db_employee)
|
||||
db.commit()
|
||||
db.refresh(db_employee)
|
||||
return db_employee
|
||||
|
||||
auth_logger.info(
|
||||
"Employee created",
|
||||
extra={"employee_id": db_employee.id}
|
||||
)
|
||||
|
||||
return db_employee
|
||||
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
auth_logger.error(f"Error creating employee: {e}", exc_info=True)
|
||||
raise
|
@@ -1,119 +1,35 @@
|
||||
"""Request management database operations"""
|
||||
from sqlalchemy.orm import Session
|
||||
from ..models import request as models
|
||||
from ..schemas import tables
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
from ..schemas import tables
|
||||
from datetime import datetime
|
||||
|
||||
from ..utils.loggers import request_logger
|
||||
|
||||
def create_request(db: Session, request: models.RequestCreate):
|
||||
db_request = tables.Request(
|
||||
employee_id=request.employee_id,
|
||||
department=request.department,
|
||||
request_type=request.request_type,
|
||||
priority=request.priority,
|
||||
description=request.description,
|
||||
status="new",
|
||||
)
|
||||
db.add(db_request)
|
||||
db.commit()
|
||||
db.refresh(db_request)
|
||||
return db_request
|
||||
|
||||
|
||||
def get_requests(db: Session, skip: int = 0, limit: int = 100):
|
||||
requests = (
|
||||
db.query(
|
||||
tables.Request,
|
||||
tables.Employee.last_name.label("employee_last_name"),
|
||||
tables.Employee.first_name.label("employee_first_name"),
|
||||
)
|
||||
.join(tables.Employee)
|
||||
.offset(skip)
|
||||
.limit(limit)
|
||||
.all()
|
||||
)
|
||||
|
||||
return [
|
||||
{
|
||||
"id": req[0].id,
|
||||
"employee_id": req[0].employee_id,
|
||||
"department": req[0].department,
|
||||
"request_type": req[0].request_type,
|
||||
"priority": req[0].priority,
|
||||
"status": req[0].status,
|
||||
"description": req[0].description,
|
||||
"created_at": req[0].created_at,
|
||||
"employee_last_name": req[1],
|
||||
"employee_first_name": req[2],
|
||||
}
|
||||
for req in requests
|
||||
]
|
||||
|
||||
|
||||
def get_requests_by_employee_lastname(db: Session, last_name: str):
|
||||
requests = (
|
||||
db.query(
|
||||
tables.Request,
|
||||
tables.Employee.last_name.label("employee_last_name"),
|
||||
tables.Employee.first_name.label("employee_first_name"),
|
||||
)
|
||||
.join(tables.Employee)
|
||||
.filter(tables.Employee.last_name.ilike(f"%{last_name}%"))
|
||||
.all()
|
||||
)
|
||||
|
||||
return [
|
||||
{
|
||||
"id": req[0].id,
|
||||
"employee_id": req[0].employee_id,
|
||||
"department": req[0].department,
|
||||
"request_type": req[0].request_type,
|
||||
"priority": req[0].priority,
|
||||
"status": req[0].status,
|
||||
"description": req[0].description,
|
||||
"created_at": req[0].created_at,
|
||||
"employee_last_name": req[1],
|
||||
"employee_first_name": req[2],
|
||||
}
|
||||
for req in requests
|
||||
]
|
||||
|
||||
|
||||
def update_request_status(
|
||||
db: Session, request_id: int, new_status: models.RequestStatus
|
||||
):
|
||||
"""Create new request"""
|
||||
try:
|
||||
db_request = (
|
||||
db.query(tables.Request).filter(tables.Request.id == request_id).first()
|
||||
db_request = tables.Request(
|
||||
employee_id=request.employee_id,
|
||||
department=request.department,
|
||||
request_type=request.request_type,
|
||||
priority=request.priority,
|
||||
description=request.description,
|
||||
status="new",
|
||||
)
|
||||
if not db_request:
|
||||
return None
|
||||
|
||||
# Define valid status transitions
|
||||
valid_transitions = {
|
||||
models.RequestStatus.NEW: [models.RequestStatus.IN_PROGRESS],
|
||||
models.RequestStatus.IN_PROGRESS: [models.RequestStatus.RESOLVED],
|
||||
models.RequestStatus.RESOLVED: [models.RequestStatus.CLOSED],
|
||||
models.RequestStatus.CLOSED: [],
|
||||
}
|
||||
|
||||
current_status = models.RequestStatus(db_request.status)
|
||||
if new_status not in valid_transitions[current_status]:
|
||||
raise ValueError(
|
||||
f"Invalid status transition from {current_status} to {new_status}"
|
||||
)
|
||||
|
||||
db_request.status = new_status
|
||||
db.add(db_request)
|
||||
db.commit()
|
||||
db.refresh(db_request)
|
||||
|
||||
request_logger.info(
|
||||
"Request created",
|
||||
extra={"request_id": db_request.id}
|
||||
)
|
||||
|
||||
return db_request
|
||||
|
||||
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
raise e
|
||||
|
||||
request_logger.error(f"Error creating request: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
def get_request_details(db: Session, request_id: int):
|
||||
"""Get detailed request information including employee details"""
|
||||
@@ -129,6 +45,7 @@ def get_request_details(db: Session, request_id: int):
|
||||
|
||||
return {
|
||||
"id": request.id,
|
||||
"employee_id": request.employee_id,
|
||||
"employee_last_name": request.employee.last_name,
|
||||
"employee_first_name": request.employee.first_name,
|
||||
"department": request.department,
|
||||
@@ -137,71 +54,5 @@ def get_request_details(db: Session, request_id: int):
|
||||
"priority": request.priority,
|
||||
"description": request.description,
|
||||
"status": request.status,
|
||||
"created_at": request.created_at.isoformat(),
|
||||
}
|
||||
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
from ..schemas import tables
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def get_request_details(db: Session, request_id: int):
|
||||
"""Get detailed request information including employee details"""
|
||||
request = (
|
||||
db.query(tables.Request)
|
||||
.join(tables.Employee)
|
||||
.filter(tables.Request.id == request_id)
|
||||
.first()
|
||||
)
|
||||
|
||||
if not request:
|
||||
return None
|
||||
|
||||
return {
|
||||
"id": request.id,
|
||||
"employee_last_name": request.employee.last_name,
|
||||
"employee_first_name": request.employee.first_name,
|
||||
"department": request.department,
|
||||
"office": request.employee.office,
|
||||
"request_type": request.request_type,
|
||||
"priority": request.priority,
|
||||
"description": request.description,
|
||||
"status": request.status,
|
||||
"created_at": request.created_at.isoformat(),
|
||||
}
|
||||
|
||||
|
||||
def update_request_status(db: Session, request_id: int, new_status: str):
|
||||
"""Update request status with validation"""
|
||||
try:
|
||||
# Define valid status transitions
|
||||
valid_transitions = {
|
||||
"new": ["in_progress"],
|
||||
"in_progress": ["resolved"],
|
||||
"resolved": ["closed"],
|
||||
"closed": [],
|
||||
}
|
||||
|
||||
db_request = (
|
||||
db.query(tables.Request).filter(tables.Request.id == request_id).first()
|
||||
)
|
||||
if not db_request:
|
||||
return None
|
||||
|
||||
current_status = db_request.status
|
||||
if new_status not in valid_transitions.get(current_status, []):
|
||||
raise ValueError(
|
||||
f"Invalid status transition from {current_status} to {new_status}"
|
||||
)
|
||||
|
||||
db_request.status = new_status
|
||||
db.commit()
|
||||
db.refresh(db_request)
|
||||
|
||||
# Get full request details after update
|
||||
return get_request_details(db, request_id)
|
||||
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
raise e
|
||||
"created_at": request.created_at.isoformat()
|
||||
}
|
@@ -1,4 +1,5 @@
|
||||
"""Logging configuration for the application"""
|
||||
import sys
|
||||
|
||||
logging_config = {
|
||||
"version": 1,
|
||||
@@ -7,10 +8,6 @@ logging_config = {
|
||||
"default": {
|
||||
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
||||
"datefmt": "%Y-%m-%d %H:%M:%S"
|
||||
},
|
||||
"access": {
|
||||
"format": "%(asctime)s - %(name)s - %(levelname)s - %(client_addr)s - %(request_line)s - %(status_code)s",
|
||||
"datefmt": "%Y-%m-%d %H:%M:%S"
|
||||
}
|
||||
},
|
||||
"handlers": {
|
||||
@@ -18,37 +15,21 @@ logging_config = {
|
||||
"class": "logging.StreamHandler",
|
||||
"level": "INFO",
|
||||
"formatter": "default",
|
||||
"stream": "ext://sys.stdout"
|
||||
},
|
||||
"file": {
|
||||
"class": "logging.handlers.RotatingFileHandler",
|
||||
"level": "INFO",
|
||||
"formatter": "default",
|
||||
"filename": "logs/app.log",
|
||||
"maxBytes": 10485760, # 10MB
|
||||
"backupCount": 5
|
||||
},
|
||||
"access_file": {
|
||||
"class": "logging.handlers.RotatingFileHandler",
|
||||
"level": "INFO",
|
||||
"formatter": "access",
|
||||
"filename": "logs/access.log",
|
||||
"maxBytes": 10485760, # 10MB
|
||||
"backupCount": 5
|
||||
"stream": sys.stdout
|
||||
}
|
||||
},
|
||||
"loggers": {
|
||||
"": { # Root logger
|
||||
"handlers": ["console", "file"],
|
||||
"handlers": ["console"],
|
||||
"level": "INFO"
|
||||
},
|
||||
"app": { # Application logger
|
||||
"handlers": ["console", "file"],
|
||||
"handlers": ["console"],
|
||||
"level": "INFO",
|
||||
"propagate": False
|
||||
},
|
||||
"app.access": { # Access logger
|
||||
"handlers": ["access_file"],
|
||||
"handlers": ["console"],
|
||||
"level": "INFO",
|
||||
"propagate": False
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
from fastapi import FastAPI, Depends, HTTPException
|
||||
"""Main application module"""
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.openapi.docs import get_swagger_ui_html
|
||||
from fastapi.openapi.utils import get_openapi
|
||||
@@ -6,7 +7,7 @@ import logging
|
||||
from logging.config import dictConfig
|
||||
from .logging_config import logging_config
|
||||
from .middleware import LoggingMiddleware
|
||||
from .routers import auth, requests
|
||||
from .routers import auth, employees, requests
|
||||
|
||||
# Configure logging
|
||||
dictConfig(logging_config)
|
||||
@@ -34,6 +35,7 @@ app.add_middleware(LoggingMiddleware)
|
||||
|
||||
# Include routers
|
||||
app.include_router(auth.router, prefix="/api/auth", tags=["auth"])
|
||||
app.include_router(employees.router, prefix="/api/employees", tags=["employees"])
|
||||
app.include_router(requests.router, prefix="/api/requests", tags=["requests"])
|
||||
|
||||
# Custom OpenAPI documentation
|
||||
@@ -41,8 +43,7 @@ app.include_router(requests.router, prefix="/api/requests", tags=["requests"])
|
||||
async def custom_swagger_ui_html():
|
||||
return get_swagger_ui_html(
|
||||
openapi_url="/api/openapi.json",
|
||||
title="Support Portal API Documentation",
|
||||
swagger_favicon_url="/favicon.ico"
|
||||
title="Support Portal API Documentation"
|
||||
)
|
||||
|
||||
@app.get("/api/openapi.json", include_in_schema=False)
|
||||
|
@@ -8,13 +8,19 @@ logger = logging.getLogger("app.access")
|
||||
|
||||
class LoggingMiddleware(BaseHTTPMiddleware):
|
||||
async def dispatch(self, request: Request, call_next):
|
||||
"""Process request and log details"""
|
||||
start_time = time.time()
|
||||
|
||||
# Get client info safely
|
||||
client_host = "unknown"
|
||||
if request.client and hasattr(request.client, "host"):
|
||||
client_host = request.client.host
|
||||
|
||||
# Log request
|
||||
logger.info(
|
||||
"Request started1",
|
||||
"Request started",
|
||||
extra={
|
||||
"client_addr": request.client.host,
|
||||
"client_addr": client_host,
|
||||
"request_line": f"{request.method} {request.url.path}",
|
||||
"status_code": "PENDING"
|
||||
}
|
||||
@@ -29,7 +35,7 @@ class LoggingMiddleware(BaseHTTPMiddleware):
|
||||
logger.info(
|
||||
"Request completed",
|
||||
extra={
|
||||
"client_addr": request.client.host,
|
||||
"client_addr": client_host,
|
||||
"request_line": f"{request.method} {request.url.path}",
|
||||
"status_code": response.status_code,
|
||||
"process_time": f"{process_time:.2f}s"
|
||||
|
@@ -1,21 +1,20 @@
|
||||
from pydantic import BaseModel
|
||||
"""Employee models"""
|
||||
from pydantic import BaseModel, Field
|
||||
from datetime import datetime
|
||||
|
||||
from typing import Optional
|
||||
|
||||
class EmployeeBase(BaseModel):
|
||||
last_name: str
|
||||
first_name: str
|
||||
department: str
|
||||
office: str
|
||||
|
||||
first_name: str = Field(..., min_length=1)
|
||||
last_name: str = Field(..., min_length=1)
|
||||
department: str = Field(..., pattern="^(aho|gkh|general)$")
|
||||
office: str = Field(..., min_length=1)
|
||||
|
||||
class EmployeeCreate(EmployeeBase):
|
||||
password: str
|
||||
|
||||
password: str = Field(..., min_length=6)
|
||||
|
||||
class Employee(EmployeeBase):
|
||||
id: int
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
from_attributes = True
|
@@ -3,40 +3,37 @@ from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from ..database import get_db
|
||||
from ..crud import auth as auth_crud
|
||||
from ..models.employee import EmployeeBase
|
||||
from logging import getLogger
|
||||
from ..utils.loggers import auth_logger
|
||||
|
||||
router = APIRouter()
|
||||
logger = getLogger(__name__)
|
||||
|
||||
@router.post("/login")
|
||||
async def login(credentials: dict, db: Session = Depends(get_db)):
|
||||
"""Employee login endpoint"""
|
||||
try:
|
||||
# Validate required fields
|
||||
if not credentials.get("lastName") or not credentials.get("password"):
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Необходимо указать фамилию и пароль"
|
||||
)
|
||||
|
||||
employee = auth_crud.authenticate_employee(
|
||||
db,
|
||||
credentials.get("lastName"),
|
||||
credentials.get("password")
|
||||
credentials["lastName"],
|
||||
credentials["password"]
|
||||
)
|
||||
|
||||
if not employee:
|
||||
raise HTTPException(status_code=401, detail="Неверные учетные данные")
|
||||
raise HTTPException(
|
||||
status_code=401,
|
||||
detail="Неверные учетные данные"
|
||||
)
|
||||
|
||||
return employee
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Login error: {e}", exc_info=True)
|
||||
raise HTTPException(status_code=500, detail="Ошибка сервера")
|
||||
|
||||
@router.post("/admin")
|
||||
async def admin_login(credentials: dict, db: Session = Depends(get_db)):
|
||||
"""Admin login endpoint"""
|
||||
try:
|
||||
is_valid = auth_crud.authenticate_admin(
|
||||
db,
|
||||
credentials.get("username"),
|
||||
credentials.get("password")
|
||||
)
|
||||
if not is_valid:
|
||||
raise HTTPException(status_code=401, detail="Неверные учетные данные")
|
||||
return {"status": "success"}
|
||||
except Exception as e:
|
||||
logger.error(f"Admin login error: {e}", exc_info=True)
|
||||
auth_logger.error(f"Login error: {e}", exc_info=True)
|
||||
raise HTTPException(status_code=500, detail="Ошибка сервера")
|
@@ -1,6 +1,7 @@
|
||||
"""Employee management routes"""
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from ..database import get_db
|
||||
from ..crud import employees as employees_crud
|
||||
from ..models.employee import EmployeeCreate
|
||||
@@ -12,11 +13,20 @@ router = APIRouter()
|
||||
async def create_employee(employee: EmployeeCreate, db: Session = Depends(get_db)):
|
||||
"""Create new employee"""
|
||||
try:
|
||||
# Check if employee already exists
|
||||
existing = employees_crud.get_employee_by_lastname(db, employee.last_name)
|
||||
if existing:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Сотрудник с такой фамилией уже существует"
|
||||
)
|
||||
|
||||
db_employee = employees_crud.create_employee(db, employee)
|
||||
auth_logger.info(
|
||||
"Employee created",
|
||||
extra={"employee_id": db_employee.id}
|
||||
)
|
||||
|
||||
return {
|
||||
"id": db_employee.id,
|
||||
"firstName": db_employee.first_name,
|
||||
@@ -25,6 +35,14 @@ async def create_employee(employee: EmployeeCreate, db: Session = Depends(get_db
|
||||
"office": db_employee.office,
|
||||
"createdAt": db_employee.created_at
|
||||
}
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except IntegrityError:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Сотрудник с такими данными уже существует"
|
||||
)
|
||||
except Exception as e:
|
||||
auth_logger.error(f"Error creating employee: {e}", exc_info=True)
|
||||
raise HTTPException(status_code=500, detail="Ошибка при создании сотрудника")
|
@@ -3,6 +3,7 @@ from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from ..database import get_db
|
||||
from ..crud import requests as requests_crud
|
||||
from ..crud import employees as employees_crud
|
||||
from ..models.request import RequestCreate
|
||||
from ..bot.notifications import send_notification
|
||||
from ..utils.loggers import request_logger
|
||||
@@ -13,13 +14,27 @@ router = APIRouter()
|
||||
async def create_request(request: RequestCreate, db: Session = Depends(get_db)):
|
||||
"""Create new request"""
|
||||
try:
|
||||
# Verify employee exists
|
||||
employee = employees_crud.get_employee(db, request.employee_id)
|
||||
if not employee:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="Сотрудник не найден"
|
||||
)
|
||||
|
||||
db_request = requests_crud.create_request(db, request)
|
||||
request_logger.info(
|
||||
"Request created",
|
||||
extra={"request_id": db_request.id, "employee_id": request.employee_id}
|
||||
)
|
||||
await send_notification(requests_crud.get_request_details(db, db_request.id))
|
||||
|
||||
request_details = requests_crud.get_request_details(db, db_request.id)
|
||||
await send_notification(request_details)
|
||||
|
||||
return db_request
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
request_logger.error(
|
||||
f"Error creating request: {e}",
|
||||
|
Reference in New Issue
Block a user