Compare commits
2 Commits
23027432b1
...
dc96d7a15a
Author | SHA1 | Date | |
---|---|---|---|
dc96d7a15a | |||
5c4d5bb5a2 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
|||||||
__pycache__
|
__pycache__
|
||||||
venv
|
venv
|
||||||
|
static
|
||||||
|
@ -8,11 +8,14 @@ RUN pip install --no-cache-dir -r requirements.txt
|
|||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Создаем директорию для хранения БД
|
# Создаем директории для хранения файлов
|
||||||
RUN mkdir -p /app/data
|
RUN mkdir -p /app/static/images
|
||||||
|
|
||||||
# Указываем порт, который будет слушать приложение
|
# Указываем порт, который будет слушать приложение
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
|
# Указываем том для хранения загруженных изображений
|
||||||
|
VOLUME /app/static/images
|
||||||
|
|
||||||
# Запускаем приложение
|
# Запускаем приложение
|
||||||
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "3000"]
|
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "3000"]
|
||||||
|
96
main.py
96
main.py
@ -1,4 +1,5 @@
|
|||||||
from fastapi import FastAPI, Depends, HTTPException, status, Query, Path
|
from fastapi import FastAPI, Depends, HTTPException, status, Query, Path, UploadFile, File, Form
|
||||||
|
from fastapi.staticfiles import StaticFiles
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
import crud
|
import crud
|
||||||
import models
|
import models
|
||||||
@ -6,12 +7,17 @@ import schemas
|
|||||||
from database import engine, get_db
|
from database import engine, get_db
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
import uvicorn
|
import uvicorn
|
||||||
|
from utils import save_image, delete_image
|
||||||
|
import json
|
||||||
|
|
||||||
# Создание таблиц в БД
|
# Создание таблиц в БД
|
||||||
models.Base.metadata.create_all(bind=engine)
|
models.Base.metadata.create_all(bind=engine)
|
||||||
|
|
||||||
app = FastAPI(title="AutoBro API", description="API для управления базой данных автомобилей")
|
app = FastAPI(title="AutoBro API", description="API для управления базой данных автомобилей")
|
||||||
|
|
||||||
|
# Добавляем обработку статических файлов
|
||||||
|
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||||
|
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
def read_root():
|
def read_root():
|
||||||
return {"message": "AutoBro API"}
|
return {"message": "AutoBro API"}
|
||||||
@ -40,38 +46,104 @@ def get_car(
|
|||||||
return {"car": car}
|
return {"car": car}
|
||||||
|
|
||||||
@app.post("/cars", response_model=schemas.CarResponse, status_code=status.HTTP_201_CREATED)
|
@app.post("/cars", response_model=schemas.CarResponse, status_code=status.HTTP_201_CREATED)
|
||||||
def create_car(
|
async def create_car(
|
||||||
car: schemas.CarCreate,
|
car_data: str = Form(..., description="Данные автомобиля в JSON формате"),
|
||||||
|
image: UploadFile = File(None, description="Изображение автомобиля"),
|
||||||
db: Session = Depends(get_db)
|
db: Session = Depends(get_db)
|
||||||
):
|
):
|
||||||
db_car = crud.create_car(db=db, car=car)
|
try:
|
||||||
return {"car": db_car}
|
# Преобразуем строку JSON в словарь
|
||||||
|
car_dict = json.loads(car_data)
|
||||||
|
|
||||||
|
# Загружаем изображение, если оно предоставлено
|
||||||
|
if image:
|
||||||
|
image_path = await save_image(image)
|
||||||
|
car_dict["image"] = image_path
|
||||||
|
|
||||||
|
# Создаем объект Pydantic для валидации данных
|
||||||
|
car = schemas.CarCreate(**car_dict)
|
||||||
|
|
||||||
|
# Создаем запись в БД
|
||||||
|
db_car = crud.create_car(db=db, car=car)
|
||||||
|
return {"car": db_car}
|
||||||
|
except ValueError as e:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail=str(e)
|
||||||
|
)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="Неверный формат JSON"
|
||||||
|
)
|
||||||
|
|
||||||
@app.put("/cars/{car_id}", response_model=schemas.CarResponse)
|
@app.put("/cars/{car_id}", response_model=schemas.CarResponse)
|
||||||
def update_car(
|
async def update_car(
|
||||||
car_id: int = Path(..., description="ID автомобиля", gt=0),
|
car_id: int = Path(..., description="ID автомобиля", gt=0),
|
||||||
car_update: schemas.CarUpdate = ...,
|
car_data: str = Form(None, description="Данные автомобиля в JSON формате"),
|
||||||
|
image: UploadFile = File(None, description="Изображение автомобиля"),
|
||||||
db: Session = Depends(get_db)
|
db: Session = Depends(get_db)
|
||||||
):
|
):
|
||||||
updated_car = crud.update_car(db=db, car_id=car_id, car_update=car_update)
|
# Проверяем существование автомобиля
|
||||||
if updated_car is None:
|
existing_car = crud.get_car(db, car_id=car_id)
|
||||||
|
if existing_car is None:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
detail="Автомобиль не найден"
|
detail="Автомобиль не найден"
|
||||||
)
|
)
|
||||||
return {"car": updated_car}
|
|
||||||
|
try:
|
||||||
|
# Преобразуем строку JSON в словарь, если она предоставлена
|
||||||
|
car_dict = {}
|
||||||
|
if car_data:
|
||||||
|
car_dict = json.loads(car_data)
|
||||||
|
|
||||||
|
# Загружаем новое изображение, если оно предоставлено
|
||||||
|
if image:
|
||||||
|
# Удаляем старое изображение, если есть
|
||||||
|
if existing_car.image:
|
||||||
|
delete_image(existing_car.image)
|
||||||
|
|
||||||
|
# Сохраняем новое изображение
|
||||||
|
image_path = await save_image(image)
|
||||||
|
car_dict["image"] = image_path
|
||||||
|
|
||||||
|
# Создаем объект Pydantic для валидации данных
|
||||||
|
car_update = schemas.CarUpdate(**car_dict)
|
||||||
|
|
||||||
|
# Обновляем запись в БД
|
||||||
|
updated_car = crud.update_car(db=db, car_id=car_id, car_update=car_update)
|
||||||
|
return {"car": updated_car}
|
||||||
|
except ValueError as e:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail=str(e)
|
||||||
|
)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="Неверный формат JSON"
|
||||||
|
)
|
||||||
|
|
||||||
@app.delete("/cars/{car_id}", status_code=status.HTTP_204_NO_CONTENT)
|
@app.delete("/cars/{car_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||||
def delete_car(
|
def delete_car(
|
||||||
car_id: int = Path(..., description="ID автомобиля", gt=0),
|
car_id: int = Path(..., description="ID автомобиля", gt=0),
|
||||||
db: Session = Depends(get_db)
|
db: Session = Depends(get_db)
|
||||||
):
|
):
|
||||||
success = crud.delete_car(db=db, car_id=car_id)
|
# Получаем автомобиль перед удалением
|
||||||
if not success:
|
car = crud.get_car(db, car_id=car_id)
|
||||||
|
if car is None:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
detail="Автомобиль не найден"
|
detail="Автомобиль не найден"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Удаляем изображение, если есть
|
||||||
|
if car.image:
|
||||||
|
delete_image(car.image)
|
||||||
|
|
||||||
|
# Удаляем запись из БД
|
||||||
|
crud.delete_car(db=db, car_id=car_id)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
53
utils.py
Normal file
53
utils.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import os
|
||||||
|
import uuid
|
||||||
|
from fastapi import UploadFile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Путь для сохранения изображений
|
||||||
|
IMAGES_DIR = Path("static/images")
|
||||||
|
|
||||||
|
# Разрешенные типы файлов
|
||||||
|
ALLOWED_EXTENSIONS = {".jpg", ".jpeg", ".png", ".gif", ".webp"}
|
||||||
|
|
||||||
|
async def save_image(file: UploadFile) -> str:
|
||||||
|
"""
|
||||||
|
Сохраняет изображение в директорию и возвращает относительный путь к нему
|
||||||
|
"""
|
||||||
|
if not file:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Получаем расширение файла
|
||||||
|
_, ext = os.path.splitext(file.filename)
|
||||||
|
if ext.lower() not in ALLOWED_EXTENSIONS:
|
||||||
|
raise ValueError(f"Неподдерживаемый формат файла. Поддерживаемые форматы: {', '.join(ALLOWED_EXTENSIONS)}")
|
||||||
|
|
||||||
|
# Создаем уникальное имя файла
|
||||||
|
filename = f"{uuid.uuid4()}{ext}"
|
||||||
|
file_path = IMAGES_DIR / filename
|
||||||
|
|
||||||
|
# Создаем директорию, если не существует
|
||||||
|
os.makedirs(IMAGES_DIR, exist_ok=True)
|
||||||
|
|
||||||
|
# Сохраняем файл
|
||||||
|
contents = await file.read()
|
||||||
|
with open(file_path, "wb") as f:
|
||||||
|
f.write(contents)
|
||||||
|
|
||||||
|
# Возвращаем относительный путь для хранения в базе данных
|
||||||
|
return f"/static/images/{filename}"
|
||||||
|
|
||||||
|
def delete_image(image_path: str) -> None:
|
||||||
|
"""
|
||||||
|
Удаляет файл изображения, если оно существует
|
||||||
|
"""
|
||||||
|
if not image_path:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Удаляем первый слэш из пути, если есть
|
||||||
|
if image_path.startswith("/"):
|
||||||
|
image_path = image_path[1:]
|
||||||
|
|
||||||
|
full_path = Path(image_path)
|
||||||
|
|
||||||
|
if full_path.exists():
|
||||||
|
os.unlink(full_path)
|
Reference in New Issue
Block a user