1
0
mirror of https://gitlab.com/MoonTestUse1/AdministrationItDepartmens.git synced 2025-08-14 00:25:46 +02:00
This commit is contained in:
MoonTestUse1
2025-02-02 03:54:29 +06:00
parent 8f4d3ae878
commit ef548e3dfd
8 changed files with 165 additions and 108 deletions

View File

@@ -2,25 +2,100 @@ image: python:3.11
stages: stages:
- test - test
- build
- deploy
variables:
SECRET_KEY: "your-super-secret-key-123"
test-backend: test-backend:
image: python:3.11
stage: test stage: test
script: before_script:
- python -V - python -V
- python -m pip install --upgrade pip
- pip install pytest pytest-cov
- pip install -r backend/requirements.txt
script:
- cd backend - cd backend
- pip install -r requirements.txt - python -m pytest -v tests/test_health.py
- python -m pytest tests/test_health.py -v only:
- 'apk --no-cache add curl' - main
- 'curl -X POST -H "Content-Type: application/json" -d "{\"chat_id\": \"<-1002453804964>\", \"text\": \"CI: new version was uploaded, see: https://preview.startup.com\"}" https://api.telegram.org/bot<7677506032:AAEVBSLTYoWJgyP3WH3elpj5muxc4sd9-AI>/sendMessage ' - Testing
tags: []
test-frontend: test-frontend:
stage: test
image: node:18 image: node:18
script: stage: test
before_script:
- cd frontend - cd frontend
- npm install - npm install
script:
- npm run test - npm run test
- 'apk --no-cache add curl' only:
- 'curl -X POST -H "Content-Type: application/json" -d "{\"chat_id\": \"<-1002453804964>\", \"text\": \"CI: new version was uploaded, see: https://preview.startup.com\"}" https://api.telegram.org/bot<7677506032:AAEVBSLTYoWJgyP3WH3elpj5muxc4sd9-AI>/sendMessage ' - main
tags: [] - Testing
build-backend:
stage: build
image: docker:latest
variables:
DOCKER_TLS_CERTDIR: ""
services:
- name: docker:dind
alias: docker
command: ["--tls=false"]
before_script:
- docker info
script:
- cd backend
- docker build -t backend:latest .
- docker save backend:latest > backend.tar
artifacts:
paths:
- backend/backend.tar
expire_in: 1 hour
only:
- main
build-frontend:
stage: build
image: docker:latest
variables:
DOCKER_TLS_CERTDIR: ""
services:
- name: docker:dind
alias: docker
command: ["--tls=false"]
before_script:
- docker info
script:
- cd frontend
- docker build -t frontend:latest .
- docker save frontend:latest > frontend.tar
artifacts:
paths:
- frontend/frontend.tar
expire_in: 1 hour
only:
- main
deploy:
stage: deploy
image: python:3.11
script:
- apt-get update -qy
- apt-get install -y sshpass
- sshpass -p "$SSH_PASSWORD" scp -o StrictHostKeyChecking=no backend/backend.tar frontend/frontend.tar docker-compose.yml root@185.139.70.62:/root/app/
- |
sshpass -p "$SSH_PASSWORD" ssh -o StrictHostKeyChecking=no root@185.139.70.62 "bash -c '
cd /root/app &&
docker load < backend.tar &&
docker load < frontend.tar &&
export SECRET_KEY=\"your-super-secret-key-123\" &&
/usr/bin/docker compose down &&
/usr/bin/docker compose up -d
'"
only:
- main
environment:
name: production

View File

