Skip to content

Commit

Permalink
feat(internal): move internal to admin
Browse files Browse the repository at this point in the history
  • Loading branch information
wangxin688 committed Jul 7, 2024
1 parent 238a024 commit b965e86
Show file tree
Hide file tree
Showing 14 changed files with 99 additions and 76 deletions.
20 changes: 4 additions & 16 deletions backend/src/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,23 +46,11 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]: # noqa: ARG001
openapi_url="/api/openapi.json",
)

@app.get(
"/api/health", include_in_schema=False, tags=["Internal"], operation_id="e7372032-61c5-4e3d-b2f1-b788fe1c52ba"
)
def health() -> dict[str, str]:
return {"status": "ok"}

@app.get(
"/api/version", include_in_schema=False, tags=["Internal"], operation_id="47918987-15d9-4eea-8c29-e73cb009a4d5"
)
def version() -> dict[str, str]:
return {"version": settings.VERSION}

@app.get(
"/api/elements", include_in_schema=False, tags=["Docs"], operation_id="1a4987dd-6c38-4502-a879-3fe35050ae38"
)
@app.get("/api/elements", include_in_schema=False, operation_id="1a4987dd-6c38-4502-a879-3fe35050ae38")
def get_stoplight_elements() -> HTMLResponse:
return get_stoplight_elements_html(openapi_url="/api/openapi.json", title=settings.PROJECT_NAME)
return get_stoplight_elements_html(
openapi_url="/api/openapi.json", title=settings.PROJECT_NAME, base_path="/api/elements"
)

app.include_router(router, prefix="/api")
for handler in exception_handlers:
Expand Down
8 changes: 7 additions & 1 deletion backend/src/core/repositories/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from uuid import UUID

from pydantic import BaseModel
from sqlalchemy import Row, Select, Text, cast, desc, func, inspect, not_, or_, select, text, types
from sqlalchemy import Row, Select, Text, cast, delete, desc, func, inspect, not_, or_, select, text, types
from sqlalchemy.dialects.postgresql import ARRAY, HSTORE, INET, JSON, JSONB, MACADDR
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.ext.mutable import Mutable
Expand Down Expand Up @@ -945,6 +945,12 @@ async def delete(self, session: AsyncSession, db_obj: ModelT) -> None:
await session.delete(db_obj)
await session.commit()

async def batch_delete(self, session: AsyncSession, ids: list[PkIdT]) -> None:
"""batch delete without fetch data for performance"""
id_str = self.get_id_attribute_value(self.model)
await session.execute(delete(self.model).where(id_str.in_(ids)))
await session.commit()

async def get_audit_log(self, session: AsyncSession, pk_id: PkIdT) -> tuple[int, Sequence["AuditLog"] | None]:
if hasattr(self.model, "audit_log"):
stmt = self._get_base_stmt()
Expand Down
68 changes: 57 additions & 11 deletions backend/src/features/admin/api.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
from fastapi import APIRouter, Depends, status
from uuid import UUID

from fastapi import APIRouter, Depends, Request, status
from fastapi.security import OAuth2PasswordRequestForm
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload

from src.core.config import settings
from src.core.errors.err_codes import ERR_10005
from src.core.errors.exception_handlers import GenerError
from src.core.repositories import BaseRepository
from src.core.utils.cbv import cbv
from src.core.utils.validators import list_to_tree
from src.features._types import IdResponse, ListT
from src.features.admin import schemas
from src.features.admin.models import Group, Menu, Role, User
from src.features.admin import schemas, services
from src.features.admin.models import Group, Permission, Role, User
from src.features.admin.security import generate_access_token_response
from src.features.admin.services import MenuService, UserService
from src.features.deps import auth, get_session

router = APIRouter()
Expand All @@ -23,16 +24,15 @@ async def login_pwd(
user: OAuth2PasswordRequestForm = Depends(),
session: AsyncSession = Depends(get_session),
) -> schemas.AccessToken:
service = UserService(User)
result = await service.verify_user(session, user)
result = await services.user_service.verify_user(session, user)
return generate_access_token_response(result.id)


@cbv(router)
class UserAPI:
user: User = Depends(auth)
session: AsyncSession = Depends(get_session)
service = UserService(User)
service = services.user_service

