From 23474a242226cdea98c9856a628103bac3ec5190 Mon Sep 17 00:00:00 2001 From: fit2cloud-chenyw Date: Fri, 25 Apr 2025 17:52:58 +0800 Subject: [PATCH] feat: Terminology module --- .env.example | 1 + backend/alembic/env.py | 3 +- .../1c8bcc7e25c8_002_ddl_autogenerate.py | 50 ++++++++++++++ backend/apps/api.py | 3 +- backend/apps/settings/__init__.py | 0 backend/apps/settings/api/__init__.py | 0 backend/apps/settings/api/terminology.py | 23 +++++++ backend/apps/settings/models/__init__.py | 0 .../apps/settings/models/setting_models.py | 12 ++++ backend/apps/settings/schemas/__init__.py | 0 .../apps/settings/schemas/setting_schemas.py | 8 +++ backend/common/core/pagination.py | 69 +++++++++++++++++++ backend/common/core/schemas.py | 22 +++++- 13 files changed, 187 insertions(+), 4 deletions(-) create mode 100644 backend/alembic/versions/1c8bcc7e25c8_002_ddl_autogenerate.py create mode 100644 backend/apps/settings/__init__.py create mode 100644 backend/apps/settings/api/__init__.py create mode 100644 backend/apps/settings/api/terminology.py create mode 100644 backend/apps/settings/models/__init__.py create mode 100644 backend/apps/settings/models/setting_models.py create mode 100644 backend/apps/settings/schemas/__init__.py create mode 100644 backend/apps/settings/schemas/setting_schemas.py create mode 100644 backend/common/core/pagination.py diff --git a/.env.example b/.env.example index baf38eef..d211818c 100644 --- a/.env.example +++ b/.env.example @@ -12,6 +12,7 @@ SECRET_KEY=y5txe1mRmS_JpOrUzFzHEu-kIQn3lf7ll0AOv9DQh0s FIRST_SUPERUSER=admin@example.com FIRST_SUPERUSER_PASSWORD=123456 # Change this to your pwd +TOKEN_KEY="X-SQLBOT-TOKEN" # Postgres POSTGRES_SERVER=localhost diff --git a/backend/alembic/env.py b/backend/alembic/env.py index 0278e381..f76e0363 100755 --- a/backend/alembic/env.py +++ b/backend/alembic/env.py @@ -18,7 +18,8 @@ fileConfig(config.config_file_name) # target_metadata = mymodel.Base.metadata # target_metadata = None -from apps.system.models.user import SQLModel # noqa +# from apps.system.models.user import SQLModel # noqa +from apps.settings.models.setting_models import SQLModel from common.core.config import settings # noqa target_metadata = SQLModel.metadata diff --git a/backend/alembic/versions/1c8bcc7e25c8_002_ddl_autogenerate.py b/backend/alembic/versions/1c8bcc7e25c8_002_ddl_autogenerate.py new file mode 100644 index 00000000..4519ba21 --- /dev/null +++ b/backend/alembic/versions/1c8bcc7e25c8_002_ddl_autogenerate.py @@ -0,0 +1,50 @@ +"""002_ddl_autogenerate + +Revision ID: 1c8bcc7e25c8 +Revises: 5348b743b05f +Create Date: 2025-04-25 17:47:18.795288 + +""" +from alembic import op +import sqlalchemy as sa +import sqlmodel.sql.sqltypes + + +# revision identifiers, used by Alembic. +revision = '1c8bcc7e25c8' +down_revision = '5348b743b05f' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('terms', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('term', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=False), + sa.Column('definition', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=False), + sa.Column('domain', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=False), + sa.Column('create_time', sa.Integer(), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_terms_id'), 'terms', ['id'], unique=False) + op.drop_table('sys_user') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('sys_user', + sa.Column('id', sa.BIGINT(), autoincrement=True, nullable=False), + sa.Column('account', sa.VARCHAR(length=255), autoincrement=False, nullable=False), + sa.Column('name', sa.VARCHAR(length=255), autoincrement=False, nullable=False), + sa.Column('password', sa.VARCHAR(length=255), autoincrement=False, nullable=False), + sa.Column('email', sa.VARCHAR(length=255), autoincrement=False, nullable=True), + sa.Column('oid', sa.BIGINT(), autoincrement=False, nullable=False), + sa.Column('status', sa.INTEGER(), autoincrement=False, nullable=False), + sa.Column('create_time', sa.BIGINT(), autoincrement=False, nullable=False), + sa.PrimaryKeyConstraint('id', name='sys_user_pkey') + ) + op.drop_index(op.f('ix_terms_id'), table_name='terms') + op.drop_table('terms') + # ### end Alembic commands ### diff --git a/backend/apps/api.py b/backend/apps/api.py index eeda98de..d1d32f59 100644 --- a/backend/apps/api.py +++ b/backend/apps/api.py @@ -1,10 +1,11 @@ from fastapi import APIRouter from apps.system.api import login, user - +from apps.settings.api import terminology api_router = APIRouter() api_router.include_router(login.router) api_router.include_router(user.router) +api_router.include_router(terminology.router) diff --git a/backend/apps/settings/__init__.py b/backend/apps/settings/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/apps/settings/api/__init__.py b/backend/apps/settings/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/apps/settings/api/terminology.py b/backend/apps/settings/api/terminology.py new file mode 100644 index 00000000..55734eaa --- /dev/null +++ b/backend/apps/settings/api/terminology.py @@ -0,0 +1,23 @@ +from typing import Optional +from fastapi import APIRouter, Depends, Request +from apps.settings.models.setting_models import term_model +# from common.core.db import get_session +from common.core.deps import SessionDep +from common.core.pagination import Paginator +from common.core.schemas import PaginatedResponse, PaginationParams +router = APIRouter(tags=["Settings"], prefix="/settings/terminology") + + +@router.get("/pager/{pageNum}/{pageSize}", response_model=PaginatedResponse[term_model]) +async def pager( + session: SessionDep, + pageNum: int, + pageSize: int +): + pagination = PaginationParams(page=pageNum, size=pageSize) + paginator = Paginator(session) + filters = {} + return await paginator.get_paginated_response( + model=term_model, + pagination=pagination, + **filters) \ No newline at end of file diff --git a/backend/apps/settings/models/__init__.py b/backend/apps/settings/models/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/apps/settings/models/setting_models.py b/backend/apps/settings/models/setting_models.py new file mode 100644 index 00000000..83fc5a60 --- /dev/null +++ b/backend/apps/settings/models/setting_models.py @@ -0,0 +1,12 @@ +from sqlmodel import Field, SQLModel + + +class term_model(SQLModel, table=True): + __tablename__ = "terms" + + id: int = Field(primary_key=True, index=True) + term: str = Field(max_length=255) + definition: str = Field(max_length=255) + domain: str = Field(max_length=255) + create_time: int = Field(default=0) + \ No newline at end of file diff --git a/backend/apps/settings/schemas/__init__.py b/backend/apps/settings/schemas/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/apps/settings/schemas/setting_schemas.py b/backend/apps/settings/schemas/setting_schemas.py new file mode 100644 index 00000000..a4b11401 --- /dev/null +++ b/backend/apps/settings/schemas/setting_schemas.py @@ -0,0 +1,8 @@ +from pydantic import BaseModel + +class term_schema(BaseModel): + id: int + term: str + definition: str + domain: str + \ No newline at end of file diff --git a/backend/common/core/pagination.py b/backend/common/core/pagination.py new file mode 100644 index 00000000..32a2f94d --- /dev/null +++ b/backend/common/core/pagination.py @@ -0,0 +1,69 @@ +from sqlmodel.ext.asyncio.session import AsyncSession +from sqlmodel import select, func, SQLModel +from typing import Type, TypeVar, Sequence, Optional +from common.core.schemas import PaginationParams, PaginatedResponse + +ModelT = TypeVar('ModelT', bound=SQLModel) + +class Paginator: + def __init__(self, session: AsyncSession): + self.session = session + + async def paginate( + self, + model: Type[ModelT], + page: int = 1, + size: int = 20, + order_by: Optional[str] = None, + desc: bool = False, + **filters + ) -> tuple[Sequence[ModelT], int]: + offset = (page - 1) * size + stmt = select(model) + + for field, value in filters.items(): + if value is not None: + stmt = stmt.where(getattr(model, field) == value) + + if order_by: + column = getattr(model, order_by) + stmt = stmt.order_by(column.desc() if desc else column.asc()) + + count_stmt = select(func.count()).select_from(model) + for field, value in filters.items(): + if value is not None: + count_stmt = count_stmt.where(getattr(model, field) == value) + + total = (await self.session.execute(count_stmt)).scalar_one() + + stmt = stmt.offset(offset).limit(size) + + result = await self.session.execute(stmt) + items = result.scalars().all() + + return items, total + + async def get_paginated_response( + self, + model: Type[ModelT], + pagination: PaginationParams, + **filters + ) -> PaginatedResponse[ModelT]: + items, total = await self.paginate( + model=model, + page=pagination.page, + size=pagination.size, + order_by=pagination.order_by, + desc=pagination.desc, + **filters + ) + + total_pages = (total + pagination.size - 1) // pagination.size + + return PaginatedResponse[ModelT]( + items=items, + total=total, + page=pagination.page, + size=pagination.size, + total_pages=total_pages + ) \ No newline at end of file diff --git a/backend/common/core/schemas.py b/backend/common/core/schemas.py index d383da6d..a4637ff8 100644 --- a/backend/common/core/schemas.py +++ b/backend/common/core/schemas.py @@ -1,4 +1,3 @@ -from typing import Optional from fastapi import HTTPException, Request from fastapi.security import OAuth2PasswordBearer from pydantic import BaseModel @@ -6,6 +5,7 @@ from sqlmodel import SQLModel from starlette.status import HTTP_403_FORBIDDEN, HTTP_401_UNAUTHORIZED from common.core.config import settings from fastapi.security.utils import get_authorization_scheme_param +from typing import Generic, TypeVar, Optional class TokenPayload(BaseModel): account: str | None = None id: int | None = None @@ -28,4 +28,22 @@ class XOAuth2PasswordBearer(OAuth2PasswordBearer): ) else: return None - return param \ No newline at end of file + return param + + + + +T = TypeVar('T') + +class PaginationParams(BaseModel): + page: int = 1 + size: int = 20 + order_by: Optional[str] = None + desc: bool = False + +class PaginatedResponse(BaseModel, Generic[T]): + items: list[T] + total: int + page: int + size: int + total_pages: int \ No newline at end of file