@@ -1,11 +1,11 @@
#222 Administration Project2s # 22Administration Projectss
[![pipeline status](https://gitlab.com/mysite7215201/Administration/badges/main/pipeline.svg)](https://gitlab.com/mysite7215201/Administration/-/commits/main) [![pipeline status](https://gitlab.com/mysite7215201/Administration/badges/main/pipeline.svg)](https://gitlab.com/mysite7215201/Administration/-/commits/main)
## Description ## Description
Project for managing administrative tasks Project for managing administrative tasks
## CI/CD Status2 ## CI/CD Status
- Last test run: Pending - Last test run: Pending
- Build status: Pending - Build status: Pending
- Deploy status: Pending - Deploy status: Pending

View File

@@ -7,7 +7,7 @@ COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt RUN pip install --no-cache-dir -r requirements.txt
RUN pip install pytest pytest-asyncio pytest-cov RUN pip install pytest pytest-asyncio pytest-cov
# Копируем код приложения222 # Копируем код приложения
COPY . . COPY . .
# Ждем доступности базы данных и запускаем тесты # Ждем доступности базы данных и запускаем тесты

View File

@@ -1,9 +1,13 @@
"""Employee CRUD operations2""" """Employee CRUD operations"""
from typing import Optional, Dict, Any
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from typing import List, Optional
from ..models.employee import Employee from ..models.employee import Employee
from ..utils.security import get_password_hash from ..schemas.employee import EmployeeCreate, EmployeeUpdate
from ..utils.loggers import auth_logger
def get_employees(db: Session, skip: int = 0, limit: int = 100) -> List[Employee]:
"""Get all employees"""
return db.query(Employee).offset(skip).limit(limit).all()
def get_employee(db: Session, employee_id: int) -> Optional[Employee]: def get_employee(db: Session, employee_id: int) -> Optional[Employee]:
"""Get employee by ID""" """Get employee by ID"""
@@ -16,49 +20,35 @@ def get_employee_by_credentials(db: Session, first_name: str, last_name: str) ->
Employee.last_name == last_name Employee.last_name == last_name
).first() ).first()
def get_employee_by_login(db: Session, login: str) -> Optional[Employee]: def create_employee(db: Session, employee: EmployeeCreate, hashed_password: str) -> Employee:
"""Get employee by login"""
return db.query(Employee).filter(Employee.login == login).first()
def get_employees(db: Session, skip: int = 0, limit: int = 100):
"""Get list of employees"""
return db.query(Employee).offset(skip).limit(limit).all()
def create_employee(db: Session, employee_data: Dict[str, Any]) -> Employee:
"""Create new employee""" """Create new employee"""
# Хешируем пароль
hashed_password = get_password_hash(employee_data["password"])
# Создаем сотрудника
db_employee = Employee( db_employee = Employee(
login=employee_data.get("login"), first_name=employee.first_name,
first_name=employee_data["first_name"], last_name=employee.last_name,
last_name=employee_data["last_name"], department=employee.department,
department=employee_data["department"], office=employee.office,
office=employee_data["office"],
hashed_password=hashed_password, hashed_password=hashed_password,
is_admin=employee_data.get("is_admin", False) is_admin=employee.is_admin
) )
db.add(db_employee) db.add(db_employee)
db.commit() db.commit()
db.refresh(db_employee) db.refresh(db_employee)
return db_employee return db_employee
def update_employee(db: Session, employee: Employee, employee_data: Dict[str, Any]) -> Employee: def update_employee(db: Session, employee_id: int, employee: EmployeeUpdate) -> Optional[Employee]:
"""Update employee""" """Update employee data"""
# Если есть пароль в данных, хешируем его db_employee = get_employee(db, employee_id)
if "password" in employee_data: if db_employee:
employee_data["hashed_password"] = get_password_hash(employee_data.pop("password")) for key, value in employee.dict(exclude_unset=True).items():
setattr(db_employee, key, value)
# Обновляем поля
for field, value in employee_data.items():
setattr(employee, field, value)
db.commit() db.commit()
db.refresh(employee) db.refresh(db_employee)
return employee return db_employee
def delete_employee(db: Session, employee: Employee) -> None: def delete_employee(db: Session, employee_id: int) -> Optional[Employee]:
"""Delete employee""" """Delete employee"""
db.delete(employee) db_employee = get_employee(db, employee_id)
if db_employee:
db.delete(db_employee)
db.commit() db.commit()
return db_employee

View File

@@ -9,7 +9,6 @@ class Employee(Base):
__tablename__ = "employees" __tablename__ = "employees"
id = Column(Integer, primary_key=True, index=True) id = Column(Integer, primary_key=True, index=True)
login = Column(String, unique=True, nullable=False, index=True)
first_name = Column(String, nullable=False) first_name = Column(String, nullable=False)
last_name = Column(String, nullable=False) last_name = Column(String, nullable=False)
department = Column(String, nullable=False) department = Column(String, nullable=False)

View File

