diff --git a/library_service/main.py b/library_service/main.py
index 6fa9b1e..eb42121 100644
--- a/library_service/main.py
+++ b/library_service/main.py
@@ -1,9 +1,11 @@
"""Основной модуль"""
from contextlib import asynccontextmanager
+from pathlib import Path
from alembic import command
from alembic.config import Config
from fastapi import FastAPI
+from fastapi.staticfiles import StaticFiles
from .routers import api_router
from .settings import engine, get_app
@@ -29,3 +31,5 @@ async def lifespan(app: FastAPI):
# Подключение маршрутов
app.include_router(api_router)
+static_path = Path(__file__).parent / "static"
+app.mount("/static", StaticFiles(directory=static_path), name="static")
diff --git a/library_service/routers/misc.py b/library_service/routers/misc.py
index 678305d..0b94be8 100644
--- a/library_service/routers/misc.py
+++ b/library_service/routers/misc.py
@@ -31,7 +31,13 @@ def get_info(app) -> Dict:
@router.get("/", include_in_schema=False)
async def root(request: Request, app=Depends(get_app)):
"""Эндпоинт главной страницы"""
- return templates.TemplateResponse(request, "index.html", get_info(app))
+ return RedirectResponse("/books")
+
+
+@router.get("/books", include_in_schema=False)
+async def books(request: Request, app=Depends(get_app)):
+ """Эндпоинт страницы выбора книг"""
+ return templates.TemplateResponse(request, "books.html", get_info(app))
@router.get("/auth", include_in_schema=False)
@@ -61,18 +67,6 @@ async def favicon():
)
-@router.get("/static/{path:path}", include_in_schema=False)
-async def serve_static(path: str):
- """Статические файлы"""
- static_dir = Path(__file__).parent.parent / "static"
- file_path = static_dir / path
-
- if not file_path.is_file() or not file_path.is_relative_to(static_dir):
- return JSONResponse(status_code=404, content={"error": "File not found"})
-
- return FileResponse(file_path)
-
-
@router.get(
"/api/info",
summary="Информация о сервисе",
diff --git a/library_service/static/auth.js b/library_service/static/auth.js
new file mode 100644
index 0000000..14c64fe
--- /dev/null
+++ b/library_service/static/auth.js
@@ -0,0 +1,287 @@
+$(function () {
+ const $loginTab = $("#login-tab");
+ const $registerTab = $("#register-tab");
+ const $loginForm = $("#login-form");
+ const $registerForm = $("#register-form");
+
+ const $guestLink = $("#guest-link");
+ const $userBtn = $("#user-btn");
+ const $userDropdown = $("#user-dropdown");
+ const $userArrow = $("#user-arrow");
+ const $userAvatar = $("#user-avatar");
+ const $dropdownName = $("#dropdown-name");
+ const $dropdownUsername = $("#dropdown-username");
+ const $dropdownEmail = $("#dropdown-email");
+ const $logoutBtn = $("#logout-btn");
+ const $menuContainer = $("#user-menu-container");
+
+ function switchToLogin() {
+ $loginTab.addClass("text-gray-700 bg-gray-50 border-b-2 border-gray-500").removeClass("text-gray-400");
+ $registerTab.removeClass("text-gray-700 bg-gray-50 border-b-2 border-gray-500").addClass("text-gray-400");
+ $loginForm.removeClass("hidden"); $registerForm.addClass("hidden");
+ history.replaceState(null, "", "/auth#login");
+ }
+
+ function switchToRegister() {
+ $registerTab.addClass("text-gray-700 bg-gray-50 border-b-2 border-gray-500").removeClass("text-gray-400");
+ $loginTab.removeClass("text-gray-700 bg-gray-50 border-b-2 border-gray-500").addClass("text-gray-400");
+ $registerForm.removeClass("hidden"); $loginForm.addClass("hidden");
+ history.replaceState(null, "", "/auth#register");
+ }
+
+ $loginTab.on("click", switchToLogin);
+ $registerTab.on("click", switchToRegister);
+
+ $("body").on("click", ".toggle-password", function () {
+ const $btn = $(this);
+ const $input = $btn.siblings("input");
+ const $eyeOpen = $btn.find(".eye-open");
+ const $eyeClosed = $btn.find(".eye-closed");
+
+ if ($input.attr("type") === "password") {
+ $input.attr("type", "text");
+ $eyeOpen.addClass("hidden");
+ $eyeClosed.removeClass("hidden");
+ } else {
+ $input.attr("type", "password");
+ $eyeOpen.removeClass("hidden");
+ $eyeClosed.addClass("hidden");
+ }
+ });
+
+ $("#register-password").on("input", function () {
+ const password = $(this).val();
+ let strength = 0;
+ if (password.length >= 8) strength++;
+ if (password.length >= 12) strength++;
+ if (/[a-z]/.test(password) && /[A-Z]/.test(password)) strength++;
+ if (/\d/.test(password)) strength++;
+ if (/[^a-zA-Z0-9]/.test(password)) strength++;
+
+ const levels = [
+ { width: "0%", color: "", text: "" },
+ { width: "20%", color: "bg-red-500", text: "Очень слабый" },
+ { width: "40%", color: "bg-orange-500", text: "Слабый" },
+ { width: "60%", color: "bg-yellow-500", text: "Средний" },
+ { width: "80%", color: "bg-lime-500", text: "Хороший" },
+ { width: "100%", color: "bg-green-500", text: "Отличный" },
+ ];
+
+ const level = levels[strength];
+ const $bar = $("#password-strength-bar");
+
+ $bar.css("width", level.width);
+ $bar.attr("class", "h-full transition-all duration-300 " + level.color);
+ $("#password-strength-text").text(level.text);
+
+ checkPasswordMatch();
+ });
+
+ function checkPasswordMatch() {
+ const password = $("#register-password").val();
+ const confirm = $("#register-password-confirm").val();
+ const $error = $("#password-match-error");
+
+ if (confirm && password !== confirm) {
+ $error.removeClass("hidden");
+ return false;
+ } else {
+ $error.addClass("hidden");
+ return true;
+ }
+ }
+
+ $("#register-password-confirm").on("input", checkPasswordMatch);
+
+ $loginForm.on("submit", async function (event) {
+ event.preventDefault();
+
+ const $errorDiv = $("#login-error");
+ const $submitBtn = $("#login-submit");
+ const username = $("#login-username").val();
+ const password = $("#login-password").val();
+
+ $errorDiv.addClass("hidden");
+ $submitBtn.prop("disabled", true).text("Вход...");
+
+ try {
+ const formData = new URLSearchParams();
+ formData.append("username", username);
+ formData.append("password", password);
+
+ const response = await fetch("/api/auth/token", {
+ method: "POST",
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
+ body: formData.toString(),
+ });
+
+ const data = await response.json();
+
+ if (response.ok) {
+ localStorage.setItem("access_token", data.access_token);
+ if (data.refresh_token) {
+ localStorage.setItem("refresh_token", data.refresh_token);
+ }
+ window.location.href = "/";
+ } else {
+ $errorDiv.text(data.detail || "Неверное имя пользователя или пароль");
+ $errorDiv.removeClass("hidden");
+ $submitBtn.prop("disabled", false).text("Войти");
+ }
+ } catch (error) {
+ console.error("Login error:", error);
+ $errorDiv.text("Ошибка соединения с сервером");
+ $errorDiv.removeClass("hidden");
+ $submitBtn.prop("disabled", false).text("Войти");
+ }
+ });
+
+ $registerForm.on("submit", async function (event) {
+ event.preventDefault();
+
+ const $errorDiv = $("#register-error");
+ const $successDiv = $("#register-success");
+ const $submitBtn = $("#register-submit");
+
+ if (!checkPasswordMatch()) {
+ $errorDiv.text("Пароли не совпадают").removeClass("hidden");
+ return;
+ }
+
+ const userData = {
+ username: $("#register-username").val(),
+ email: $("#register-email").val(),
+ full_name: $("#register-fullname").val() || null,
+ password: $("#register-password").val(),
+ };
+
+ $errorDiv.addClass("hidden");
+ $successDiv.addClass("hidden");
+ $submitBtn.prop("disabled", true).text("Регистрация...");
+
+ try {
+ const response = await fetch("/api/auth/register", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(userData),
+ });
+
+ const data = await response.json();
+
+ if (response.ok) {
+ $successDiv.text("Регистрация успешна! Переключаемся на вход...").removeClass("hidden");
+ setTimeout(() => {
+ $("#login-username").val(userData.username);
+ switchToLogin();
+ }, 2000);
+ } else {
+ let errorMessage = data.detail;
+ if (Array.isArray(data.detail)) {
+ errorMessage = data.detail.map((err) => err.msg).join(". ");
+ }
+ $errorDiv.text(errorMessage || "Ошибка регистрации").removeClass("hidden");
+ }
+ } catch (error) {
+ console.error("Register error:", error);
+ $errorDiv.text("Ошибка соединения с сервером").removeClass("hidden");
+ } finally {
+ $submitBtn.prop("disabled", false).text("Зарегистрироваться");
+ }
+ });
+
+ let isDropdownOpen = false;
+
+ function openDropdown() {
+ isDropdownOpen = true;
+ $userDropdown.removeClass("hidden");
+ $userArrow.addClass("rotate-180");
+ }
+
+ function closeDropdown() {
+ isDropdownOpen = false;
+ $userDropdown.addClass("hidden");
+ $userArrow.removeClass("rotate-180");
+ }
+
+ $userBtn.on("click", function (e) {
+ e.stopPropagation();
+ isDropdownOpen ? closeDropdown() : openDropdown();
+ });
+
+ $(document).on("click", function (e) {
+ if (isDropdownOpen && !$(e.target).closest("#user-menu-container").length) {
+ closeDropdown();
+ }
+ });
+
+ $(document).on("keydown", function (e) {
+ if (e.key === "Escape" && isDropdownOpen) {
+ closeDropdown();
+ }
+ });
+
+ $logoutBtn.on("click", function () {
+ localStorage.removeItem("access_token");
+ localStorage.removeItem("refresh_token");
+ window.location.href = "/";
+ });
+
+ function showGuest() {
+ $guestLink.removeClass("hidden");
+ $userBtn.addClass("hidden").removeClass("flex");
+ closeDropdown();
+ }
+
+ function showUser(user) {
+ $guestLink.addClass("hidden");
+ $userBtn.removeClass("hidden").addClass("flex");
+
+ const displayName = user.full_name || user.username;
+ const firstLetter = displayName.charAt(0).toUpperCase();
+
+ $userAvatar.text(firstLetter);
+ $dropdownName.text(displayName);
+ $dropdownUsername.text("@" + user.username);
+ $dropdownEmail.text(user.email);
+ }
+
+
+ function updateUserAvatar(email) {
+ if (!email) return;
+ const cleanEmail = email.trim().toLowerCase();
+ const emailHash = sha256(cleanEmail);
+
+ const avatarUrl = `https://www.gravatar.com/avatar/${emailHash}?d=identicon&s=200`;
+ const avatarImg = document.getElementById('user-avatar');
+ if (avatarImg) { avatarImg.src = avatarUrl; }
+ }
+
+ if (window.location.hash === "#register") { switchToRegister(); }
+
+ const token = localStorage.getItem("access_token");
+
+ if (!token) {
+ showGuest();
+ } else {
+ fetch("/api/auth/me", {
+ headers: { Authorization: "Bearer " + token },
+ })
+ .then((response) => {
+ if (response.ok) return response.json();
+ throw new Error("Unauthorized");
+ })
+ .then((user) => {
+ showUser(user);
+ updateUserAvatar(user.email);
+
+ document.getElementById('user-btn').classList.remove('hidden');
+ document.getElementById('guest-link').classList.add('hidden');
+ if (window.location.pathname === "/auth") { window.location.href = "/"; }
+ })
+ .catch(() => {
+ localStorage.removeItem("access_token");
+ localStorage.removeItem("refresh_token");
+ showGuest();
+ });
+ }
+ });
\ No newline at end of file
diff --git a/library_service/static/script.js b/library_service/static/books.js
similarity index 56%
rename from library_service/static/script.js
rename to library_service/static/books.js
index 48e6c97..1857d2c 100644
--- a/library_service/static/script.js
+++ b/library_service/static/books.js
@@ -1,7 +1,7 @@
$(document).ready(function () {
Promise.all([
- fetch("/authors").then((response) => response.json()),
- fetch("/genres").then((response) => response.json()),
+ fetch("/api/authors").then((response) => response.json()),
+ fetch("/api/genres").then((response) => response.json()),
])
.then(([authorsData, genresData]) => {
const $dropdown = $("#author-dropdown");
@@ -112,4 +112,106 @@ $(document).ready(function () {
renderSelectedAuthors();
}
-});
+
+ const $guestLink = $("#guest-link");
+ const $userBtn = $("#user-btn");
+ const $userDropdown = $("#user-dropdown");
+ const $userArrow = $("#user-arrow");
+ const $userAvatar = $("#user-avatar");
+ const $dropdownName = $("#dropdown-name");
+ const $dropdownUsername = $("#dropdown-username");
+ const $dropdownEmail = $("#dropdown-email");
+ const $logoutBtn = $("#logout-btn");
+
+ let isDropdownOpen = false;
+
+ function openDropdown() {
+ isDropdownOpen = true;
+ $userDropdown.removeClass("hidden");
+ $userArrow.addClass("rotate-180");
+ }
+
+ function closeDropdown() {
+ isDropdownOpen = false;
+ $userDropdown.addClass("hidden");
+ $userArrow.removeClass("rotate-180");
+ }
+
+ $userBtn.on("click", function (e) {
+ e.stopPropagation();
+ isDropdownOpen ? closeDropdown() : openDropdown();
+ });
+
+ $(document).on("click", function (e) {
+ if (isDropdownOpen && !$(e.target).closest("#user-menu-container").length) {
+ closeDropdown();
+ }
+ });
+
+ $(document).on("keydown", function (e) {
+ if (e.key === "Escape" && isDropdownOpen) {
+ closeDropdown();
+ }
+ });
+
+ $logoutBtn.on("click", function () {
+ localStorage.removeItem("access_token");
+ localStorage.removeItem("refresh_token");
+ window.location.reload();
+ });
+
+ function showGuest() {
+ $guestLink.removeClass("hidden");
+ $userBtn.addClass("hidden").removeClass("flex");
+ closeDropdown();
+ }
+
+ function showUser(user) {
+ $guestLink.addClass("hidden");
+ $userBtn.removeClass("hidden").addClass("flex");
+
+ const displayName = user.full_name || user.username;
+ const firstLetter = displayName.charAt(0).toUpperCase();
+
+ $userAvatar.text(firstLetter);
+ $dropdownName.text(displayName);
+ $dropdownUsername.text("@" + user.username);
+ $dropdownEmail.text(user.email);
+ }
+
+ function updateUserAvatar(email) {
+ if (!email) return;
+ const cleanEmail = email.trim().toLowerCase();
+ const emailHash = sha256(cleanEmail);
+
+ const avatarUrl = `https://www.gravatar.com/avatar/${emailHash}?d=identicon&s=200`;
+ const avatarImg = document.getElementById('user-avatar');
+ if (avatarImg) { avatarImg.src = avatarUrl; }
+ }
+
+ const token = localStorage.getItem("access_token");
+
+ if (!token) {
+ showGuest();
+ } else {
+ fetch("/api/auth/me", {
+ headers: { Authorization: "Bearer " + token },
+ })
+ .then((response) => {
+ if (response.ok) return response.json();
+ throw new Error("Unauthorized");
+ })
+ .then((user) => {
+ showUser(user);
+ updateUserAvatar(user.email);
+
+ document.getElementById('user-btn').classList.remove('hidden');
+ document.getElementById('guest-link').classList.add('hidden');
+ })
+ .catch(() => {
+ localStorage.removeItem("access_token");
+ localStorage.removeItem("refresh_token");
+ showGuest();
+ });
+ }
+});
\ No newline at end of file
diff --git a/library_service/static/styles.css b/library_service/static/styles.css
index 5a8b0a3..cb44c55 100644
--- a/library_service/static/styles.css
+++ b/library_service/static/styles.css
@@ -75,3 +75,91 @@ nav ul li a {
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
+
+.auth-tab {
+ font-family: "Dited", sans-serif;
+ letter-spacing: 1.5px;
+}
+
+input:focus {
+ transform: translateY(-1px);
+}
+
+button:disabled {
+ opacity: 0.7;
+}
+
+#login-form,
+#register-form {
+ animation: fadeIn 0.3s ease-in-out;
+}
+
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(-10px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.flex.justify-center.gap-4 button:hover {
+ transform: translateY(-2px);
+}
+
+.shake {
+ animation: shake 0.5s ease-in-out;
+}
+
+@keyframes shake {
+ 0%, 100% { transform: translateX(0); }
+ 10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); }
+ 20%, 40%, 60%, 80% { transform: translateX(5px); }
+}
+
+#req-length, #req-upper, #req-lower, #req-digit {
+ transition: color 0.2s ease;
+}
+
+.req-icon {
+ font-size: 10px;
+ width: 12px;
+ display: inline-block;
+}
+
+#login-form:not(.hidden),
+#register-form:not(.hidden) {
+ animation: fadeIn 0.3s ease-in-out;
+}
+
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(-10px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+#login-tab, #register-tab {
+ font-family: "Dited", sans-serif;
+ letter-spacing: 1.5px;
+ cursor: pointer;
+}
+
+#user-dropdown {
+ animation: dropdownFade 0.1s ease-out;
+}
+
+@keyframes dropdownFade {
+ from { opacity: 0; transform: translateY(-5px); }
+ to { opacity: 1; transform: translateY(0); }
+}
+
+#user-arrow.rotate-180 {
+ transform: rotate(180deg);
+}
\ No newline at end of file
diff --git a/library_service/templates/auth.html b/library_service/templates/auth.html
index e69de29..31e0c95 100644
--- a/library_service/templates/auth.html
+++ b/library_service/templates/auth.html
@@ -0,0 +1,193 @@
+
+{% extends "base.html" %}
+
+{% block title %}LiB - Авторизация{% endblock %}
+
+{% block content %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+{% endblock %}
+
+{% block scripts %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/library_service/templates/base.html b/library_service/templates/base.html
new file mode 100644
index 0000000..cb9d1c3
--- /dev/null
+++ b/library_service/templates/base.html
@@ -0,0 +1,77 @@
+
+
+
+ {% block title %}LiB{% endblock %}
+
+
+
+
+
+
+ {% block extra_head %}{% endblock %}
+
+
+
+
+ {% block content %}{% endblock %}
+
+
+
+ {% block scripts %}{% endblock %}
+
+
\ No newline at end of file
diff --git a/library_service/templates/books.html b/library_service/templates/books.html
new file mode 100644
index 0000000..af6312a
--- /dev/null
+++ b/library_service/templates/books.html
@@ -0,0 +1,66 @@
+{% extends "base.html" %}
+
+{% block title %}LiB - Главная{% endblock %}
+
+{% block content %}
+
+
+
+
+
+
+
Product Title 1
+
+ A short description of the product, highlighting its
+ key features and benefits.
+
+
+
$29.99
+
+
+
+
+
Product Title 2
+
+ Another great product with amazing features. You'll
+ love it!
+
+
+
$49.99
+
+
+
+
+
Product Title 3
+
+ This product is a must-have for every modern home.
+ High quality and durable.
+
+
+
$19.99
+
+
+
+{% endblock %}
+
+{% block scripts %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/library_service/templates/index.html b/library_service/templates/index.html
deleted file mode 100644
index 44bee1b..0000000
--- a/library_service/templates/index.html
+++ /dev/null
@@ -1,139 +0,0 @@
-
-
-
- LiB
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Product Title 1
-
- A short description of the product, highlighting its
- key features and benefits.
-
-
-
$29.99
-
-
-
-
-
-
Product Title 2
-
- Another great product with amazing features. You'll
- love it!
-
-
-
$49.99
-
-
-
-
-
-
Product Title 3
-
- This product is a must-have for every modern home.
- High quality and durable.
-
-
-
$19.99
-
-
-
-
-
-
-
-
-
diff --git a/poetry.lock b/poetry.lock
index 7b421ae..1477661 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,4 +1,4 @@
-# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand.
+# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand.
[[package]]
name = "aiofiles"
@@ -59,6 +59,7 @@ files = [
[package.dependencies]
idna = ">=2.8"
sniffio = ">=1.1"
+typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""}
[package.extras]
doc = ["Sphinx (>=8.2,<9.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"]
@@ -525,7 +526,7 @@ description = "Lightweight in-process concurrent programming"
optional = false
python-versions = ">=3.9"
groups = ["main"]
-markers = "python_version == \"3.13\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"
+markers = "python_version < \"3.14\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"
files = [
{file = "greenlet-3.2.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:1afd685acd5597349ee6d7a88a8bec83ce13c106ac78c196ee9dde7c04fe87be"},
{file = "greenlet-3.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:761917cac215c61e9dc7324b2606107b3b292a8349bdebb31503ab4de3f559ac"},
@@ -1471,6 +1472,7 @@ files = [
[package.dependencies]
pytest = ">=8.2,<10"
+typing-extensions = {version = ">=4.12", markers = "python_version < \"3.13\""}
[package.extras]
docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1)"]
@@ -1855,11 +1857,12 @@ version = "4.15.0"
description = "Backported and Experimental Type Hints for Python 3.9+"
optional = false
python-versions = ">=3.9"
-groups = ["main"]
+groups = ["main", "dev"]
files = [
{file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"},
{file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"},
]
+markers = {dev = "python_version == \"3.12\""}
[[package]]
name = "typing-inspection"
@@ -2243,5 +2246,5 @@ files = [
[metadata]
lock-version = "2.1"
-python-versions = "^3.13"
-content-hash = "2fdd550e819b1456733250a62196acb442127a296ff60e010c424ecbebec294e"
+python-versions = "^3.12"
+content-hash = "a8d44f0decfa3ba437e998207c16ca7429ee42e930e8aa1d40f87231e71f219f"
diff --git a/pyproject.toml b/pyproject.toml
index 822e221..36b5601 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -7,7 +7,7 @@ readme = "README.md"
packages = [{ include = "library_service" }]
[tool.poetry.dependencies]
-python = "^3.13"
+python = "^3.12"
fastapi = { extras = ["all"], version = "^0.115.12" }
psycopg2-binary = "^2.9.10"
alembic = "^1.16.1"
@@ -28,9 +28,6 @@ isort = "^7.0.0"
pytest-asyncio = "^1.3.0"
pylint = "^4.0.4"
-[tool.poetry.requires-plugins]
-poetry-plugin-export = ">=1.8"
-
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"