From ef548e3dfd16af4d9ed6a0337af841686a698772 Mon Sep 17 00:00:00 2001 From: MoonTestUse1 Date: Sun, 2 Feb 2025 03:54:29 +0600 Subject: [PATCH] dip test --- .gitlab-ci.yml | 97 ++++++++++++++++++++++++--- README.md | 4 +- backend/Dockerfile.test | 2 +- backend/app/crud/employees.py | 68 ++++++++----------- backend/app/models/employee.py | 1 - backend/app/routers/auth.py | 88 ++++++++++++------------ backend/app/schemas/employee.py | 11 ++- frontend/src/views/AdminLoginView.vue | 2 +- 8 files changed, 165 insertions(+), 108 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a791b8d..0dde889 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,25 +2,100 @@ image: python:3.11 stages: - test + - build + - deploy + +variables: + SECRET_KEY: "your-super-secret-key-123" test-backend: + image: python:3.11 stage: test - script: + before_script: - python -V + - python -m pip install --upgrade pip + - pip install pytest pytest-cov + - pip install -r backend/requirements.txt + script: - cd backend - - pip install -r requirements.txt - - python -m pytest tests/test_health.py -v - - 'apk --no-cache add curl' - - '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 ' - tags: [] + - python -m pytest -v tests/test_health.py + only: + - main + - Testing test-frontend: - stage: test image: node:18 - script: + stage: test + before_script: - cd frontend - npm install + script: - npm run test - - 'apk --no-cache add curl' - - '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 ' - tags: [] \ No newline at end of file + only: + - main + - 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 \ No newline at end of file diff --git a/README.md b/README.md index 8503ae0..f249795 100644 --- a/README.md +++ b/README.md @@ -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) ## Description Project for managing administrative tasks -## CI/CD Status2 +## CI/CD Status - Last test run: Pending - Build status: Pending - Deploy status: Pending diff --git a/backend/Dockerfile.test b/backend/Dockerfile.test index c1ca9c2..aad1332 100644 --- a/backend/Dockerfile.test +++ b/backend/Dockerfile.test @@ -7,7 +7,7 @@ COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt RUN pip install pytest pytest-asyncio pytest-cov -# Копируем код приложения222 +# Копируем код приложения COPY . . # Ждем доступности базы данных и запускаем тесты diff --git a/backend/app/crud/employees.py b/backend/app/crud/employees.py index a0e04de..c87e356 100644 --- a/backend/app/crud/employees.py +++ b/backend/app/crud/employees.py @@ -1,9 +1,13 @@ -"""Employee CRUD operations2""" -from typing import Optional, Dict, Any +"""Employee CRUD operations""" from sqlalchemy.orm import Session - +from typing import List, Optional 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]: """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 ).first() -def get_employee_by_login(db: Session, login: str) -> Optional[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: +def create_employee(db: Session, employee: EmployeeCreate, hashed_password: str) -> Employee: """Create new employee""" - # Хешируем пароль - hashed_password = get_password_hash(employee_data["password"]) - - # Создаем сотрудника db_employee = Employee( - login=employee_data.get("login"), - first_name=employee_data["first_name"], - last_name=employee_data["last_name"], - department=employee_data["department"], - office=employee_data["office"], + first_name=employee.first_name, + last_name=employee.last_name, + department=employee.department, + office=employee.office, hashed_password=hashed_password, - is_admin=employee_data.get("is_admin", False) + is_admin=employee.is_admin ) db.add(db_employee) db.commit() db.refresh(db_employee) return db_employee -def update_employee(db: Session, employee: Employee, employee_data: Dict[str, Any]) -> Employee: - """Update employee""" - # Если есть пароль в данных, хешируем его - if "password" in employee_data: - employee_data["hashed_password"] = get_password_hash(employee_data.pop("password")) - - # Обновляем поля - for field, value in employee_data.items(): - setattr(employee, field, value) - - db.commit() - db.refresh(employee) - return employee +def update_employee(db: Session, employee_id: int, employee: EmployeeUpdate) -> Optional[Employee]: + """Update employee data""" + db_employee = get_employee(db, employee_id) + if db_employee: + for key, value in employee.dict(exclude_unset=True).items(): + setattr(db_employee, key, value) + db.commit() + db.refresh(db_employee) + return db_employee -def delete_employee(db: Session, employee: Employee) -> None: +def delete_employee(db: Session, employee_id: int) -> Optional[Employee]: """Delete employee""" - db.delete(employee) - db.commit() \ No newline at end of file + db_employee = get_employee(db, employee_id) + if db_employee: + db.delete(db_employee) + db.commit() + return db_employee \ No newline at end of file diff --git a/backend/app/models/employee.py b/backend/app/models/employee.py index 3e9a07c..c5dfc57 100644 --- a/backend/app/models/employee.py +++ b/backend/app/models/employee.py @@ -9,7 +9,6 @@ class Employee(Base): __tablename__ = "employees" id = Column(Integer, primary_key=True, index=True) - login = Column(String, unique=True, nullable=False, index=True) first_name = Column(String, nullable=False) last_name = Column(String, nullable=False) department = Column(String, nullable=False) diff --git a/backend/app/routers/auth.py b/backend/app/routers/auth.py index 6a92ae0..b522a9e 100644 --- a/backend/app/routers/auth.py +++ b/backend/app/routers/auth.py @@ -1,57 +1,20 @@ """Authentication router""" from fastapi import APIRouter, Depends, HTTPException, status -from fastapi.security import OAuth2PasswordRequestForm +from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm from sqlalchemy.orm import Session +from typing import Optional +from ..database import get_db from ..crud import employees -from ..schemas.token import Token -from ..utils.security import verify_password +from ..schemas.auth import Token, LoginCredentials +from ..utils.auth import verify_password from ..utils.jwt import create_and_save_token -from ..dependencies import get_db router = APIRouter() - -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" - } +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/login") @router.post("/login", response_model=Token) -async def login( +async def login_for_access_token( form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db) ): @@ -61,8 +24,8 @@ async def login( first_name, last_name = form_data.username.split() except ValueError: raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="Username must be in format: 'First Last'", + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Username should be in format: 'First Last'", headers={"WWW-Authenticate": "Bearer"}, ) @@ -83,3 +46,36 @@ async def login( "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" + } + diff --git a/backend/app/schemas/employee.py b/backend/app/schemas/employee.py index b6af1ca..a55d5e1 100644 --- a/backend/app/schemas/employee.py +++ b/backend/app/schemas/employee.py @@ -1,21 +1,19 @@ """Employee schemas""" +from pydantic import BaseModel +from datetime import datetime from typing import Optional -from pydantic import BaseModel, EmailStr class EmployeeBase(BaseModel): """Base employee schema""" - login: str first_name: str last_name: str department: str office: str - is_active: bool = True is_admin: bool = False class EmployeeCreate(EmployeeBase): """Employee creation schema""" password: str - hashed_password: Optional[str] = None class EmployeeUpdate(BaseModel): """Employee update schema""" @@ -23,13 +21,12 @@ class EmployeeUpdate(BaseModel): last_name: Optional[str] = None department: Optional[str] = None office: Optional[str] = None - password: Optional[str] = None - is_active: Optional[bool] = None - is_admin: Optional[bool] = None class Employee(EmployeeBase): """Employee schema""" id: int + is_active: bool + created_at: datetime class Config: """Pydantic config""" diff --git a/frontend/src/views/AdminLoginView.vue b/frontend/src/views/AdminLoginView.vue index 15d37e5..dbe2ec4 100644 --- a/frontend/src/views/AdminLoginView.vue +++ b/frontend/src/views/AdminLoginView.vue @@ -3,7 +3,7 @@

- Панель администратора1ыы + Панель администратора

Вход в систему управления