@@ -1,57 +1,20 @@
"""Authentication router""" """Authentication router"""
from fastapi import APIRouter, Depends, HTTPException, status from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from typing import Optional
from ..database import get_db
from ..crud import employees from ..crud import employees
from ..schemas.token import Token from ..schemas.auth import Token, LoginCredentials
from ..utils.security import verify_password from ..utils.auth import verify_password
from ..utils.jwt import create_and_save_token from ..utils.jwt import create_and_save_token
from ..dependencies import get_db
router = APIRouter() router = APIRouter()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/login")
ADMIN_LOGIN = "admin"
ADMIN_PASSWORD = "admin123"
@router.post("/admin/login", response_model=Token)
async def admin_login(
form_data: OAuth2PasswordRequestForm = Depends(),
db: Session = Depends(get_db)
):
"""Авторизация администратора"""
# Проверяем фиксированные учетные данные администратора
if form_data.username != ADMIN_LOGIN or form_data.password != ADMIN_PASSWORD:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
# Получаем или создаем админа в базе
admin = employees.get_employee_by_login(db, ADMIN_LOGIN)
if not admin:
# Если админа нет в базе, создаем его
admin = employees.create_employee(db, {
"login": ADMIN_LOGIN,
"first_name": "Admin",
"last_name": "User",
"department": "IT",
"office": "Main",
"password": ADMIN_PASSWORD,
"is_admin": True
})
# Создаем и сохраняем токен
access_token = create_and_save_token(admin.id, db)
return {
"access_token": access_token,
"token_type": "bearer"
}
@router.post("/login", response_model=Token) @router.post("/login", response_model=Token)
async def login( async def login_for_access_token(
form_data: OAuth2PasswordRequestForm = Depends(), form_data: OAuth2PasswordRequestForm = Depends(),
db: Session = Depends(get_db) db: Session = Depends(get_db)
): ):
@@ -61,8 +24,8 @@ async def login(
first_name, last_name = form_data.username.split() first_name, last_name = form_data.username.split()
except ValueError: except ValueError:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, status_code=status.HTTP_401_UNAUTHORIZED,
detail="Username must be in format: 'First Last'", detail="Username should be in format: 'First Last'",
headers={"WWW-Authenticate": "Bearer"}, headers={"WWW-Authenticate": "Bearer"},
) )
@@ -83,3 +46,36 @@ async def login(
"token_type": "bearer" "token_type": "bearer"
} }
@router.post("/admin/login", response_model=Token)
async def admin_login(
form_data: OAuth2PasswordRequestForm = Depends(),
db: Session = Depends(get_db)
):
"""Авторизация администратора"""
# Разделяем username на имя и фамилию
try:
first_name, last_name = form_data.username.split()
except ValueError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Username should be in format: 'First Last'",
headers={"WWW-Authenticate": "Bearer"},
)
# Проверяем учетные данные администратора
employee = employees.get_employee_by_credentials(db, first_name, last_name)
if not employee or not employee.is_admin or not verify_password(form_data.password, employee.hashed_password):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
# Создаем и сохраняем токен
access_token = create_and_save_token(employee.id, db)
return {
"access_token": access_token,
"token_type": "bearer"
}

View File

@@ -1,21 +1,19 @@
"""Employee schemas""" """Employee schemas"""
from pydantic import BaseModel
from datetime import datetime
from typing import Optional from typing import Optional
from pydantic import BaseModel, EmailStr
class EmployeeBase(BaseModel): class EmployeeBase(BaseModel):
"""Base employee schema""" """Base employee schema"""
login: str
first_name: str first_name: str
last_name: str last_name: str
department: str department: str
office: str office: str
is_active: bool = True
is_admin: bool = False is_admin: bool = False
class EmployeeCreate(EmployeeBase): class EmployeeCreate(EmployeeBase):
"""Employee creation schema""" """Employee creation schema"""
password: str password: str
hashed_password: Optional[str] = None
class EmployeeUpdate(BaseModel): class EmployeeUpdate(BaseModel):
"""Employee update schema""" """Employee update schema"""
@@ -23,13 +21,12 @@ class EmployeeUpdate(BaseModel):
last_name: Optional[str] = None last_name: Optional[str] = None
department: Optional[str] = None department: Optional[str] = None
office: Optional[str] = None office: Optional[str] = None
password: Optional[str] = None
is_active: Optional[bool] = None
is_admin: Optional[bool] = None
class Employee(EmployeeBase): class Employee(EmployeeBase):
"""Employee schema""" """Employee schema"""
id: int id: int
is_active: bool
created_at: datetime
class Config: class Config:
"""Pydantic config""" """Pydantic config"""

View File

@@ -3,7 +3,7 @@
<div class="max-w-md w-full bg-white rounded-xl shadow-2xl p-8"> <div class="max-w-md w-full bg-white rounded-xl shadow-2xl p-8">
<div class="text-center mb-8"> <div class="text-center mb-8">
<h2 class="text-3xl font-bold text-gray-900"> <h2 class="text-3xl font-bold text-gray-900">
Панель администратора1ыы Панель администратора
</h2> </h2>
<p class="mt-2 text-gray-600"> <p class="mt-2 text-gray-600">
Вход в систему управления Вход в систему управления