@router.post("/users", operation_id="5091fff6-1adc-4a22-8a8c-ef0107122df7", summary="创建新用户/Create new user")
async def create_user(self, user: schemas.UserCreate) -> IdResponse:
Expand Down Expand Up @@ -84,7 +84,7 @@ async def delete_user(self, id: int) -> IdResponse:
class GroupAPI:
user: User = Depends(auth)
session: AsyncSession = Depends(get_session)
service = BaseRepository(Group)
service = services.group_service

@router.post("/groups", operation_id="9e3e639d-c694-467d-9209-717b038cf267")
async def create_group(self, group: schemas.GroupCreate) -> IdResponse:
Expand Down Expand Up @@ -118,7 +118,7 @@ async def delete_group(self, id: int) -> IdResponse:
class RoleAPI:
user: User = Depends(auth)
session: AsyncSession = Depends(get_session)
service = BaseRepository(Role)
service = services.role_service

@router.post("/roles", operation_id="a18a152b-e9e9-4128-b8be-8a8e9c842abb")
async def create_role(self, role: schemas.RoleCreate) -> IdResponse:
Expand Down Expand Up @@ -152,7 +152,7 @@ async def delete_role(self, id: int) -> IdResponse:
class MenuAPI:
user: User = Depends(auth)
session: AsyncSession = Depends(get_session)
service = MenuService(Menu)
service = services.menu_service

@router.post("/menus", operation_id="008bf4d4-cc01-48b0-82b8-1a67c0348b31")
async def create_menu(self, meun: schemas.MenuCreate) -> IdResponse:
Expand All @@ -175,3 +175,49 @@ async def delete_menu(self, id: int) -> IdResponse:
db_menu = await self.service.get_one_or_404(self.session, id)
await self.service.delete(self.session, db_menu)
return IdResponse(id=id)


@cbv(router)
class PermissionAPI:
user: User = Depends(auth)
session: AsyncSession = Depends(get_session)
service = services.permission_service

@router.get("/permissions", operation_id="8057d614-150f-42ee-984c-d0af35796da3", summary="Permissions: Get All")
async def get_permissions(self) -> list[schemas.Permission]:
permissions = await services.permission_service.get_all(self.session)
return [schemas.Permission.model_validate(p) for p in permissions]

@router.post("/permissions", operation_id="e0fe80d5-cbe0-4c2c-9eff-57e80ecba522", summary="Permissions: Sync All")
async def sync_db_permission(self, request: Request) -> dict:
routes = request.app.routes
operation_ids = [route.operation_id for route in routes]
router_mappings = {
router.operation_id: {
"name": router.name,
"path": router.path,
"methods": router.methods,
"description": router.description,
}
for router in routes
}
permissions = await self.service.get_multi_by_ids(self.session, operation_ids)
removed = {str(p.id) for p in permissions} - set(operation_ids)
added = set(operation_ids) - {str(p.id) for p in permissions}
if removed:
await self.service.batch_delete(self.session, [UUID(r) for r in removed])
if added:
new_permissions = [Permission(id=p_id, **router_mappings[p_id]) for p_id in added]
self.session.add_all(new_permissions)
await self.session.commit()
return {"added": added, "removed": removed}


@router.get("/health", operation_id="e7372032-61c5-4e3d-b2f1-b788fe1c52ba", summary="Service Health check")
def health() -> dict[str, str]:
return {"status": "ok"}


@router.get("/version", operation_id="47918987-15d9-4eea-8c29-e73cb009a4d5", summary="Get Service Version")
def version() -> dict[str, str]:
return {"version": settings.VERSION}
26 changes: 25 additions & 1 deletion backend/src/features/admin/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,18 @@
from src.core.repositories import BaseRepository
from src.core.utils.context import locale_ctx
from src.features.admin import schemas
from src.features.admin.models import Menu, Permission, User
from src.features.admin.models import Group, Menu, Permission, Role, User
from src.features.admin.schemas import PermissionCreate, PermissionUpdate
from src.features.admin.security import verify_password

__all__ = (
"user_service",
"permission_service",
"menu_service",
"group_service",
"role_service",
)


