Add FolderUpdate model and folder update endpoint
This commit is contained in:
parent
fb461df550
commit
b27604130a
9 changed files with 230 additions and 93 deletions
Binary file not shown.
|
|
@ -67,3 +67,8 @@ class NoteUpdate(SQLModel):
|
|||
class FolderCreate(SQLModel):
|
||||
name: str
|
||||
parent_id: Optional[int] = None
|
||||
|
||||
|
||||
class FolderUpdate(SQLModel):
|
||||
name: Optional[str] = None
|
||||
parent_id: Optional[int] = None
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -9,6 +9,7 @@ from app.models import (
|
|||
FolderCreate,
|
||||
FolderTreeNode,
|
||||
FolderTreeResponse,
|
||||
FolderUpdate,
|
||||
Note,
|
||||
NoteRead,
|
||||
)
|
||||
|
|
@ -74,3 +75,52 @@ def delete_folder(folder_id: int, session: Session = Depends(get_session)):
|
|||
session.delete(folder)
|
||||
session.commit()
|
||||
return {"message": "Folder deleted"}
|
||||
|
||||
|
||||
@router.patch("/{folder_id}")
|
||||
def update_folder(
|
||||
folder_id: int, folder_update: FolderUpdate, session: Session = Depends(get_session)
|
||||
):
|
||||
"""Update a folder"""
|
||||
print(f"=== UPDATE FOLDER CALLED ===")
|
||||
print(f"Folder ID: {folder_id}")
|
||||
print(f"Update data received: {folder_update}")
|
||||
|
||||
folder = session.get(Folder, folder_id)
|
||||
if not folder:
|
||||
raise HTTPException(status_code=404, detail="Folder not found")
|
||||
|
||||
print(
|
||||
f"Found folder: id={folder.id}, name={folder.name}, parent_id={folder.parent_id}"
|
||||
)
|
||||
|
||||
# Update folder attributes from the request body
|
||||
update_data = folder_update.model_dump(exclude_unset=True)
|
||||
print(f"Update data dict (exclude_unset): {update_data}")
|
||||
print(f"Update data keys: {list(update_data.keys())}")
|
||||
|
||||
for key, value in update_data.items():
|
||||
print(f"Setting {key} = {value}")
|
||||
setattr(folder, key, value)
|
||||
|
||||
print(
|
||||
f"After setattr: id={folder.id}, name={folder.name}, parent_id={folder.parent_id}"
|
||||
)
|
||||
|
||||
session.add(folder)
|
||||
session.commit()
|
||||
print(f"Committed changes to database")
|
||||
|
||||
session.refresh(folder)
|
||||
print(
|
||||
f"After refresh: id={folder.id}, name={folder.name}, parent_id={folder.parent_id}"
|
||||
)
|
||||
|
||||
# Verify the change persisted
|
||||
verification = session.get(Folder, folder_id)
|
||||
print(
|
||||
f"Verification query: id={verification.id}, name={verification.name}, parent_id={verification.parent_id}"
|
||||
)
|
||||
print(f"=== UPDATE COMPLETE ===")
|
||||
|
||||
return folder
|
||||
|
|
|
|||
BIN
backend/notes.db
BIN
backend/notes.db
Binary file not shown.
|
|
@ -36,6 +36,11 @@ export interface FolderCreate {
|
|||
parent_id: number | null;
|
||||
}
|
||||
|
||||
export interface FolderUpdate {
|
||||
name?: string;
|
||||
parent_id?: number | null;
|
||||
}
|
||||
|
||||
const getFolderTree = async () => {
|
||||
const { data } = await axios.get<FolderTreeResponse>(
|
||||
`${API_URL}/folders/tree`,
|
||||
|
|
@ -46,10 +51,24 @@ const getFolderTree = async () => {
|
|||
return decryptedFolderTree;
|
||||
};
|
||||
|
||||
const updateFolder = async (id: number, folder: FolderUpdate) => {
|
||||
console.log(`Updating folder ${id} with:`, folder);
|
||||
try {
|
||||
const response = await axios.patch(`${API_URL}/folders/${id}`, folder);
|
||||
console.log(`Folder ${id} update response:`, response.data);
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error(`Failed to update folder ${id}:`, error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const folderApi = {
|
||||
tree: () => getFolderTree(),
|
||||
list: () => axios.get<Folder[]>(`${API_URL}/folders`),
|
||||
create: (folder: FolderCreate) =>
|
||||
axios.post<Folder>(`${API_URL}/folders`, folder),
|
||||
delete: (id: number) => axios.delete(`${API_URL}/folders/${id}`),
|
||||
update: (id: number, updateData: FolderUpdate) =>
|
||||
updateFolder(id, updateData),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React from "react";
|
||||
import { useDroppable } from "@dnd-kit/core";
|
||||
import { useDroppable, useDraggable } from "@dnd-kit/core";
|
||||
import { Folder, NoteRead } from "../../api/folders";
|
||||
|
||||
export const DroppableFolder = ({
|
||||
|
|
@ -17,12 +17,33 @@ export const DroppableFolder = ({
|
|||
setCollapse: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
collapse: boolean;
|
||||
}) => {
|
||||
const { isOver, setNodeRef } = useDroppable({
|
||||
const { isOver, setNodeRef: setDroppableRef } = useDroppable({
|
||||
id: folder.id!,
|
||||
data: { type: "folder", folder },
|
||||
});
|
||||
|
||||
const {
|
||||
attributes,
|
||||
listeners,
|
||||
setNodeRef: setDraggableRef,
|
||||
transform,
|
||||
isDragging,
|
||||
} = useDraggable({
|
||||
id: `folder-${folder.id}`,
|
||||
data: { type: "folder", folder },
|
||||
});
|
||||
|
||||
const setNodeRef = (node: HTMLElement | null) => {
|
||||
setDroppableRef(node);
|
||||
setDraggableRef(node);
|
||||
};
|
||||
|
||||
const style = {
|
||||
color: isOver ? "green" : undefined,
|
||||
opacity: isDragging ? 0.5 : 1,
|
||||
transform: transform
|
||||
? `translate3d(${transform.x}px, ${transform.y}px, 0)`
|
||||
: undefined,
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
@ -35,10 +56,18 @@ export const DroppableFolder = ({
|
|||
? "bg-ctp-surface1"
|
||||
: "hover:bg-ctp-surface0"
|
||||
}`}
|
||||
{...listeners}
|
||||
{...attributes}
|
||||
>
|
||||
<i className="fadr fa-folder text-sm"></i>
|
||||
{folder.name}
|
||||
<div onClick={() => setCollapse(!collapse)} className="ml-auto">
|
||||
<div
|
||||
onClick={(e) => {
|
||||
e.stopPropagation(); // Prevent dragging when clicking the collapse button
|
||||
setCollapse(!collapse);
|
||||
}}
|
||||
className="ml-auto"
|
||||
>
|
||||
x
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,14 @@ import {
|
|||
import { DraggableNote } from "./DraggableNote";
|
||||
import { DroppableFolder } from "./DroppableFolder";
|
||||
import { useNoteStore } from "../../stores/notesStore";
|
||||
import {
|
||||
DndContext,
|
||||
DragEndEvent,
|
||||
PointerSensor,
|
||||
useSensor,
|
||||
useSensors,
|
||||
} from "@dnd-kit/core";
|
||||
import { notesApi } from "../../api/notes";
|
||||
|
||||
export const Sidebar = ({ clearSelection }: { clearSelection: () => void }) => {
|
||||
// const [folderTree, setFolderTree] = useState<FolderTreeResponse | null>(null);
|
||||
|
|
@ -47,7 +55,59 @@ export const Sidebar = ({ clearSelection }: { clearSelection: () => void }) => {
|
|||
setNewFolder(false);
|
||||
};
|
||||
|
||||
const pointer = useSensor(PointerSensor, {
|
||||
activationConstraint: {
|
||||
distance: 30,
|
||||
},
|
||||
});
|
||||
const sensors = useSensors(pointer);
|
||||
|
||||
const handleDragEnd = async (event: DragEndEvent) => {
|
||||
const { active, over } = event;
|
||||
if (!over) return;
|
||||
|
||||
console.log("Drag ended:", {
|
||||
activeId: active.id,
|
||||
activeType: active.data.current?.type,
|
||||
activeFolder: active.data.current?.folder,
|
||||
overId: over.id,
|
||||
overType: over.data.current?.type,
|
||||
});
|
||||
|
||||
if (active.data.current?.type === "note") {
|
||||
console.log("Updating note", active.id, "to folder", over.id);
|
||||
await notesApi.update(active.id as number, {
|
||||
folder_id: over.id as number,
|
||||
});
|
||||
} else if (active.data.current?.type === "folder") {
|
||||
// Prevent dropping folder into itself
|
||||
if (active.data.current.folder.id === over.id) {
|
||||
console.log("Cannot drop folder into itself");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(
|
||||
"Updating folder",
|
||||
active.data.current.folder.id,
|
||||
"parent to",
|
||||
over.id,
|
||||
);
|
||||
try {
|
||||
const response = await folderApi.update(active.data.current.folder.id, {
|
||||
parent_id: over.id as number,
|
||||
});
|
||||
console.log("Folder update response:", response);
|
||||
} catch (error) {
|
||||
console.error("Failed to update folder:", error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
loadFolderTree();
|
||||
};
|
||||
|
||||
return (
|
||||
<DndContext onDragEnd={handleDragEnd} autoScroll={false} sensors={sensors}>
|
||||
<div
|
||||
className="bg-ctp-mantle border-r border-ctp-surface2 w-[300px] p-4 overflow-y-auto sm:block hidden flex-col gap-3"
|
||||
onDragOver={(e) => e.preventDefault()}
|
||||
|
|
@ -112,6 +172,7 @@ export const Sidebar = ({ clearSelection }: { clearSelection: () => void }) => {
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
</DndContext>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,9 @@ import {
|
|||
import "@mdxeditor/editor/style.css";
|
||||
import { DroppableFolder } from "../components/sidebar/DroppableFolder";
|
||||
import { DraggableNote } from "../components/sidebar/DraggableNote";
|
||||
// @ts-ignore
|
||||
import CheckIcon from "../assets/fontawesome/svg/circle-check.svg?react";
|
||||
// @ts-ignore
|
||||
import SpinnerIcon from "../assets/fontawesome/svg/rotate.svg?react";
|
||||
import { useNoteStore } from "../stores/notesStore";
|
||||
import { create } from "zustand";
|
||||
|
|
@ -83,12 +85,7 @@ function Home() {
|
|||
selectedNote,
|
||||
} = useNoteStore();
|
||||
|
||||
const pointer = useSensor(PointerSensor, {
|
||||
activationConstraint: {
|
||||
distance: 30,
|
||||
},
|
||||
});
|
||||
const sensors = useSensors(pointer);
|
||||
|
||||
|
||||
const newFolderRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
|
|
@ -139,19 +136,10 @@ function Home() {
|
|||
setContent("");
|
||||
};
|
||||
|
||||
const handleDragEnd = async (event: DragEndEvent) => {
|
||||
const { active, over } = event;
|
||||
if (!over) return;
|
||||
|
||||
await notesApi.update(active.id as number, {
|
||||
folder_id: over.id as number,
|
||||
});
|
||||
|
||||
loadFolderTree();
|
||||
};
|
||||
|
||||
return (
|
||||
<DndContext onDragEnd={handleDragEnd} autoScroll={false} sensors={sensors}>
|
||||
|
||||
<div className="flex bg-ctp-base h-screen text-ctp-text overflow-hidden">
|
||||
{/* Sidebar */}
|
||||
|
||||
|
|
@ -188,7 +176,6 @@ function Home() {
|
|||
<>
|
||||
<UndoRedo />
|
||||
<BoldItalicUnderlineToggles />
|
||||
<DiffSourceToggleWrapper />
|
||||
</>
|
||||
),
|
||||
}),
|
||||
|
|
@ -251,19 +238,6 @@ function Home() {
|
|||
Create Note
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Encryption toggle */}
|
||||
{/*<label className="flex items-center gap-2 ml-auto cursor-pointer group">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={encrypted}
|
||||
onChange={() => setEncrypted(!encrypted)}
|
||||
className="w-4 h-4 rounded border-ctp-surface2 text-ctp-mauve focus:ring-ctp-mauve focus:ring-offset-ctp-base cursor-pointer"
|
||||
/>
|
||||
<span className="text-sm text-ctp-subtext0 group-hover:text-ctp-text transition-colors">
|
||||
Encrypt
|
||||
</span>
|
||||
</label>*/}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -286,7 +260,6 @@ function Home() {
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
</DndContext>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue