2025-11-23 09:08:01 +00:00
|
|
|
from typing import List
|
|
|
|
|
|
2025-12-08 22:08:30 +00:00
|
|
|
from app.auth import require_auth
|
2025-11-23 09:08:01 +00:00
|
|
|
from app.database import get_session
|
|
|
|
|
from app.models import (
|
|
|
|
|
Folder,
|
|
|
|
|
FolderCreate,
|
|
|
|
|
FolderTreeNode,
|
|
|
|
|
FolderTreeResponse,
|
2025-11-30 19:40:10 +00:00
|
|
|
FolderUpdate,
|
2025-11-23 09:08:01 +00:00
|
|
|
Note,
|
|
|
|
|
NoteRead,
|
2025-12-08 22:08:30 +00:00
|
|
|
User,
|
2025-11-23 09:08:01 +00:00
|
|
|
)
|
2025-12-15 21:33:00 +00:00
|
|
|
from fastapi import APIRouter, Depends, HTTPException # type: ignore
|
|
|
|
|
from sqlalchemy.orm import selectinload
|
|
|
|
|
from sqlmodel import Session, select # type: ignore
|
2025-11-23 09:08:01 +00:00
|
|
|
|
|
|
|
|
router = APIRouter(prefix="/folders", tags=["folders"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def build_folder_tree_node(folder: Folder) -> FolderTreeNode:
|
|
|
|
|
"""Recursively build a folder tree node with notes and children"""
|
|
|
|
|
return FolderTreeNode(
|
2025-12-08 22:08:30 +00:00
|
|
|
id=folder.id, # pyright: ignore[reportArgumentType]
|
2025-11-23 09:08:01 +00:00
|
|
|
name=folder.name,
|
|
|
|
|
notes=[NoteRead.model_validate(note) for note in folder.notes],
|
|
|
|
|
children=[build_folder_tree_node(child) for child in folder.children],
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/tree", response_model=FolderTreeResponse)
|
2025-12-08 22:08:30 +00:00
|
|
|
def get_folder_tree(
|
|
|
|
|
current_user: User = Depends(require_auth), session: Session = Depends(get_session)
|
|
|
|
|
):
|
2025-11-23 09:08:01 +00:00
|
|
|
"""Get complete folder tree with notes"""
|
|
|
|
|
|
|
|
|
|
top_level_folders = session.exec(
|
2025-12-08 22:08:30 +00:00
|
|
|
select(Folder)
|
2025-12-15 21:33:00 +00:00
|
|
|
.options(selectinload(Folder.notes).selectinload(Note.tags))
|
2025-12-08 22:08:30 +00:00
|
|
|
.where(Folder.parent_id == None)
|
|
|
|
|
.where(Folder.user_id == current_user.id)
|
2025-11-23 09:08:01 +00:00
|
|
|
).all()
|
|
|
|
|
|
2025-12-08 22:08:30 +00:00
|
|
|
orphaned_notes = session.exec(
|
|
|
|
|
select(Note)
|
2025-12-15 21:33:00 +00:00
|
|
|
.options(selectinload(Note.tags))
|
2025-12-08 22:08:30 +00:00
|
|
|
.where(Note.folder_id == None)
|
|
|
|
|
.where(Note.user_id == current_user.id)
|
|
|
|
|
).all()
|
2025-11-23 09:08:01 +00:00
|
|
|
|
|
|
|
|
tree = [build_folder_tree_node(folder) for folder in top_level_folders]
|
|
|
|
|
|
|
|
|
|
return FolderTreeResponse(
|
|
|
|
|
folders=tree,
|
|
|
|
|
orphaned_notes=[NoteRead.model_validate(note) for note in orphaned_notes],
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/", response_model=List[Folder])
|
|
|
|
|
def list_folders(session: Session = Depends(get_session)):
|
|
|
|
|
"""Get flat list of all folders"""
|
|
|
|
|
folders = session.exec(select(Folder)).all()
|
|
|
|
|
return folders
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/", response_model=Folder)
|
2025-12-08 22:08:30 +00:00
|
|
|
def create_folder(
|
|
|
|
|
folder: FolderCreate,
|
|
|
|
|
current_user: User = Depends(require_auth),
|
|
|
|
|
session: Session = Depends(get_session),
|
|
|
|
|
):
|
2025-11-23 09:08:01 +00:00
|
|
|
"""Create a new folder"""
|
2025-12-08 22:08:30 +00:00
|
|
|
folder_data = folder.model_dump()
|
|
|
|
|
folder_data["user_id"] = current_user.id
|
|
|
|
|
db_folder = Folder.model_validate(folder_data)
|
2025-11-23 09:08:01 +00:00
|
|
|
session.add(db_folder)
|
|
|
|
|
session.commit()
|
|
|
|
|
session.refresh(db_folder)
|
|
|
|
|
return db_folder
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.delete("/{folder_id}")
|
|
|
|
|
def delete_folder(folder_id: int, session: Session = Depends(get_session)):
|
|
|
|
|
"""Delete a folder"""
|
|
|
|
|
folder = session.get(Folder, folder_id)
|
|
|
|
|
if not folder:
|
|
|
|
|
raise HTTPException(status_code=404, detail="Folder not found")
|
|
|
|
|
|
|
|
|
|
session.delete(folder)
|
|
|
|
|
session.commit()
|
|
|
|
|
return {"message": "Folder deleted"}
|
2025-11-30 19:40:10 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.patch("/{folder_id}")
|
|
|
|
|
def update_folder(
|
|
|
|
|
folder_id: int, folder_update: FolderUpdate, session: Session = Depends(get_session)
|
|
|
|
|
):
|
|
|
|
|
"""Update a folder"""
|
|
|
|
|
folder = session.get(Folder, folder_id)
|
|
|
|
|
if not folder:
|
|
|
|
|
raise HTTPException(status_code=404, detail="Folder not found")
|
|
|
|
|
|
|
|
|
|
update_data = folder_update.model_dump(exclude_unset=True)
|
|
|
|
|
|
|
|
|
|
for key, value in update_data.items():
|
|
|
|
|
setattr(folder, key, value)
|
|
|
|
|
|
|
|
|
|
session.add(folder)
|
|
|
|
|
session.commit()
|
|
|
|
|
|
|
|
|
|
session.refresh(folder)
|
|
|
|
|
|
|
|
|
|
return folder
|