создание, правка, удаление, скрытие инструмента

This commit is contained in:
2025-12-12 23:50:38 +03:00
parent 8b38d69980
commit f85ca7d002
19 changed files with 1607 additions and 162 deletions
+18
View File
@@ -1,4 +1,5 @@
from fastapi import APIRouter, Depends, Request
from fastapi.responses import RedirectResponse
from db.handlers.categories import CategoryHandler
from utils import render, requestDict, logger
@@ -18,7 +19,24 @@ router.include_router(toolkit, prefix="/toolkit", tags=["toolkit"])
@router.get("/")
async def main_page(request: Request):
cookies = request.cookies
checkList = ["toolbox_user", "toolbox_access"]
if all(key in cookies for key in checkList):
return await render(request)
else:
for key in checkList:
if key in cookies:
deleteCookie = key
break
else:
deleteCookie = None
if deleteCookie:
response = RedirectResponse(url="/user/login", status_code=302)
response.set_cookie(deleteCookie, "", expires=0)
return response
else:
return RedirectResponse(url="/user/login", status_code=302)
@router.post("/")
Binary file not shown.
Binary file not shown.
+93 -1
View File
@@ -9,6 +9,28 @@ from utils import requestDict, logger
router = APIRouter()
def handleResult(result: dict, response: dict) -> dict:
if "errorMessage" in result.keys():
response["message"] = result["errorMessage"]
else:
response["status"] = "ok"
return response
@router.get("/", summary="Получение инструмента")
async def get_toolkit(reqData: dict = Depends(requestDict)):
logger.info(f"Получение инструмента")
response = {"status": "error"}
toolkitId = reqData.get("query").get("toolkitId")
if toolkitId:
toolkit = await ToolkitHandler.get(int(toolkitId))
if toolkit:
# logger.info(toolkit)
response["status"] = "ok"
response["data"] = toolkit
return response
@router.post("/", summary="Запрос остатка инструмента")
async def toolkit_request(
reqData: dict = Depends(requestDict),
@@ -16,7 +38,6 @@ async def toolkit_request(
response = {"status": "error", "data": {}}
toolkitId = reqData.get("body").get("toolkitId")
logger.info(f"Получение запроса остатка инструмента #{toolkitId}")
# logger.info(request_data)
stocks = await StockHandler.getByToolkitId(toolkitId)
if not stocks:
return response
@@ -100,3 +121,74 @@ async def categories_batch(reqData: dict = Depends(requestDict)):
if success:
response["status"] = "ok"
return response
@router.get("/categories", summary="Получение категорий")
async def get_categories():
logger.info(f"Получение категорий")
response = {"status": "error"}
categories = await CategoryHandler.getAll()
if categories:
categoriesDict = {
category["id"]: {
"id": category["id"],
"title": category["title"],
"description": category["description"],
}
for category in categories
}
response["status"] = "ok"
response["data"] = categoriesDict
return response
@router.post("/hide", summary="Скрытие инструмента")
async def hide_toolkit(reqData: dict = Depends(requestDict)):
logger.info(f"Скрытие/отображение инструмента")
response = {"status": "error"}
toolkitId = int(reqData.get("body").get("toolkitId"))
userId = reqData.get("body").get("userId")
hidden = reqData.get("body").get("hidden")
result = await ToolkitHandler.hideToolkit(userId, toolkitId, hidden)
response = handleResult(result, response)
return response
@router.post("/manage", summary="Управление инструментами")
async def manage_toolkit(reqData: dict = Depends(requestDict)):
logger.info(f"Управление инструментами")
response = {"status": "error"}
action = reqData.get("body").get("action")
userId = reqData.get("body").get("UserId")
toolkitData = reqData.get("body").get("formData")
if "category_id" in toolkitData:
toolkitData["category_id"] = int(toolkitData.get("category_id"))
if "image" in toolkitData:
if (
not toolkitData.get("image").get("main")
or toolkitData.get("image").get("main") == ""
):
if len(toolkitData.get("image").get("additional")) == 0:
toolkitData.pop("image")
match action:
case "create":
toolkit = await ToolkitHandler.add(toolkitData, userId)
response = handleResult(toolkit, response)
case "copy":
toolkitData.pop("id")
toolkit = await ToolkitHandler.add(toolkitData, userId)
response = handleResult(toolkit, response)
case "update":
toolkit = await ToolkitHandler.edit(userId, **toolkitData)
response = handleResult(toolkit, response)
case "delete":
toolkit = await ToolkitHandler.delete(toolkitData.get("id"), userId)
response = handleResult(toolkit, response)
case _:
pass
logger.info(
f"Управление инструментами ({action}) прошло {'успешно' if response.get('status') == 'ok' else 'неуспешно'}"
)
return response
Binary file not shown.

After

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

+16 -3
View File
@@ -1,14 +1,27 @@
// api.js
export async function apiRequest(url, payload = {}, method = 'POST') {
const res = await fetch(url, {
method = method.toUpperCase();
let finalUrl = url;
let options = {
method,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify(payload),
credentials: 'same-origin'
});
};
// --- Если GET → добавляем payload в URL ---
if (method === 'GET') {
const params = new URLSearchParams(payload);
finalUrl = `${url}?${params.toString()}`;
} else {
// --- Для остальных методов → отправляем body ---
options.body = JSON.stringify(payload);
}
const res = await fetch(finalUrl, options);
if (!res.ok) {
const text = await res.text();
+17 -7
View File
@@ -23,16 +23,26 @@ export async function setCookie(name, value, days = 180) {
}
}
const secure = true; // TODO включить после тестов
const sameSite = 'Lax';
const expires = new Date(Date.now() + days * 864e5).toUTCString();
const encodedName = encodeURIComponent(name);
let cookie = `${encodeURIComponent(name)}=${cookieValue}; expires=${expires}; path=/`;
if (secure) cookie += '; Secure';
if (sameSite) cookie += `; SameSite=${sameSite}`;
// ---------- 1. Пытаемся установить безопасную куку ----------
let secureCookie = `${encodedName}=${cookieValue}; expires=${expires}; path=/; Secure; SameSite=Lax`;
document.cookie = secureCookie;
document.cookie = cookie;
// ---------- 2. Проверяем, записалась ли она ----------
const isSet = document.cookie.split('; ')
.some(c => c.startsWith(`${encodedName}=`));
if (isSet) {
return true; // безопасная кука успешно установлена
}
// ---------- 3. Фолбэк: ставим обычную (без Secure) ----------
let normalCookie = `${encodedName}=${cookieValue}; expires=${expires}; path=/; SameSite=Lax`;
document.cookie = normalCookie;
return false; // безопасную куку установить не удалось
}
export async function getCookie(name) {
+1355 -77
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
Binary file not shown.
+7
View File
@@ -138,6 +138,13 @@ class StockHandler:
stocks = await CRUD.read(query, True)
return await filterQuantity(stocks, filtered)
async def checkToolkitExists(toolkitId: int) -> bool:
from db import CRUD
query = select(Stock).where(Stock.toolkit_id == toolkitId)
stocks = await CRUD.read(query)
return True if stocks else False
async def getByToolboxIdAndToolkitId(
toolboxId: int, toolkitId: int, filtered: bool = True
) -> list[dict]:
+69 -43
View File
@@ -1,15 +1,24 @@
from datetime import datetime
from utils import logger, saveImage, safeFilename, deleteImage
from db.handlers.stock import StockHandler
from utils import logger, saveImage, safeFilename
from db import CRUD
from db.schemas.toolkit import Toolkit
from sqlalchemy import select
from db.handlers.records import ServiceRecordsHandler
from utils.image import deleteImage
def handleToolkitImage(imageData, title: str):
import base64
title = safeFilename(title)
fileName = f"tools/{title}.png"
if not saveImage(imageData, fileName):
fileName = f"static/images/tools/{title}.png"
if imageData.startswith("data:image"):
header, encoded = imageData.split(",", 1)
else:
encoded = imageData
file_bytes = base64.b64decode(encoded)
if not saveImage(file_bytes, fileName):
return None
return fileName
@@ -19,22 +28,20 @@ class ToolkitHandler:
title = toolkitData.get("title", None)
if not title:
logger.error("Не указано название инструмента")
return {}
return {"errorMessage": "Не указано название инструмента"}
query = select(Toolkit).where(Toolkit.title == title)
toolkit = await CRUD.read(query)
if toolkit:
logger.error("Инструмент с таким названием уже существует")
return {}
return {"errorMessage": "Инструмент с таким названием уже существует"}
try:
imageDict = {"main": "static/images/tools/default.png", "additional": []}
if "image" in toolkitData:
imageData = toolkitData.pop("image")
mainImage = imageData.get("main")
if isinstance(mainImage, str) and mainImage.startswith(
"static/images/"
):
if mainImage.startswith("static/images/"):
imageDict["main"] = mainImage
else:
imageFileName = handleToolkitImage(mainImage, title)
@@ -43,9 +50,7 @@ class ToolkitHandler:
additionalImages = imageData.get("additional", [])
if len(additionalImages) > 0:
for image in additionalImages:
if isinstance(image, str) and image.startswith(
"static/images/"
):
if image.startswith("static/images/"):
imageDict["additional"].append(image)
else:
imageFileName = handleToolkitImage(image, title)
@@ -55,18 +60,22 @@ class ToolkitHandler:
newToolkit = await Toolkit(**toolkitData).save()
except Exception as e:
logger.error(f"Ошибка сохранения инструмента: {str(e)}")
return {}
return {"errorMessage": f"Ошибка сохранения инструмента: {str(e)}"}
if not newToolkit:
logger.error("Инструмент не сохранен")
return {}
return {"errorMessage": "Инструмент не сохранен"}
logger.info(f"Инструмент {newToolkit.title} успешно создан")
await ServiceRecordsHandler.add(user_id, {"Добавлен инструмент": toolkitData})
return newToolkit
return newToolkit.toDict()
async def updateMovindDate(toolkitId: int):
editedToolkit = await ToolkitHandler.edit(toolkitId, moved_at=datetime.now())
toolkit = await CRUD.read(select(Toolkit).where(Toolkit.id == toolkitId))
if not toolkit:
logger.error("Инструмент не найден")
return False
editedToolkit = await toolkit.edit(moved_at=datetime.now())
if not editedToolkit:
logger.error("Инструмент не обновлен")
return False
@@ -74,65 +83,78 @@ class ToolkitHandler:
async def updateRefillDate(toolkitId: int):
logger.info(f"Обновление даты пополнения инструмента {toolkitId}...")
editedToolkit = await ToolkitHandler.edit(toolkitId, refilled_at=datetime.now())
toolkit = await CRUD.read(select(Toolkit).where(Toolkit.id == toolkitId))
if not toolkit:
logger.error("Инструмент не найден")
return False
editedToolkit = await toolkit.edit(refilled_at=datetime.now())
if not editedToolkit:
logger.error("Инструмент не обновлен")
return False
return True
async def edit(toolkitId: int, **kwargs):
async def hideToolkit(userId: int, toolkitId: int, hidden: bool = True):
logger.info(
f"{'Скрытие' if hidden else 'Отображение'} инструмента {toolkitId}..."
)
return await ToolkitHandler.edit(userId, id=toolkitId, hidden=hidden)
async def edit(user_id: int, **kwargs):
title = kwargs.get("title", None)
toolkitId = kwargs.pop("id")
if title:
query = select(Toolkit).where(Toolkit.title == title)
toolkit = await CRUD.read(query)
if toolkit:
if toolkit.id != toolkitId:
logger.error("Инструмент с таким названием уже существует")
return {
"errorMessage": "Инструмент с таким названием уже существует"
}
query = select(Toolkit).where(Toolkit.id == toolkitId)
toolkit = await CRUD.read(query)
if not toolkit:
logger.error("Инструмент не найден")
return {}
return {"errorMessage": "Инструмент не найден"}
try:
if "image" in kwargs:
title = kwargs.get("title", toolkit.title)
imageData = kwargs.pop("image")
imageDict = {"main": "", "additional": []}
existImagesList = [toolkit.image.get("main")]
existImagesList.extend(toolkit.image.get("additional"))
newImagesList = [imageData.get("main")]
newImagesList.extend(imageData.get("additional"))
for existImage in existImagesList:
if existImage not in newImagesList:
deleteImage(existImage)
if toolkit.image.get("main") != imageData.get("main"):
if imageData.get("main") in existImagesList:
if imageData.get("main").startswith("static/images/"):
imageDict["main"] = imageData.get("main")
else:
imageFileName = handleToolkitImage(imageData.get("main"), title)
if imageFileName:
imageDict["main"] = imageFileName
deleteImage(toolkit.image.get("main"))
for image in imageData.get("additional"):
if image.startswith("static/images/"):
imageDict["additional"].append(image)
else:
imageDict["main"] = "images/tools/default.png"
imageDict["additional"].extend(imageData.get("additional"))
uploadList = imageData.get("upload", [])
if len(uploadList) > 0:
for image in uploadList:
imageFileName = handleToolkitImage(image, title)
if imageFileName:
imageDict["additional"].append(imageFileName)
for existImage in toolkit.image.get("additional"):
if existImage not in imageDict.get("additional"):
deleteImage(existImage)
kwargs["image"] = imageDict
user_id = kwargs.pop("user_id", None)
logger.debug(f"Обновление инструмента {toolkit.title}...")
editedToolkit = await toolkit.edit(**kwargs)
except Exception as e:
logger.error(f"Ошибка обновления инструмента: {str(e)}")
return {}
return {"errorMessage": f"Ошибка обновления инструмента: {str(e)}"}
if not editedToolkit:
logger.error("Инструмент не обновлен")
return {}
return {"errorMessage": "Инструмент не обновлен"}
logger.info(
f"Инструмент {editedToolkit.title} успешно обновлен, изменены данные: {kwargs.keys()}"
@@ -166,24 +188,28 @@ class ToolkitHandler:
return True if toolkit else False
async def delete(toolkitId: int, user_id: int = None):
movements = await StockHandler.checkToolkitExists(toolkitId)
if movements:
logger.error("По инструменту было движение")
return {"errorMessage": "По инструменту было движение"}
query = select(Toolkit).where(Toolkit.id == toolkitId)
toolkit = await CRUD.read(query)
if not toolkit:
logger.error("Инструмент не найден")
return False
return {"errorMessage": "Инструмент не найден"}
try:
toolkitTitle = toolkit.title
result = await CRUD.delete(toolkit)
except Exception as e:
logger.error(f"Ошибка удаления инструмента: {str(e)}")
return False
return {"errorMessage": f"Ошибка удаления инструмента: {str(e)}"}
logger.info(
f"Инструмент {toolkitTitle} {'успешно удален' if result else 'не удален'}"
)
await ServiceRecordsHandler.add(
user_id, {"Удален инструмент": f"Название: {toolkitTitle}"}
)
return result
return {"status": "ok"} if result else {"errorMessage": "Инструмент не удален"}
async def initialize():
from .categories import CategoryHandler
Binary file not shown.
+2 -1
View File
@@ -1,5 +1,5 @@
from datetime import datetime
from sqlalchemy import Column, DateTime, ForeignKey, Integer, String, Text
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String, Text
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import relationship
from db import Base, CRUD
@@ -25,6 +25,7 @@ class Toolkit(Base):
quantity_min = Column(Integer, nullable=True)
quantity_min_extra = Column(Integer, nullable=True)
external_link = Column(String, nullable=True)
hidden = Column(Boolean, default=False)
created_at = Column(DateTime, default=datetime.now)
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
refilled_at = Column(DateTime, default=datetime.now)
+2 -2
View File
@@ -23,8 +23,8 @@ async def main():
from db.initialize import DatabaseInitializer
try:
force = False
reNewDB = False
force = True
reNewDB = True
await DatabaseInitializer(DATABASE_URL).initialize(force, reNewDB)
except Exception as e:
logger.error(f"Инициализация базы завершилась ошибкой: {str(e)}", exc_info=True)
Binary file not shown.
+5 -5
View File
@@ -32,7 +32,7 @@ def saveImage(file_bytes: bytes, file_name: str) -> bool:
if not target_path.lower().endswith(".png"):
target_path += ".png"
logger.info(f"[ImageSave] Saving image to {target_path}")
logger.debug(f"[ImageSave] Saving image to {target_path}")
img.save(target_path, "PNG")
return True
@@ -47,10 +47,10 @@ def deleteImage(fileName: str):
try:
import os
file_name = f"api/{file_name}"
logger.info(f"Удаляем изображение {fileName}")
os.remove(f"static/images/{fileName}")
logger.info(f"Изображение {fileName} успешно удалено")
fileName = f"api/{fileName}"
logger.debug(f"Удаляем изображение {fileName}")
os.remove(fileName)
logger.debug(f"Изображение {fileName} успешно удалено")
return True
except Exception as e:
logger.error(f"Ошибка удаления изображения: {str(e)}")