Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""个人回收站表增加上级信息

Revision ID: 004c4aa2b3f3
Revises: d6d6ae6d9680
Create Date: 2025-05-21 21:29:14.873544

"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision: str = '004c4aa2b3f3'
down_revision: Union[str, None] = 'd6d6ae6d9680'
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.drop_constraint('articles_ibfk_1', 'articles', type_='foreignkey')
op.create_foreign_key(None, 'articles', 'folders', ['folder_id'], ['id'], ondelete='CASCADE')
op.drop_constraint('notes_ibfk_1', 'notes', type_='foreignkey')
op.create_foreign_key(None, 'notes', 'articles', ['article_id'], ['id'], ondelete='CASCADE')
op.add_column('self_recycle_bin', sa.Column('article_id', sa.Integer(), nullable=True))
op.add_column('self_recycle_bin', sa.Column('folder_id', sa.Integer(), nullable=True))
op.create_foreign_key(None, 'self_recycle_bin', 'folders', ['folder_id'], ['id'], ondelete='CASCADE')
op.create_foreign_key(None, 'self_recycle_bin', 'articles', ['article_id'], ['id'], ondelete='CASCADE')
# ### end Alembic commands ###


def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(None, 'self_recycle_bin', type_='foreignkey')
op.drop_constraint(None, 'self_recycle_bin', type_='foreignkey')
op.drop_column('self_recycle_bin', 'folder_id')
op.drop_column('self_recycle_bin', 'article_id')
op.drop_constraint(None, 'notes', type_='foreignkey')
op.create_foreign_key('notes_ibfk_1', 'notes', 'articles', ['article_id'], ['id'])
op.drop_constraint(None, 'articles', type_='foreignkey')
op.create_foreign_key('articles_ibfk_1', 'articles', 'folders', ['folder_id'], ['id'])
# ### end Alembic commands ###
40 changes: 40 additions & 0 deletions alembic/versions/d6d6ae6d9680_增加个人回收站表.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""增加个人回收站表

Revision ID: d6d6ae6d9680
Revises: 7af566a6091b
Create Date: 2025-05-14 11:25:12.719964

"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision: str = 'd6d6ae6d9680'
down_revision: Union[str, None] = '7af566a6091b'
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.create_table('self_recycle_bin',
sa.Column('user_id', sa.Integer(), nullable=True),
sa.Column('type', sa.Integer(), nullable=False),
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.Text(), nullable=False),
sa.Column('create_time', sa.DateTime(), nullable=False),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
sa.PrimaryKeyConstraint('type', 'id')
)
# ### end Alembic commands ###


def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('self_recycle_bin')
# ### end Alembic commands ###
30 changes: 24 additions & 6 deletions app/api/v1/endpoints/article.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from app.utils.get_db import get_db
from app.utils.auth import get_current_user
from app.curd.article import crud_upload_to_self_folder, crud_get_self_folders, crud_get_articles_in_folder, crud_self_create_folder, crud_self_article_to_recycle_bin, crud_self_folder_to_recycle_bin, crud_read_article, crud_import_self_folder, crud_export_self_folder,crud_create_tag, crud_delete_tag, crud_get_article_tags, crud_all_tags_order, crud_change_folder_name, crud_change_article_name, crud_article_statistic, crud_self_tree, crud_self_article_statistic
from app.curd.article import crud_upload_to_self_folder, crud_get_self_folders, crud_get_articles_in_folder, crud_self_create_folder, crud_self_article_to_recycle_bin, crud_self_folder_to_recycle_bin, crud_read_article, crud_import_self_folder, crud_export_self_folder,crud_create_tag, crud_delete_tag, crud_get_article_tags, crud_all_tags_order, crud_change_folder_name, crud_change_article_name, crud_article_statistic, crud_self_tree, crud_self_article_statistic, crud_items_in_recycle_bin, crud_delete_forever, crud_recover
from app.schemas.article import SelfCreateFolder

