FastAPI 12. Интеграция Starlette Admin
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				Lint project / lint (push) Successful in 21m11s
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	Lint project / lint (push) Successful in 21m11s
				
			This commit is contained in:
		@@ -61,9 +61,10 @@ PostgreSQL, Poetry, Pydantic и других.
 | 
				
			|||||||
9. [FastAPI 9. Logout и проверка авторизации](https://pressanybutton.ru/post/servis-na-fastapi/fastapi-9-logout-i-proverka-avtorizacii/)
 | 
					9. [FastAPI 9. Logout и проверка авторизации](https://pressanybutton.ru/post/servis-na-fastapi/fastapi-9-logout-i-proverka-avtorizacii/)
 | 
				
			||||||
10. [FastAPI 10. Изменение данных пользователя](https://pressanybutton.ru/post/servis-na-fastapi/fastapi-10-izmenenie-dannyh-polzovatelya/)
 | 
					10. [FastAPI 10. Изменение данных пользователя](https://pressanybutton.ru/post/servis-na-fastapi/fastapi-10-izmenenie-dannyh-polzovatelya/)
 | 
				
			||||||
11. [FastAPI 11. Хранение и сокращение ссылок](https://pressanybutton.ru/post/servis-na-fastapi/fastapi-11-hranenie-i-sokrashenie-ssylok/)
 | 
					11. [FastAPI 11. Хранение и сокращение ссылок](https://pressanybutton.ru/post/servis-na-fastapi/fastapi-11-hranenie-i-sokrashenie-ssylok/)
 | 
				
			||||||
 | 
					11. [FastAPI 12. Интеграция Starlette Admin](https://pressanybutton.ru/post/servis-na-fastapi/fastapi-12-integraciya-starlette-admin/)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Установка
 | 
					## Установка
 | 
				
			||||||
h
 | 
					
 | 
				
			||||||
Для установки и запуска проекта на вашем локальном компьютере выполните следующие шаги.
 | 
					Для установки и запуска проекта на вашем локальном компьютере выполните следующие шаги.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
1. **Клонируйте репозиторий:**
 | 
					1. **Клонируйте репозиторий:**
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										818
									
								
								poetry.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										818
									
								
								poetry.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -6,23 +6,25 @@ authors = [
 | 
				
			|||||||
    { name = "proDream", email = "sushkoos@gmail.com" }
 | 
					    { name = "proDream", email = "sushkoos@gmail.com" }
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
readme = "README.md"
 | 
					readme = "README.md"
 | 
				
			||||||
requires-python = ">=3.12"
 | 
					requires-python = ">=3.12,<4"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
    "fastapi[standard] (>=0.115.6,<0.116.0)",
 | 
					    "fastapi[standard] (>=0.118.0,<0.120.0)",
 | 
				
			||||||
    "uvicorn[standard] (>=0.34.0,<0.35.0)",
 | 
					    "uvicorn[standard] (>=0.37.0,<0.40.0)",
 | 
				
			||||||
    "pre-commit (>=4.0.1,<5.0.0)",
 | 
					    "pre-commit (>=4.3.0,<5.0.0)",
 | 
				
			||||||
    "sqlalchemy (>=2.0.37,<3.0.0)",
 | 
					    "sqlalchemy (>=2.0.44,<3.0.0)",
 | 
				
			||||||
    "asyncpg (>=0.30.0,<0.31.0)",
 | 
					    "asyncpg (>=0.30.0,<0.31.0)",
 | 
				
			||||||
    "pydantic-settings (>=2.7.1,<3.0.0)",
 | 
					    "pydantic-settings (>=2.11.0,<3.0.0)",
 | 
				
			||||||
    "alembic (>=1.14.0,<2.0.0)",
 | 
					    "alembic (>=1.17.0,<2.0.0)",
 | 
				
			||||||
    "ruff (>=0.9.0,<0.10.0)",
 | 
					    "ruff (>=0.14.0,<0.20.0)",
 | 
				
			||||||
    "pydantic[email] (>=2.10.5,<3.0.0)",
 | 
					    "pydantic[email] (>=2.12.1,<3.0.0)",
 | 
				
			||||||
    "passlib (>=1.7.4,<2.0.0)",
 | 
					    "passlib (>=1.7.4,<2.0.0)",
 | 
				
			||||||
    "bcrypt (==4.0.1)",
 | 
					    "bcrypt (==4.0.1)",
 | 
				
			||||||
    "celery (>=5.4.0,<6.0.0)",
 | 
					    "celery (>=5.5.3,<6.0.0)",
 | 
				
			||||||
    "redis (>=5.2.1,<6.0.0)",
 | 
					    "redis (>=6.4.0,<7.0.0)",
 | 
				
			||||||
    "itsdangerous (>=2.2.0,<3.0.0)",
 | 
					    "itsdangerous (>=2.2.0,<3.0.0)",
 | 
				
			||||||
    "pyjwt (>=2.10.1,<3.0.0)",
 | 
					    "pyjwt (>=2.10.1,<3.0.0)",
 | 
				
			||||||
 | 
					    "poetry-core (>=2.2.1,<3.0.0)",
 | 
				
			||||||
 | 
					    "starlette-admin (>=0.15.1,<0.16.0)",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[build-system]
 | 
					[build-system]
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										0
									
								
								src/lkeep/apps/admin/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								src/lkeep/apps/admin/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										80
									
								
								src/lkeep/apps/admin/admin_auth.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								src/lkeep/apps/admin/admin_auth.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,80 @@
 | 
				
			|||||||
 | 
					from fastapi import HTTPException
 | 
				
			||||||
 | 
					from pydantic import EmailStr, ValidationError
 | 
				
			||||||
 | 
					from starlette.requests import Request
 | 
				
			||||||
 | 
					from starlette.responses import Response
 | 
				
			||||||
 | 
					from starlette_admin.auth import AuthProvider
 | 
				
			||||||
 | 
					from starlette_admin.exceptions import LoginFailed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from lkeep.apps.auth.handlers import AuthHandler
 | 
				
			||||||
 | 
					from lkeep.apps.auth.managers import UserManager
 | 
				
			||||||
 | 
					from lkeep.apps.auth.schemas import AuthUser, GetUserByID
 | 
				
			||||||
 | 
					from lkeep.apps.auth.utils import get_token_from_cookies
 | 
				
			||||||
 | 
					from lkeep.core.core_dependency.db_dependency import DBDependency
 | 
				
			||||||
 | 
					from lkeep.core.core_dependency.redis_dependency import RedisDependency
 | 
				
			||||||
 | 
					from lkeep.core.settings import settings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AdminAuthProvider(AuthProvider):
 | 
				
			||||||
 | 
					    def __init__(self, handler: AuthHandler, manager: UserManager):
 | 
				
			||||||
 | 
					        super().__init__()
 | 
				
			||||||
 | 
					        self.handler = handler
 | 
				
			||||||
 | 
					        self.manager = manager
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def is_authenticated(self, request: Request) -> bool:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            token = await get_token_from_cookies(request=request)
 | 
				
			||||||
 | 
					            decoded_token = await self.handler.decode_access_token(token=token)
 | 
				
			||||||
 | 
					        except HTTPException:
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        user_id = str(decoded_token.get("user_id"))
 | 
				
			||||||
 | 
					        session_id = str(decoded_token.get("session_id"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not await self.manager.get_access_token(user_id=user_id, session_id=session_id):
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        request.state.user = {"id": user_id, "session_id": session_id}
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def login(
 | 
				
			||||||
 | 
					        self, email: EmailStr, password: str, remember_me: bool, request: Request, response: Response
 | 
				
			||||||
 | 
					    ) -> Response:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            auth_data = AuthUser(email=email, password=password)
 | 
				
			||||||
 | 
					        except ValidationError:
 | 
				
			||||||
 | 
					            raise LoginFailed(msg="Invalid email or password")
 | 
				
			||||||
 | 
					        exist_user = await self.manager.get_user_by_email_for_admin(email=auth_data.email)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (
 | 
				
			||||||
 | 
					            exist_user is None
 | 
				
			||||||
 | 
					            or not exist_user.is_superuser
 | 
				
			||||||
 | 
					            or not await self.handler.verify_password(
 | 
				
			||||||
 | 
					                hashed_password=exist_user.hashed_password, raw_password=auth_data.password
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        ):
 | 
				
			||||||
 | 
					            raise LoginFailed(msg="Invalid credentials")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        token, session_id = await self.handler.create_access_token(user_id=exist_user.id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await self.manager.store_access_token(token=token, user_id=exist_user.id, session_id=session_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        response.set_cookie(key="Authorization", value=token, httponly=True, max_age=settings.access_token_expire)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def logout(self, request: Request, response: Response) -> Response:
 | 
				
			||||||
 | 
					        response.delete_cookie("Authorization")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        user = request.state.user
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await self.manager.revoke_access_token(user_id=user["id"], session_id=user["session_id"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_admin_user(self, request: Request) -> GetUserByID:
 | 
				
			||||||
 | 
					        return GetUserByID(**request.state.user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_admin_auth_provider() -> AdminAuthProvider:
 | 
				
			||||||
 | 
					    manager = UserManager(db=DBDependency(), redis=RedisDependency())
 | 
				
			||||||
 | 
					    return AdminAuthProvider(handler=AuthHandler(), manager=manager)
 | 
				
			||||||
							
								
								
									
										17
									
								
								src/lkeep/apps/admin/admin_base.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/lkeep/apps/admin/admin_base.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					from fastapi import FastAPI
 | 
				
			||||||
 | 
					from starlette_admin.contrib.sqla import Admin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from lkeep.apps.admin.admin_auth import get_admin_auth_provider
 | 
				
			||||||
 | 
					from lkeep.apps.admin.views.link_view import LinkViewView
 | 
				
			||||||
 | 
					from lkeep.apps.admin.views.user_view import UserView
 | 
				
			||||||
 | 
					from lkeep.core.core_dependency.db_dependency import get_db_engine
 | 
				
			||||||
 | 
					from lkeep.database.models import Link, User
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def setup_admin(app: FastAPI) -> None:
 | 
				
			||||||
 | 
					    admin = Admin(engine=get_db_engine(), title="Lkeep Admin", auth_provider=get_admin_auth_provider())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    admin.add_view(UserView(User))
 | 
				
			||||||
 | 
					    admin.add_view(LinkViewView(Link))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    admin.mount_to(app=app)
 | 
				
			||||||
							
								
								
									
										0
									
								
								src/lkeep/apps/admin/views/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								src/lkeep/apps/admin/views/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										20
									
								
								src/lkeep/apps/admin/views/link_view.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/lkeep/apps/admin/views/link_view.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					from typing import Any
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from starlette.requests import Request
 | 
				
			||||||
 | 
					from starlette_admin import StringField
 | 
				
			||||||
 | 
					from starlette_admin.contrib.sqla import ModelView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from lkeep.database.models import Link
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class LinkViewView(ModelView):
 | 
				
			||||||
 | 
					    fields = [
 | 
				
			||||||
 | 
					        "id",
 | 
				
			||||||
 | 
					        "full_link",
 | 
				
			||||||
 | 
					        "short_link",
 | 
				
			||||||
 | 
					        StringField("owner_id", label="owner_id", read_only=True),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def before_create(self, request: Request, data: dict[str, Any], link: Link):
 | 
				
			||||||
 | 
					        admin_user = request.state.user
 | 
				
			||||||
 | 
					        link.owner_id = admin_user["id"]
 | 
				
			||||||
							
								
								
									
										49
									
								
								src/lkeep/apps/admin/views/user_view.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								src/lkeep/apps/admin/views/user_view.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
				
			|||||||
 | 
					from typing import Any
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from email_validator import EmailSyntaxError, validate_email
 | 
				
			||||||
 | 
					from passlib.context import CryptContext
 | 
				
			||||||
 | 
					from starlette.requests import Request
 | 
				
			||||||
 | 
					from starlette_admin import PasswordField
 | 
				
			||||||
 | 
					from starlette_admin.contrib.sqla import ModelView
 | 
				
			||||||
 | 
					from starlette_admin.exceptions import FormValidationError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from lkeep.database.models import User
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UserView(ModelView):
 | 
				
			||||||
 | 
					    fields = [
 | 
				
			||||||
 | 
					        "id",
 | 
				
			||||||
 | 
					        "email",
 | 
				
			||||||
 | 
					        PasswordField(
 | 
				
			||||||
 | 
					            name="password",
 | 
				
			||||||
 | 
					            label="Password",
 | 
				
			||||||
 | 
					            required=True,
 | 
				
			||||||
 | 
					            exclude_from_list=True,
 | 
				
			||||||
 | 
					            exclude_from_detail=True,
 | 
				
			||||||
 | 
					            exclude_from_edit=True,
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        "is_active",
 | 
				
			||||||
 | 
					        "is_verified",
 | 
				
			||||||
 | 
					        "is_superuser",
 | 
				
			||||||
 | 
					        "created_at",
 | 
				
			||||||
 | 
					        "updated_at",
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    exclude_fields_from_edit = ["created_at", "updated_at"]
 | 
				
			||||||
 | 
					    exclude_fields_from_create = ["created_at", "updated_at"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    sortable_fields = ["email", "created_at"]
 | 
				
			||||||
 | 
					    fields_default_sort = [("created_at", True)]
 | 
				
			||||||
 | 
					    searchable_fields = ["email"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    page_size = 20
 | 
				
			||||||
 | 
					    page_size_options = [5, 10, 25, 50, -1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def before_create(self, request: Request, data: dict[str, Any], user: User) -> None:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            validate_email(data["email"])
 | 
				
			||||||
 | 
					        except EmailSyntaxError:
 | 
				
			||||||
 | 
					            raise FormValidationError(errors={"email": "Invalid email"})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
 | 
				
			||||||
 | 
					        user.hashed_password = pwd_context.hash(data.pop("password"))
 | 
				
			||||||
@@ -14,6 +14,7 @@ from sqlalchemy.exc import IntegrityError
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from lkeep.apps.auth.schemas import (
 | 
					from lkeep.apps.auth.schemas import (
 | 
				
			||||||
    CreateUser,
 | 
					    CreateUser,
 | 
				
			||||||
 | 
					    GetUserForAdmin,
 | 
				
			||||||
    GetUserWithIDAndEmail,
 | 
					    GetUserWithIDAndEmail,
 | 
				
			||||||
    UserReturnData,
 | 
					    UserReturnData,
 | 
				
			||||||
    UserVerifySchema,
 | 
					    UserVerifySchema,
 | 
				
			||||||
@@ -98,6 +99,20 @@ class UserManager:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            return None
 | 
					            return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def get_user_by_email_for_admin(self, email: str) -> GetUserForAdmin | None:
 | 
				
			||||||
 | 
					        async with self.db.db_session() as session:
 | 
				
			||||||
 | 
					            query = select(self.model.id, self.model.email, self.model.hashed_password, self.model.is_superuser).where(
 | 
				
			||||||
 | 
					                self.model.email == email
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            result = await session.execute(query)
 | 
				
			||||||
 | 
					            user = result.mappings().first()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if user:
 | 
				
			||||||
 | 
					                return GetUserForAdmin(**user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async def get_user_by_id(self, user_id: uuid.UUID | str) -> UserVerifySchema | None:
 | 
					    async def get_user_by_id(self, user_id: uuid.UUID | str) -> UserVerifySchema | None:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Возвращает информацию о пользователе по его идентификатору.
 | 
					        Возвращает информацию о пользователе по его идентификатору.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -83,6 +83,10 @@ class GetUserWithIDAndEmail(GetUserByID, CreateUser):
 | 
				
			|||||||
    pass
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class GetUserForAdmin(GetUserWithIDAndEmail):
 | 
				
			||||||
 | 
					    is_superuser: bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class UserReturnData(GetUserByID, GetUserByEmail):
 | 
					class UserReturnData(GetUserByID, GetUserByEmail):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    Класс для представления данных пользователя, возвращаемых из API.
 | 
					    Класс для представления данных пользователя, возвращаемых из API.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,7 +6,12 @@
 | 
				
			|||||||
https://pressanybutton.ru/category/servis-na-fastapi/
 | 
					https://pressanybutton.ru/category/servis-na-fastapi/
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
 | 
					from sqlalchemy.ext.asyncio import (
 | 
				
			||||||
 | 
					    AsyncEngine,
 | 
				
			||||||
 | 
					    AsyncSession,
 | 
				
			||||||
 | 
					    async_sessionmaker,
 | 
				
			||||||
 | 
					    create_async_engine,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from lkeep.core.settings import settings
 | 
					from lkeep.core.settings import settings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -32,3 +37,11 @@ class DBDependency:
 | 
				
			|||||||
        :rtype: async_sessionmaker[AsyncSession]
 | 
					        :rtype: async_sessionmaker[AsyncSession]
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        return self._session_factory
 | 
					        return self._session_factory
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def db_engine(self) -> AsyncEngine:
 | 
				
			||||||
 | 
					        return self._engine
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_db_engine() -> AsyncEngine:
 | 
				
			||||||
 | 
					    return DBDependency().db_engine
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,6 +11,7 @@ from fastapi import FastAPI
 | 
				
			|||||||
from starlette.middleware.cors import CORSMiddleware
 | 
					from starlette.middleware.cors import CORSMiddleware
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from lkeep.apps import apps_router
 | 
					from lkeep.apps import apps_router
 | 
				
			||||||
 | 
					from lkeep.apps.admin.admin_base import setup_admin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
app = FastAPI()
 | 
					app = FastAPI()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -24,6 +25,8 @@ app.add_middleware(
 | 
				
			|||||||
    allow_headers=["*"],
 | 
					    allow_headers=["*"],
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					setup_admin(app=app)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def start():
 | 
					def start():
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user