class UserService(BaseRepository[User, schemas.UserCreate, schemas.UserUpdate, schemas.UserQuery]):
async def verify_user(self, session: AsyncSession, user: OAuth2PasswordRequestForm) -> User:
Expand Down Expand Up @@ -51,10 +59,26 @@ async def update(
async def delete(self, session: AsyncSession, db_obj: Permission) -> None:
raise NotImplementedError

async def get_all(self, session: AsyncSession) -> Sequence[Permission]:
return (await session.scalars(select(self.model))).all()


class MenuService(BaseRepository[Menu, schemas.MenuCreate, schemas.MenuUpdate, schemas.MenuQuery]):
async def get_all(self, session: AsyncSession) -> Sequence[Menu]:
return (await session.scalars(select(self.model))).all()

@staticmethod
def menu_tree_transform(menus: Sequence[Menu]) -> list[dict]: ...


class GroupService(BaseRepository[Group, schemas.GroupCreate, schemas.GroupUpdate, schemas.GroupQuery]): ...


class RoleService(BaseRepository[Role, schemas.RoleCreate, schemas.RoleUpdate, schemas.RoleQuery]): ...


user_service = UserService(User)
permission_service = PermissionService(Permission)
menu_service = MenuService(Menu)
group_service = GroupService(Group)
role_service = RoleService(Role)
2 changes: 1 addition & 1 deletion backend/src/features/circuit/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from fastapi import Query
from pydantic import AnyHttpUrl, EmailStr, Field, IPvAnyAddress, IPvAnyNetwork

from src.features import schemas
from src.features._types import (
AuditTime,
AuditTimeQuery,
Expand All @@ -15,7 +16,6 @@
QueryParams,
)
from src.features.consts import CircuitStatus
from src.features.internal import schemas


class ISPBase(BaseModel):
Expand Down
2 changes: 1 addition & 1 deletion backend/src/features/dcim/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from fastapi import Query
from pydantic import Field, IPvAnyAddress, model_validator

from src.features import schemas
from src.features._types import (
AuditTime,
AuditUser,
Expand All @@ -11,7 +12,6 @@
QueryParams,
)
from src.features.consts import APMode, DeviceEquipmentType, DeviceStatus, InterfaceAdminStatus
from src.features.internal import schemas


class DeviceBase(BaseModel):
Expand Down
2 changes: 1 addition & 1 deletion backend/src/features/intend/schemas.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from fastapi import Query
from pydantic import AnyHttpUrl, Field

from src.features import schemas
from src.features._types import AuditTime, BaseModel, I18nField, NameStr, QueryParams
from src.features.internal import schemas


class CircuitTypeCreate(BaseModel):
Expand Down
Empty file.
39 changes: 0 additions & 39 deletions backend/src/features/internal/api.py

This file was deleted.

2 changes: 1 addition & 1 deletion backend/src/features/ipam/schemas.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from fastapi import Query
from pydantic import Field, IPvAnyInterface, IPvAnyNetwork, model_validator

from src.features import schemas
from src.features._types import AuditTime, BaseModel, NameChineseStr, NameStr, QueryParams
from src.features.admin.schemas import UserBrief
from src.features.consts import IPRangeStatus, PrefixStatus, VLANStatus
from src.features.internal import schemas


class BlockBase(BaseModel):
Expand Down
2 changes: 1 addition & 1 deletion backend/src/features/org/schemas.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from fastapi import Query
from pydantic import Field, model_validator

from src.features import schemas
from src.features._types import (
AuditUser,
AuthUserBase,
Expand All @@ -10,7 +11,6 @@
QueryParams,
)
from src.features.consts import LocationStatus, LocationType, SiteStatus
from src.features.internal import schemas


class SiteGroupBase(BaseModel):
Expand Down
File renamed without changes.
2 changes: 0 additions & 2 deletions backend/src/register/routers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from src.features.circuit.api import router as circuit_router
from src.features.dcim.api import router as dcim_router
from src.features.intend.api import router as intend_router
from src.features.internal.api import router as internal_router
from src.features.ipam.api import router as ipam_router
from src.features.org.api import router as org_router

Expand All @@ -17,7 +16,6 @@ def register_router() -> APIRouter:
root_router.include_router(dcim_router, prefix="/dcim", tags=["DCIM"])
root_router.include_router(ipam_router, prefix="/ipam", tags=["IPAM"])
root_router.include_router(circuit_router, prefix="/circuit", tags=["Circuit"])
root_router.include_router(internal_router, prefix="/internal", tags=["Internal"])
return root_router


Expand Down
2 changes: 1 addition & 1 deletion backend/tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

@pytest.mark.parametrize(
"path",
["/api/docs", "/api/redoc", "/api/openapi.json", "/api/health", "/api/version", "/api/elements"],
["/api/docs", "/api/redoc", "/api/openapi.json", "/api/admin/health", "/api/admin/version", "/api/elements"],
)
async def test_main(client: "AsyncClient", path: str) -> None:
response = await client.get(path)
Expand Down

0 comments on commit b965e86

Please sign in to comment.