From e60b2b4eb411fe0cd0429abe61bd8171b92e9fcc Mon Sep 17 00:00:00 2001 From: Fantasy lee <129943055+Fantasylee21@users.noreply.github.com> Date: Sun, 13 Apr 2025 12:31:15 +0800 Subject: [PATCH] =?UTF-8?q?[feat]:=20=E5=AE=8C=E6=88=90=E7=AC=94=E8=AE=B0?= =?UTF-8?q?=E5=AF=BC=E5=87=BA=E5=92=8C=E8=BA=AB=E4=BB=BD=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...66\351\227\264\344\277\241\346\201\257.py" | 42 +++++++++++++++++++ ...6\351\227\264\344\277\241\346\201\2572.py" | 38 +++++++++++++++++ ...71\345\217\230\346\227\266\345\214\272.py" | 32 ++++++++++++++ ...66\345\214\272\346\222\244\351\224\200.py" | 32 ++++++++++++++ app/api/v1/endpoints/auth.py | 4 +- app/api/v1/endpoints/note.py | 18 ++++++-- app/curd/note.py | 20 ++++++++- app/main.py | 6 ++- app/models/model.py | 15 ++++++- app/routers/router.py | 4 +- app/schemas/note.py | 16 +++++++ app/utils/auth.py | 20 +++++++++ app/utils/middleware.py | 0 13 files changed, 236 insertions(+), 11 deletions(-) create mode 100644 "alembic/versions/1791499b5d10_\345\242\236\345\212\240\346\227\266\351\227\264\344\277\241\346\201\257.py" create mode 100644 "alembic/versions/4df692d79c60_\345\242\236\345\212\240\346\227\266\351\227\264\344\277\241\346\201\2572.py" create mode 100644 "alembic/versions/d60fdc9865f8_\346\224\271\345\217\230\346\227\266\345\214\272.py" create mode 100644 "alembic/versions/f1242bbcad2d_\346\224\271\345\217\230\346\227\266\345\214\272\346\222\244\351\224\200.py" create mode 100644 app/utils/auth.py create mode 100644 app/utils/middleware.py diff --git "a/alembic/versions/1791499b5d10_\345\242\236\345\212\240\346\227\266\351\227\264\344\277\241\346\201\257.py" "b/alembic/versions/1791499b5d10_\345\242\236\345\212\240\346\227\266\351\227\264\344\277\241\346\201\257.py" new file mode 100644 index 0000000..92c39f9 --- /dev/null +++ "b/alembic/versions/1791499b5d10_\345\242\236\345\212\240\346\227\266\351\227\264\344\277\241\346\201\257.py" @@ -0,0 +1,42 @@ +"""增加时间信息 + +Revision ID: 1791499b5d10 +Revises: e04ed2119f01 +Create Date: 2025-04-13 10:20:44.770496 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '1791499b5d10' +down_revision: Union[str, None] = 'e04ed2119f01' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('articles', sa.Column('create_time', sa.DateTime(), nullable=False)) + op.add_column('articles', sa.Column('update_time', sa.DateTime(), nullable=False)) + op.add_column('folders', sa.Column('create_time', sa.DateTime(), nullable=False)) + op.add_column('folders', sa.Column('update_time', sa.DateTime(), nullable=False)) + op.add_column('notes', sa.Column('create_time', sa.DateTime(), nullable=False)) + op.add_column('notes', sa.Column('update_time', sa.DateTime(), nullable=False)) + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('notes', 'update_time') + op.drop_column('notes', 'create_time') + op.drop_column('folders', 'update_time') + op.drop_column('folders', 'create_time') + op.drop_column('articles', 'update_time') + op.drop_column('articles', 'create_time') + # ### end Alembic commands ### diff --git "a/alembic/versions/4df692d79c60_\345\242\236\345\212\240\346\227\266\351\227\264\344\277\241\346\201\2572.py" "b/alembic/versions/4df692d79c60_\345\242\236\345\212\240\346\227\266\351\227\264\344\277\241\346\201\2572.py" new file mode 100644 index 0000000..2575376 --- /dev/null +++ "b/alembic/versions/4df692d79c60_\345\242\236\345\212\240\346\227\266\351\227\264\344\277\241\346\201\2572.py" @@ -0,0 +1,38 @@ +"""增加时间信息2 + +Revision ID: 4df692d79c60 +Revises: 1791499b5d10 +Create Date: 2025-04-13 10:25:25.139263 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '4df692d79c60' +down_revision: Union[str, None] = '1791499b5d10' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('groups', sa.Column('create_time', sa.DateTime(), nullable=False)) + op.add_column('groups', sa.Column('update_time', sa.DateTime(), nullable=False)) + op.add_column('tags', sa.Column('create_time', sa.DateTime(), nullable=False)) + op.add_column('tags', sa.Column('update_time', sa.DateTime(), nullable=False)) + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('tags', 'update_time') + op.drop_column('tags', 'create_time') + op.drop_column('groups', 'update_time') + op.drop_column('groups', 'create_time') + # ### end Alembic commands ### diff --git "a/alembic/versions/d60fdc9865f8_\346\224\271\345\217\230\346\227\266\345\214\272.py" "b/alembic/versions/d60fdc9865f8_\346\224\271\345\217\230\346\227\266\345\214\272.py" new file mode 100644 index 0000000..8f4252d --- /dev/null +++ "b/alembic/versions/d60fdc9865f8_\346\224\271\345\217\230\346\227\266\345\214\272.py" @@ -0,0 +1,32 @@ +"""改变时区" + +Revision ID: d60fdc9865f8 +Revises: 4df692d79c60 +Create Date: 2025-04-13 10:35:50.120925 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = 'd60fdc9865f8' +down_revision: Union[str, None] = '4df692d79c60' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### diff --git "a/alembic/versions/f1242bbcad2d_\346\224\271\345\217\230\346\227\266\345\214\272\346\222\244\351\224\200.py" "b/alembic/versions/f1242bbcad2d_\346\224\271\345\217\230\346\227\266\345\214\272\346\222\244\351\224\200.py" new file mode 100644 index 0000000..b4cdf97 --- /dev/null +++ "b/alembic/versions/f1242bbcad2d_\346\224\271\345\217\230\346\227\266\345\214\272\346\222\244\351\224\200.py" @@ -0,0 +1,32 @@ +"""改变时区撤销 + +Revision ID: f1242bbcad2d +Revises: d60fdc9865f8 +Create Date: 2025-04-13 10:38:58.146909 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = 'f1242bbcad2d' +down_revision: Union[str, None] = 'd60fdc9865f8' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### diff --git a/app/api/v1/endpoints/auth.py b/app/api/v1/endpoints/auth.py index b32394b..f14cb56 100644 --- a/app/api/v1/endpoints/auth.py +++ b/app/api/v1/endpoints/auth.py @@ -29,9 +29,9 @@ def create_access_token(data: dict, expires_delta: timedelta = None): to_encode = data.copy() if expires_delta: - expire = datetime.utcnow() + expires_delta + expire = datetime.now() + expires_delta else: - expire = datetime.utcnow() + timedelta(minutes=15) + expire = datetime.now() + timedelta(minutes=15) to_encode.update({"exp": expire}) encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) return encoded_jwt diff --git a/app/api/v1/endpoints/note.py b/app/api/v1/endpoints/note.py index b5dc8ae..f73975a 100644 --- a/app/api/v1/endpoints/note.py +++ b/app/api/v1/endpoints/note.py @@ -1,8 +1,8 @@ from fastapi import APIRouter, HTTPException, Depends from sqlalchemy.orm import Session -from app.schemas.note import NoteCreate, NoteUpdate +from app.schemas.note import NoteCreate, NoteUpdate, NoteFind from app.utils.get_db import get_db -from app.curd.note import create_note_in_db, delete_note_in_db, update_note_in_db +from app.curd.note import create_note_in_db, delete_note_in_db, update_note_in_db, find_notes_in_db router = APIRouter() @@ -24,4 +24,16 @@ def update_note(note_id: int, content: str, db: Session = Depends(get_db)): updated_note = update_note_in_db(note_id, note, db) if not updated_note: raise HTTPException(status_code=404, detail="Note not found") - return {"msg": "Note updated successfully", "note_id": updated_note.id} \ No newline at end of file + return {"msg": "Note updated successfully", "note_id": updated_note.id} + +@router.get("", response_model=dict) +def get_notes(note_find: NoteFind = Depends(), db: Session = Depends(get_db)): + notes, total_count = find_notes_in_db(note_find, db) + return { + "pagination": { + "total_count": total_count, + "page": note_find.page, + "page_size": note_find.page_size + }, + "notes": [note.model_dump() for note in notes] + } diff --git a/app/curd/note.py b/app/curd/note.py index b733f43..71072a0 100644 --- a/app/curd/note.py +++ b/app/curd/note.py @@ -1,6 +1,6 @@ from sqlalchemy.orm import Session from app.models.model import Note -from app.schemas.note import NoteCreate, NoteUpdate +from app.schemas.note import NoteCreate, NoteUpdate, NoteFind, NoteResponse def create_note_in_db(note: NoteCreate, db: Session): new_note = Note(content=note.content, article_id=note.article_id) @@ -22,4 +22,20 @@ def update_note_in_db(note_id: int, note: NoteUpdate, db: Session): existing_note.content = note.content db.commit() db.refresh(existing_note) - return existing_note \ No newline at end of file + return existing_note + +def find_notes_in_db(note_find: NoteFind, db: Session): + query = db.query(Note) + + if note_find.id is not None: + query = query.filter(Note.id == note_find.id) + elif note_find.article_id is not None: + query = query.filter(Note.article_id == note_find.article_id) + + totol_count = query.count() + # 添加分页逻辑 + if note_find.page is not None and note_find.page_size is not None: + offset = (note_find.page - 1) * note_find.page_size + query = query.offset(offset).limit(note_find.page_size) + notes = [NoteResponse.model_validate(note) for note in query.all()] + return notes, totol_count diff --git a/app/main.py b/app/main.py index 42d5e8f..a5c2c5f 100644 --- a/app/main.py +++ b/app/main.py @@ -1,5 +1,6 @@ from fastapi import FastAPI from app.routers.router import include_routers +from fastapi_pagination import add_pagination app = FastAPI() @@ -12,4 +13,7 @@ def read_item(item_id: int, q: str = None): return {"item_id": item_id, "q": q} # 注册路由 -include_routers(app) \ No newline at end of file +include_routers(app) + +# 注册分页功能 +add_pagination(app) \ No newline at end of file diff --git a/app/models/model.py b/app/models/model.py index 95bbfee..ef8cf18 100644 --- a/app/models/model.py +++ b/app/models/model.py @@ -1,5 +1,6 @@ -from sqlalchemy import Column, Integer, String, Boolean, Table, ForeignKey, UniqueConstraint, CheckConstraint, Text +from sqlalchemy import Column, Integer, String, Boolean, Table, ForeignKey, UniqueConstraint, CheckConstraint, Text, DateTime from sqlalchemy.orm import relationship +from sqlalchemy.sql import func from app.db.base_class import Base # 多对多关系表 @@ -28,6 +29,8 @@ class Group(Base): leader = Column(Integer) users = relationship('User', secondary=user_group, back_populates='groups') folders = relationship('Folder', back_populates='group') + create_time = Column(DateTime, default=func.now(), nullable=False) # 创建时间 + update_time = Column(DateTime, default=func.now(), onupdate=func.now(), nullable=False) # 更新时间 class Folder(Base): __tablename__ = 'folders' @@ -37,6 +40,9 @@ class Folder(Base): user_id = Column(Integer, ForeignKey('users.id')) group_id = Column(Integer, ForeignKey('groups.id')) + + create_time = Column(DateTime, default=func.now(), nullable=False) # 创建时间 + update_time = Column(DateTime, default=func.now(), onupdate=func.now(), nullable=False) # 更新时间 # 关系定义 user = relationship('User', back_populates='folders') @@ -53,6 +59,8 @@ class Article(Base): id = Column(Integer, primary_key=True, index=True, autoincrement=True) name = Column(String(30), nullable=False) folder_id = Column(Integer, ForeignKey('folders.id')) + create_time = Column(DateTime, default=func.now(), nullable=False) # 创建时间 + update_time = Column(DateTime, default=func.now(), onupdate=func.now(), nullable=False) # 更新时间 folder = relationship('Folder', back_populates='articles') notes = relationship('Note', back_populates='article') @@ -64,6 +72,8 @@ class Note(Base): id = Column(Integer, primary_key=True, index=True, autoincrement=True) content = Column(Text) # 将 content 字段类型改为 Text,以支持存储大量文本 article_id = Column(Integer, ForeignKey('articles.id')) + create_time = Column(DateTime, default=func.now(), nullable=False) # 创建时间 + update_time = Column(DateTime, default=func.now(), onupdate=func.now(), nullable=False) # 更新时间 article = relationship('Article', back_populates='notes') @@ -73,5 +83,6 @@ class Tag(Base): id = Column(Integer, primary_key=True, index=True, autoincrement=True) content = Column(String(30)) article_id = Column(Integer, ForeignKey('articles.id')) - + create_time = Column(DateTime, default=func.now(), nullable=False) # 创建时间 + update_time = Column(DateTime, default=func.now(), onupdate=func.now(), nullable=False) # 更新时间 article = relationship('Article', back_populates='tags') \ No newline at end of file diff --git a/app/routers/router.py b/app/routers/router.py index 01603d1..5d0118c 100644 --- a/app/routers/router.py +++ b/app/routers/router.py @@ -1,3 +1,5 @@ +from fastapi import Depends +from app.utils.auth import get_current_user from app.api.v1.endpoints.auth import router as auth_router from app.api.v1.endpoints.note import router as note_router @@ -5,7 +7,7 @@ def include_auth_router(app): app.include_router(auth_router, prefix="/public", tags=["auth"]) def include_note_router(app): - app.include_router(note_router, prefix="/notes", tags=["note"]) + app.include_router(note_router, prefix="/notes", tags=["note"], dependencies=[Depends(get_current_user)]) def include_routers(app): include_auth_router(app) diff --git a/app/schemas/note.py b/app/schemas/note.py index 14c67cd..6efb3b5 100644 --- a/app/schemas/note.py +++ b/app/schemas/note.py @@ -1,3 +1,4 @@ +from datetime import datetime from pydantic import BaseModel class NoteCreate(BaseModel): @@ -11,3 +12,18 @@ class NoteUpdate(BaseModel): id: int content: str +class NoteFind(BaseModel): + id: int | None = None + article_id: int | None = None + page: int | None = None + page_size: int | None = None + +class NoteResponse(BaseModel): + id: int + content: str + article_id: int + create_time: datetime + update_time: datetime + + class Config: + from_attributes = True \ No newline at end of file diff --git a/app/utils/auth.py b/app/utils/auth.py new file mode 100644 index 0000000..1bd6667 --- /dev/null +++ b/app/utils/auth.py @@ -0,0 +1,20 @@ +from fastapi.security import OAuth2PasswordBearer +from jwt import PyJWTError, decode +from app.core.config import settings +from fastapi import Depends, HTTPException +# 配置 OAuth2PasswordBearer +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/login") + +def get_current_user(token: str = Depends(oauth2_scheme)): + try: + payload = decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]) + email: str = payload.get("sub") + if email is None: + raise HTTPException( + status_code=401, detail="Invalid authentication credentials" + ) + return {"email": email} + except PyJWTError: + raise HTTPException( + status_code=401, detail="Invalid authentication credentials" + ) \ No newline at end of file diff --git a/app/utils/middleware.py b/app/utils/middleware.py new file mode 100644 index 0000000..e69de29