router = APIRouter()
Expand Down Expand Up @@ -72,13 +72,15 @@ async def self_create_folder(model: SelfCreateFolder, db: AsyncSession = Depends
return {"msg": "User Folder Created Successfully", "folder_id": folder_id}

@router.delete("/selfArticleToRecycleBin", response_model="dict")
async def self_article_to_recycle_bin(article_id: int = Query(...), db: AsyncSession = Depends(get_db)):
await crud_self_article_to_recycle_bin(article_id, db)
async def self_article_to_recycle_bin(article_id: int = Query(...), db: AsyncSession = Depends(get_db), user: dict = Depends(get_current_user)):
user_id = user.get("id")
await crud_self_article_to_recycle_bin(article_id, user_id, db)
return {"msg": "Article is moved to recycle bin"}

@router.delete("/selfFolderToRecycleBin", response_model="dict")
async def self_folder_to_recycle_bin(folder_id: int = Query(...), db: AsyncSession = Depends(get_db)):
await crud_self_folder_to_recycle_bin(folder_id, db)
async def self_folder_to_recycle_bin(folder_id: int = Query(...), db: AsyncSession = Depends(get_db), user: dict = Depends(get_current_user)):
user_id = user.get("id")
await crud_self_folder_to_recycle_bin(folder_id, user_id, db)
return {"msg": "Folder is moved to recycle bin"}

@router.post("/annotateSelfArticle", response_model="dict")
Expand Down Expand Up @@ -201,4 +203,20 @@ async def self_tree(page_number: Optional[int] = Query(None, ge=1), page_size: O
async def self_article_statistic(db: AsyncSession = Depends(get_db), user: dict = Depends(get_current_user)):
user_id = user.get("id")
article_total_num, articles = await crud_self_article_statistic(user_id, db)
return {"article_total_num": article_total_num, "articles": articles}
return {"article_total_num": article_total_num, "articles": articles}

@router.get("/itemsInRecycleBin", response_model=dict)
async def items_in_recycle_bin(page_number: Optional[int] = Query(None, ge=1), page_size: Optional[int] = Query(None, ge=1), db: AsyncSession = Depends(get_db), user: dict = Depends(get_current_user)):
user_id = user.get("id")
items = await crud_items_in_recycle_bin(user_id, page_number, page_size, db)
return {"items": items}

@router.delete("/deleteForever", response_model=dict)
async def delete_forever(type: int = Query(...), id: int = Query(...), db: AsyncSession = Depends(get_db)):
await crud_delete_forever(type, id, db)
return {"msg": "Item and its child nodes deleted forever successfully"}

@router.post("/recover", response_model=dict)
async def recover(type: int = Body(...), id: int = Body(...), db: AsyncSession = Depends(get_db)):
return_value = await crud_recover(type, id, db)
return return_value
5 changes: 3 additions & 2 deletions app/api/v1/endpoints/note.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ async def create_note(note: NoteCreate, db: AsyncSession = Depends(get_db), curr
return {"msg": "Note created successfully", "note_id": new_note.id}

@router.delete("/{note_id}", response_model=dict)
async def delete_note(note_id: int, db: AsyncSession = Depends(get_db)):
note = await delete_note_in_db(note_id, db)
async def delete_note(note_id: int, db: AsyncSession = Depends(get_db), current_user: dict = Depends(get_current_user)):
user_id = current_user["id"]
note = await delete_note_in_db(note_id, user_id, db)
if not note:
raise HTTPException(status_code=404, detail="Note not found")
return {"msg": "Note deleted successfully"}
Expand Down
127 changes: 112 additions & 15 deletions app/curd/article.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, delete
from sqlalchemy import select, delete, insert, desc
from sqlalchemy import func, cast, Date
from datetime import datetime, timedelta
from app.models.model import User, Group, Folder, Article, Note, Tag, user_group
from app.models.model import User, Group, Folder, Article, Note, Tag, user_group, self_recycle_bin

async def crud_upload_to_self_folder(name: str, folder_id: int, db: AsyncSession):
new_article = Article(name=name, folder_id=folder_id)
Expand Down Expand Up @@ -46,25 +46,31 @@ async def crud_self_create_folder(name: str, user_id: int, db: AsyncSession):
await db.refresh(new_folder)
return new_folder.id

async def crud_self_article_to_recycle_bin(article_id: int, db: AsyncSession):
# 查询 article
async def crud_self_article_to_recycle_bin(article_id: int, user_id: int, db: AsyncSession):
# 维护 article
query = select(Article).where(Article.id == article_id)
result = await db.execute(query)
article = result.scalar_one_or_none()

# 修改 visible 字段
article.visible = False

# 维护 self_recycle_bin 表
recycle = insert(self_recycle_bin).values(user_id=user_id, type=2, id=article_id, name=article.name, folder_id=article.folder_id)
await db.execute(recycle)

await db.commit()
await db.refresh(article)

async def crud_self_folder_to_recycle_bin(folder_id: int, db: AsyncSession):
# 查询 folder
async def crud_self_folder_to_recycle_bin(folder_id: int, user_id: int, db: AsyncSession):
# 维护 folder
query = select(Folder).where(Folder.id == folder_id)
result = await db.execute(query)
folder = result.scalar_one_or_none()

# 修改 visible 字段
folder.visible = False

# 维护 self_recycle_bin 表
recycle = insert(self_recycle_bin).values(user_id=user_id, type=1, id=folder_id, name=folder.name)
await db.execute(recycle)

await db.commit()
await db.refresh(folder)

Expand Down Expand Up @@ -166,11 +172,11 @@ async def crud_article_statistic(db: AsyncSession):
tomorrow = datetime.now().date() + timedelta(days=1)
seven_days_ago = datetime.now().date() - timedelta(days=6)

# 查询近7天内的笔记数目,按日期分组
# 查询近7天内的文献数目,按日期分组
query = (
select(
cast(Article.create_time, Date).label("date"), # 按日期分组
func.count(Article.id).label("count") # 统计每日期的笔记数
func.count(Article.id).label("count") # 统计每日期的文献数
)
.where(
Article.create_time >= seven_days_ago, # 大于等于7天前的0点
Expand Down Expand Up @@ -243,11 +249,11 @@ async def crud_self_article_statistic(user_id: int, db: AsyncSession):
tomorrow = datetime.now().date() + timedelta(days=1)
seven_days_ago = datetime.now().date() - timedelta(days=6)

# 查询近7天内的笔记数目,按日期分组
# 查询近7天内的文献数目,按日期分组
query = (
select(
cast(Article.create_time, Date).label("date"), # 按日期分组
func.count(Article.id).label("count") # 统计每日期的笔记数
func.count(Article.id).label("count") # 统计每日期的文献数
)
.join(Folder, Article.folder_id == Folder.id)
.where(
Expand All @@ -273,4 +279,95 @@ async def crud_self_article_statistic(user_id: int, db: AsyncSession):
if i == len(articles) or articles[i].get("date") != seven_days_ago + timedelta(days=i):
articles.insert(i, {"date": seven_days_ago + timedelta(days=i), "count": 0})

return article_total_num, articles
return article_total_num, articles

async def crud_items_in_recycle_bin(user_id: int, page_number: int, page_size: int, db: AsyncSession):
query = select(
self_recycle_bin.c.type,
self_recycle_bin.c.id,
self_recycle_bin.c.name,
self_recycle_bin.c.create_time
).where(self_recycle_bin.c.user_id == user_id).order_by(desc(self_recycle_bin.c.create_time))

if page_number and page_size:
offset = (page_number - 1) * page_size
query = query.offset(offset).limit(page_size)

result = await db.execute(query)
items = result.fetchall()

return [{"type": item.type, "id": item.id, "name": item.name, "time": item.create_time.strftime("%Y-%m-%d %H:%M:%S")} for item in items]

async def crud_delete_forever(type: int, id: int, db: AsyncSession):
query = delete(self_recycle_bin).where(self_recycle_bin.c.type == type, self_recycle_bin.c.id == id)
await db.execute(query)
if type == 1:
query = delete(Folder).where(Folder.id==id)
elif type == 2:
query = delete(Article).where(Article.id==id)
else:
query = delete(Note).where(Note.id==id)
await db.execute(query)
await db.commit()

async def crud_recover(type: int, id: int, db: AsyncSession):
query = select(self_recycle_bin).where(self_recycle_bin.c.type == type, self_recycle_bin.c.id == id)
result = await db.execute(query)
item = result.first()
if type == 3:
# 检查上级文献存在性
query = select(Article).where(Article.id == item.article_id)
result = await db.execute(query)
article = result.scalar_one_or_none()
article_name = article.name
article_visible = article.visible
# 检查上级文件夹存在性
query = select(Folder).where(Folder.id == item.folder_id)
result = await db.execute(query)
folder = result.scalar_one_or_none()
folder_name = folder.name
folder_visible = folder.visible
# 若上级不存在,则给用户以提示信息,请用户先恢复相应的文件夹和文献
if not article_visible or not folder_visible:
return {"info": "Note recovered failed, please check its upper-level node", "folder_name": folder_name, "article_name": article_name}
# 若上级存在,则正常恢复即可,在回收站表中删除该表项,并将Note表中visible改为True
query = delete(self_recycle_bin).where(self_recycle_bin.c.type == type, self_recycle_bin.c.id == id)
await db.execute(query)
query = select(Note).where(Note.id == id)
result = await db.execute(query)
note = result.scalar_one_or_none()
note.visible = True
await db.commit()
await db.refresh(note)
return {"info": "Note recovered successfully"}
if type == 2:
# 检查上级文件夹存在性
query = select(Folder).where(Folder.id == item.folder_id)
result = await db.execute(query)
folder = result.scalar_one_or_none()
folder_name = folder.name
folder_visible = folder.visible
# 若上级不存在,则给用户以提示信息,请用户先恢复相应的文件夹
if not folder_visible:
return {"info": "Article recovered failed, please check its upper-level node", "folder_name": folder_name}
# 若上级存在,则正常恢复即可,在回收站表中删除该表项,并将Article表中visible改为True
query = delete(self_recycle_bin).where(self_recycle_bin.c.type == type, self_recycle_bin.c.id == id)
await db.execute(query)
query = select(Article).where(Article.id == id)
result = await db.execute(query)
article = result.scalar_one_or_none()
article.visible = True
await db.commit()
await db.refresh(article)
return {"info": "Article recovered successfully"}
if type == 1:
# 正常恢复即可,在回收站表中删除该表项,并将Folder表中visible改为True
query = delete(self_recycle_bin).where(self_recycle_bin.c.type == type, self_recycle_bin.c.id == id)
await db.execute(query)
query = select(Folder).where(Folder.id == id)
result = await db.execute(query)
folder = result.scalar_one_or_none()
folder.visible = True
await db.commit()
await db.refresh(folder)
return {"info": "Folder recovered successfully"}
Loading