From 828df0e105e16e6d2c6fe07e46a6526cbf1643ca Mon Sep 17 00:00:00 2001
From: anshul23102
Date: Fri, 5 Jun 2026 21:32:06 +0530
Subject: [PATCH 1/5] feat: implement soft delete with 30-day recovery period
for notes
Users can now recover accidentally deleted notes within 30 days before
permanent deletion. Implements soft delete pattern with automatic TTL cleanup.
Changes:
1. Schema:
- Added isDeleted boolean field (indexed for efficient queries)
- Added deletedAt timestamp field
- Added TTL index: deletes records permanently after 30 days
2. Delete Endpoint (PATCH /api/notes/:id):
- Changed from hard delete to soft delete
- Sets isDeleted=true, deletedAt=now()
- Returns recovery message with 30-day window
3. Get Endpoints:
- Exclude soft-deleted notes from active notes listing
- Only show undeleted notes to users
4. New Trash API (GET /api/notes/trash):
- GET: List all deleted notes within recovery window
- PUT /api/notes/trash/restore/:id: Restore a deleted note
- DELETE /api/notes/trash/:id: Permanently delete (skip recovery)
Benefits:
- Prevents accidental data loss
- 30-day recovery period before permanent deletion
- Automatic cleanup after recovery window expires
- Clear user communication about recovery
Database behavior:
- Soft-deleted notes: recovered within 30 days
- Expired trash: automatically removed by MongoDB TTL index
- Performance: indexed isDeleted field for fast filtering
- Storage: small overhead (2 extra fields per note)
Closes #270
---
savebook/app/api/notes/[id]/route.js | 20 ++-
savebook/app/api/notes/route.js | 1 +
savebook/app/api/notes/trash/route.js | 196 ++++++++++++++++++++++++++
savebook/lib/models/Notes.js | 22 +++
4 files changed, 235 insertions(+), 4 deletions(-)
create mode 100644 savebook/app/api/notes/trash/route.js
diff --git a/savebook/app/api/notes/[id]/route.js b/savebook/app/api/notes/[id]/route.js
index 4748eb3..595308a 100644
--- a/savebook/app/api/notes/[id]/route.js
+++ b/savebook/app/api/notes/[id]/route.js
@@ -37,7 +37,11 @@ export async function GET(request, { params }) {
);
}
- const note = await Notes.findOne({ _id: id, user: decoded.userId });
+ const note = await Notes.findOne({
+ _id: id,
+ user: decoded.userId,
+ isDeleted: false, // Exclude soft-deleted notes
+ });
if (!note) {
return NextResponse.json(
@@ -108,12 +112,20 @@ export async function DELETE(request, { params }) {
);
}
- // Delete the note
- await Notes.findByIdAndDelete(id);
+ // Soft delete: mark as deleted instead of removing permanently
+ // User has 30 days to recover from trash before permanent deletion via TTL
+ await Notes.findByIdAndUpdate(
+ id,
+ {
+ isDeleted: true,
+ deletedAt: new Date(),
+ },
+ { new: true }
+ );
return NextResponse.json({
success: true,
- message: "Note deleted successfully"
+ message: "Note deleted. Recover from trash within 30 days."
});
} catch (error) {
console.error(error);
diff --git a/savebook/app/api/notes/route.js b/savebook/app/api/notes/route.js
index aa29807..3e19f47 100644
--- a/savebook/app/api/notes/route.js
+++ b/savebook/app/api/notes/route.js
@@ -35,6 +35,7 @@ export async function GET(request) {
const notes = await Notes.find({
user: new mongoose.Types.ObjectId(decoded.userId),
+ isDeleted: false, // Exclude soft-deleted notes from active notes
}).lean();
// 👇 Attach isBookmarked to each note
diff --git a/savebook/app/api/notes/trash/route.js b/savebook/app/api/notes/trash/route.js
new file mode 100644
index 0000000..6d2a528
--- /dev/null
+++ b/savebook/app/api/notes/trash/route.js
@@ -0,0 +1,196 @@
+import { NextResponse } from "next/server";
+import dbConnect from "@/lib/db/mongodb";
+import Notes from "@/lib/models/Notes";
+import mongoose from "mongoose";
+import { verifyJwtToken } from "@/lib/utils/jwtAuth";
+
+/**
+ * GET /api/notes/trash
+ * Retrieve all soft-deleted notes for the current user
+ * Notes are recoverable for 30 days after deletion
+ */
+export async function GET(request) {
+ await dbConnect();
+
+ try {
+ const token = request.cookies.get("authToken");
+
+ if (!token) {
+ return NextResponse.json(
+ { error: "Unauthorized: No token provided" },
+ { status: 401 }
+ );
+ }
+
+ const decoded = await verifyJwtToken(token.value);
+
+ if (!decoded || !decoded.success) {
+ return NextResponse.json(
+ { error: "Unauthorized: Invalid token" },
+ { status: 401 }
+ );
+ }
+
+ // Get deleted notes within 30-day recovery window
+ const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
+
+ const trash = await Notes.find({
+ user: new mongoose.Types.ObjectId(decoded.userId),
+ isDeleted: true,
+ deletedAt: { $gte: thirtyDaysAgo }, // Only show notes deleted within last 30 days
+ })
+ .sort({ deletedAt: -1 })
+ .lean();
+
+ return NextResponse.json({
+ success: true,
+ data: trash,
+ message: `${trash.length} deleted notes available for recovery (expires in 30 days)`,
+ });
+ } catch (error) {
+ console.error(error);
+ return NextResponse.json(
+ { error: "Server error" },
+ { status: 500 }
+ );
+ }
+}
+
+/**
+ * PUT /api/notes/trash/restore/:id
+ * Restore a soft-deleted note from trash
+ * This undeletes the note and makes it visible again
+ */
+export async function PUT(request) {
+ await dbConnect();
+
+ try {
+ const token = request.cookies.get("authToken");
+
+ if (!token) {
+ return NextResponse.json(
+ { error: "Unauthorized: No token provided" },
+ { status: 401 }
+ );
+ }
+
+ const decoded = await verifyJwtToken(token.value);
+
+ if (!decoded || !decoded.success) {
+ return NextResponse.json(
+ { error: "Unauthorized: Invalid token" },
+ { status: 401 }
+ );
+ }
+
+ // Get note ID from URL
+ const url = new URL(request.url);
+ const noteId = url.pathname.split("/").pop();
+
+ if (!mongoose.Types.ObjectId.isValid(noteId)) {
+ return NextResponse.json(
+ { error: "Invalid note ID" },
+ { status: 400 }
+ );
+ }
+
+ // Find soft-deleted note
+ const note = await Notes.findOne({
+ _id: noteId,
+ user: decoded.userId,
+ isDeleted: true,
+ });
+
+ if (!note) {
+ return NextResponse.json(
+ { error: "Note not found in trash or recovery period expired" },
+ { status: 404 }
+ );
+ }
+
+ // Restore note
+ note.isDeleted = false;
+ note.deletedAt = null;
+ await note.save();
+
+ return NextResponse.json({
+ success: true,
+ data: note,
+ message: "Note restored successfully",
+ });
+ } catch (error) {
+ console.error(error);
+ return NextResponse.json(
+ { error: "Server error" },
+ { status: 500 }
+ );
+ }
+}
+
+/**
+ * DELETE /api/notes/trash/:id
+ * Permanently delete a soft-deleted note (skip recovery period)
+ * Use with caution: This is permanent and cannot be undone
+ */
+export async function DELETE(request) {
+ await dbConnect();
+
+ try {
+ const token = request.cookies.get("authToken");
+
+ if (!token) {
+ return NextResponse.json(
+ { error: "Unauthorized: No token provided" },
+ { status: 401 }
+ );
+ }
+
+ const decoded = await verifyJwtToken(token.value);
+
+ if (!decoded || !decoded.success) {
+ return NextResponse.json(
+ { error: "Unauthorized: Invalid token" },
+ { status: 401 }
+ );
+ }
+
+ // Get note ID from URL
+ const url = new URL(request.url);
+ const noteId = url.pathname.split("/").pop();
+
+ if (!mongoose.Types.ObjectId.isValid(noteId)) {
+ return NextResponse.json(
+ { error: "Invalid note ID" },
+ { status: 400 }
+ );
+ }
+
+ // Find and permanently delete note
+ const note = await Notes.findOne({
+ _id: noteId,
+ user: decoded.userId,
+ isDeleted: true,
+ });
+
+ if (!note) {
+ return NextResponse.json(
+ { error: "Note not found in trash" },
+ { status: 404 }
+ );
+ }
+
+ // Permanently delete the note
+ await Notes.findByIdAndDelete(noteId);
+
+ return NextResponse.json({
+ success: true,
+ message: "Note permanently deleted",
+ });
+ } catch (error) {
+ console.error(error);
+ return NextResponse.json(
+ { error: "Server error" },
+ { status: 500 }
+ );
+ }
+}
diff --git a/savebook/lib/models/Notes.js b/savebook/lib/models/Notes.js
index 08223fd..5c99ae9 100644
--- a/savebook/lib/models/Notes.js
+++ b/savebook/lib/models/Notes.js
@@ -60,7 +60,29 @@ const NotesSchema = new Schema({
type: String,
default: null,
},
+
+ // Soft delete implementation for recovery window
+ isDeleted: {
+ type: Boolean,
+ default: false,
+ index: true, // Index for efficient queries excluding soft-deleted notes
+ },
+
+ deletedAt: {
+ type: Date,
+ default: null,
+ },
});
+// Add TTL index: automatically delete soft-deleted notes after 30 days
+// This removes records permanently after 30-day recovery period
+NotesSchema.index(
+ { deletedAt: 1 },
+ {
+ expireAfterSeconds: 2592000, // 30 days in seconds
+ partialFilterExpression: { isDeleted: true }
+ }
+);
+
export default mongoose.models.Notes ||
mongoose.model('Notes', NotesSchema);
From 5e9513fed9a2b66279f29104a089405e96ea8064 Mon Sep 17 00:00:00 2001
From: anshul23102
Date: Mon, 8 Jun 2026 17:14:43 +0530
Subject: [PATCH 2/5] feat: Add trash UI component with recovery functionality
Implements trash/recovery feature to address maintainer feedback:
- Created Trash.js component for displaying deleted notes
- Added trash tab to Notes component sidebar navigation
- Implemented recovery and permanent deletion UI
- Delete notes now soft-deleted instead of hard-deleted
- Users can recover notes within 30-day window
- Display deletion timestamp and recovery options
- Search and filter functionality for trash items
Allows users to:
- View all deleted notes in dedicated trash interface
- Recover accidentally deleted notes
- Permanently delete notes from trash
- Search trash items by title/description
- Filter by category tags
- See recovery window countdown
---
savebook/components/notes/Notes.js | 15 ++
savebook/components/notes/Trash.js | 251 +++++++++++++++++++++++++++++
2 files changed, 266 insertions(+)
create mode 100644 savebook/components/notes/Trash.js
diff --git a/savebook/components/notes/Notes.js b/savebook/components/notes/Notes.js
index 5593efe..1b3bf11 100644
--- a/savebook/components/notes/Notes.js
+++ b/savebook/components/notes/Notes.js
@@ -8,6 +8,7 @@ import Addnote from './AddNote';
import NoteItem from './NoteItem';
import { useAuth } from '@/context/auth/authContext';
import RichTextEditor from './RichTextEditor';
+import Trash from './Trash';
// Separate navigation handler component to use router with Suspense
const NavigationHandler = ({ isAuthenticated, loading }) => {
@@ -560,6 +561,9 @@ export default function Notes() {
+ {activeTab === 'trash' ? (
+
+ ) : (
{/* Search and Filter Section */}
@@ -705,6 +709,16 @@ export default function Notes() {
Whiteboards
{totalWhiteboards}
+
@@ -721,6 +735,7 @@ export default function Notes() {
+ )}
>
diff --git a/savebook/components/notes/Trash.js b/savebook/components/notes/Trash.js
new file mode 100644
index 0000000..b3d2d89
--- /dev/null
+++ b/savebook/components/notes/Trash.js
@@ -0,0 +1,251 @@
+"use client"
+import React, { useEffect, useState } from 'react'
+import toast from 'react-hot-toast';
+import NoteItem from './NoteItem';
+import { useAuth } from '@/context/auth/authContext';
+
+export default function Trash() {
+ const { isAuthenticated, loading } = useAuth();
+ const [trashedNotes, setTrashedNotes] = useState([]);
+ const [isLoading, setIsLoading] = useState(false);
+ const [searchTerm, setSearchTerm] = useState('');
+ const [selectedTag, setSelectedTag] = useState('all');
+
+ // Fetch trashed notes
+ const getTrashedNotes = async () => {
+ try {
+ setIsLoading(true);
+ const response = await fetch('/api/notes/trash');
+
+ if (!response.ok) {
+ throw new Error('Failed to fetch trash');
+ }
+
+ const data = await response.json();
+ setTrashedNotes(data.data || []);
+
+ if (data.data && data.data.length > 0) {
+ toast.success(`Loaded ${data.data.length} deleted notes`);
+ }
+ } catch (error) {
+ console.error('Error fetching trash:', error);
+ toast.error('Failed to load trash');
+ setTrashedNotes([]);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ // Load trash on mount
+ useEffect(() => {
+ if (isAuthenticated && !loading) {
+ getTrashedNotes();
+ }
+ }, [isAuthenticated, loading]);
+
+ // Restore note from trash
+ const restoreNote = async (noteId) => {
+ try {
+ const response = await fetch(`/api/notes/trash/restore/${noteId}`, {
+ method: 'PUT'
+ });
+
+ if (!response.ok) {
+ throw new Error('Failed to restore note');
+ }
+
+ const data = await response.json();
+
+ // Remove from trash display
+ setTrashedNotes(trashedNotes.filter(note => note._id !== noteId));
+ toast.success('Note restored successfully');
+ } catch (error) {
+ console.error('Error restoring note:', error);
+ toast.error('Failed to restore note');
+ }
+ };
+
+ // Permanently delete note from trash
+ const permanentlyDeleteNote = async (noteId) => {
+ try {
+ // Confirm deletion
+ const userConfirmed = window.confirm(
+ 'This will permanently delete the note. This action cannot be undone. Are you sure?'
+ );
+
+ if (!userConfirmed) {
+ return;
+ }
+
+ const response = await fetch(`/api/notes/trash/${noteId}`, {
+ method: 'DELETE'
+ });
+
+ if (!response.ok) {
+ throw new Error('Failed to permanently delete note');
+ }
+
+ // Remove from trash display
+ setTrashedNotes(trashedNotes.filter(note => note._id !== noteId));
+ toast.success('Note permanently deleted');
+ } catch (error) {
+ console.error('Error permanently deleting note:', error);
+ toast.error('Failed to permanently delete note');
+ }
+ };
+
+ const tagOptions = [
+ { id: 1, value: "General", color: "bg-blue-500" },
+ { id: 2, value: "Basic", color: "bg-gray-500" },
+ { id: 3, value: "Finance", color: "bg-green-500" },
+ { id: 4, value: "Grocery", color: "bg-orange-500" },
+ { id: 5, value: "Office", color: "bg-purple-500" },
+ { id: 6, value: "Personal", color: "bg-pink-500" },
+ { id: 7, value: "Work", color: "bg-indigo-500" },
+ { id: 8, value: "Ideas", color: "bg-teal-500" }
+ ];
+
+ // Filter trashed notes by search and tag
+ const filteredNotes = trashedNotes.filter(note => {
+ const matchesSearch = note.title?.toLowerCase().includes(searchTerm.toLowerCase()) ||
+ note.description?.toLowerCase().includes(searchTerm.toLowerCase());
+ const matchesTag = selectedTag === 'all' || note.tag === selectedTag;
+ return matchesSearch && matchesTag;
+ });
+
+ if (!isAuthenticated && !loading) {
+ return null;
+ }
+
+ return (
+
+
+ {/* Header */}
+
+
+ 🗑️ Trash
+
+
+ Recover deleted notes within 30 days. After 30 days, notes are permanently deleted.
+
+
+
+ {/* Search and Filter */}
+
+ setSearchTerm(e.target.value)}
+ className="flex-1 px-4 py-3 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-white placeholder-gray-500 dark:placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500"
+ />
+
+
+
+
+ {/* Stats */}
+
+
+
Total in Trash
+
+ {trashedNotes.length}
+
+
+
+
Matching Search
+
+ {filteredNotes.length}
+
+
+
+
Recovery Window
+
30 days
+
+
+
+ {/* Trash Items */}
+
+ {isLoading ? (
+
+ ) : filteredNotes.length === 0 ? (
+
+
+ {trashedNotes.length === 0 ? "No deleted notes" : "No notes match your search"}
+
+
+ {trashedNotes.length === 0 ? "Your trash is empty. Deleted notes will appear here." : "Try adjusting your search filters."}
+
+
+ ) : (
+ filteredNotes.map(note => (
+
+
+ {/* Note Header */}
+
+
+
+ {note.title}
+
+
+ {note.description}
+
+
+ {note.tag && (
+
t.value === note.tag)?.color || 'bg-gray-500'
+ }`}>
+ {note.tag}
+
+ )}
+
+
+ {/* Deletion Info */}
+
+
+ Deleted {new Date(note.deletedAt).toLocaleDateString()} at {new Date(note.deletedAt).toLocaleTimeString()}
+
+
+
+ {/* Action Buttons */}
+
+
+
+
+
+
+ ))
+ )}
+
+
+
+ );
+}
From ec6521e77ea1271b68eee1fbb4086dbc844def16 Mon Sep 17 00:00:00 2001
From: Anshul Jain
Date: Sun, 14 Jun 2026 23:18:12 +0530
Subject: [PATCH 3/5] fix: add navigation buttons to return from trash tab to
notes and whiteboard tabs
- Add back to Notes button that navigates to notes tab
- Add back to Whiteboard button that navigates to whiteboard tab
- Styled with consistent dark mode support
- Improves UX by providing clear navigation path from trash view
---
savebook/components/notes/Trash.js | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/savebook/components/notes/Trash.js b/savebook/components/notes/Trash.js
index b3d2d89..1f70e4b 100644
--- a/savebook/components/notes/Trash.js
+++ b/savebook/components/notes/Trash.js
@@ -130,6 +130,22 @@ export default function Trash() {
+ {/* Navigation Tabs */}
+
+
+
+
+
{/* Search and Filter */}
Date: Mon, 15 Jun 2026 16:23:06 +0530
Subject: [PATCH 4/5] Fix trash navigation, API routing, and note decryption
- Changed navigation button hrefs from /#notes and /#whiteboard to /notes and /notes/whiteboard
- Created dynamic API routes for trash operations per Next.js App Router pattern
- Moved route handlers from trash/route.js to proper dynamic route files
- Export decryptNote from NoteState context for use in Trash component
- Decrypt fetched notes in Trash component so users can identify content before deletion
Fixes:
- Navigation buttons now link to correct routes
- Restore and delete operations work with proper routing
- Trash notes display decrypted content instead of encrypted strings
Co-Authored-By: Claude Haiku 4.5
---
savebook/app/api/notes/trash/[id]/route.js | 63 ++++++++
.../app/api/notes/trash/restore/[id]/route.js | 66 +++++++++
savebook/app/api/notes/trash/route.js | 138 ------------------
savebook/components/notes/Trash.js | 24 ++-
savebook/context/NoteState.js | 2 +-
5 files changed, 147 insertions(+), 146 deletions(-)
create mode 100644 savebook/app/api/notes/trash/[id]/route.js
create mode 100644 savebook/app/api/notes/trash/restore/[id]/route.js
diff --git a/savebook/app/api/notes/trash/[id]/route.js b/savebook/app/api/notes/trash/[id]/route.js
new file mode 100644
index 0000000..e4c1638
--- /dev/null
+++ b/savebook/app/api/notes/trash/[id]/route.js
@@ -0,0 +1,63 @@
+import { NextResponse } from "next/server";
+import dbConnect from "@/lib/db/mongodb";
+import Notes from "@/lib/models/Notes";
+import mongoose from "mongoose";
+import { verifyJwtToken } from "@/lib/utils/jwtAuth";
+
+export async function DELETE(request, { params }) {
+ await dbConnect();
+
+ try {
+ const { id: noteId } = await params;
+ const token = request.cookies.get("authToken");
+
+ if (!token) {
+ return NextResponse.json(
+ { error: "Unauthorized: No token provided" },
+ { status: 401 }
+ );
+ }
+
+ const decoded = await verifyJwtToken(token.value);
+
+ if (!decoded || !decoded.success) {
+ return NextResponse.json(
+ { error: "Unauthorized: Invalid token" },
+ { status: 401 }
+ );
+ }
+
+ if (!mongoose.Types.ObjectId.isValid(noteId)) {
+ return NextResponse.json(
+ { error: "Invalid note ID" },
+ { status: 400 }
+ );
+ }
+
+ const note = await Notes.findOne({
+ _id: noteId,
+ user: decoded.userId,
+ isDeleted: true,
+ });
+
+ if (!note) {
+ return NextResponse.json(
+ { error: "Note not found in trash" },
+ { status: 404 }
+ );
+ }
+
+ await Notes.findByIdAndDelete(noteId);
+
+ return NextResponse.json({
+ success: true,
+ message: "Note permanently deleted",
+ });
+ } catch (error) {
+ console.error(error);
+ return NextResponse.json(
+ { error: "Server error" },
+ { status: 500 }
+ );
+ }
+}
diff --git a/savebook/app/api/notes/trash/restore/[id]/route.js b/savebook/app/api/notes/trash/restore/[id]/route.js
new file mode 100644
index 0000000..22635d8
--- /dev/null
+++ b/savebook/app/api/notes/trash/restore/[id]/route.js
@@ -0,0 +1,66 @@
+import { NextResponse } from "next/server";
+import dbConnect from "@/lib/db/mongodb";
+import Notes from "@/lib/models/Notes";
+import mongoose from "mongoose";
+import { verifyJwtToken } from "@/lib/utils/jwtAuth";
+
+export async function PUT(request, { params }) {
+ await dbConnect();
+
+ try {
+ const { id: noteId } = await params;
+ const token = request.cookies.get("authToken");
+
+ if (!token) {
+ return NextResponse.json(
+ { error: "Unauthorized: No token provided" },
+ { status: 401 }
+ );
+ }
+
+ const decoded = await verifyJwtToken(token.value);
+
+ if (!decoded || !decoded.success) {
+ return NextResponse.json(
+ { error: "Unauthorized: Invalid token" },
+ { status: 401 }
+ );
+ }
+
+ if (!mongoose.Types.ObjectId.isValid(noteId)) {
+ return NextResponse.json(
+ { error: "Invalid note ID" },
+ { status: 400 }
+ );
+ }
+
+ const note = await Notes.findOne({
+ _id: noteId,
+ user: decoded.userId,
+ isDeleted: true,
+ });
+
+ if (!note) {
+ return NextResponse.json(
+ { error: "Note not found in trash or recovery period expired" },
+ { status: 404 }
+ );
+ }
+
+ note.isDeleted = false;
+ note.deletedAt = null;
+ await note.save();
+
+ return NextResponse.json({
+ success: true,
+ data: note,
+ message: "Note restored successfully",
+ });
+ } catch (error) {
+ console.error(error);
+ return NextResponse.json(
+ { error: "Server error" },
+ { status: 500 }
+ );
+ }
+}
diff --git a/savebook/app/api/notes/trash/route.js b/savebook/app/api/notes/trash/route.js
index 6d2a528..d5f98d6 100644
--- a/savebook/app/api/notes/trash/route.js
+++ b/savebook/app/api/notes/trash/route.js
@@ -56,141 +56,3 @@ export async function GET(request) {
}
}
-/**
- * PUT /api/notes/trash/restore/:id
- * Restore a soft-deleted note from trash
- * This undeletes the note and makes it visible again
- */
-export async function PUT(request) {
- await dbConnect();
-
- try {
- const token = request.cookies.get("authToken");
-
- if (!token) {
- return NextResponse.json(
- { error: "Unauthorized: No token provided" },
- { status: 401 }
- );
- }
-
- const decoded = await verifyJwtToken(token.value);
-
- if (!decoded || !decoded.success) {
- return NextResponse.json(
- { error: "Unauthorized: Invalid token" },
- { status: 401 }
- );
- }
-
- // Get note ID from URL
- const url = new URL(request.url);
- const noteId = url.pathname.split("/").pop();
-
- if (!mongoose.Types.ObjectId.isValid(noteId)) {
- return NextResponse.json(
- { error: "Invalid note ID" },
- { status: 400 }
- );
- }
-
- // Find soft-deleted note
- const note = await Notes.findOne({
- _id: noteId,
- user: decoded.userId,
- isDeleted: true,
- });
-
- if (!note) {
- return NextResponse.json(
- { error: "Note not found in trash or recovery period expired" },
- { status: 404 }
- );
- }
-
- // Restore note
- note.isDeleted = false;
- note.deletedAt = null;
- await note.save();
-
- return NextResponse.json({
- success: true,
- data: note,
- message: "Note restored successfully",
- });
- } catch (error) {
- console.error(error);
- return NextResponse.json(
- { error: "Server error" },
- { status: 500 }
- );
- }
-}
-
-/**
- * DELETE /api/notes/trash/:id
- * Permanently delete a soft-deleted note (skip recovery period)
- * Use with caution: This is permanent and cannot be undone
- */
-export async function DELETE(request) {
- await dbConnect();
-
- try {
- const token = request.cookies.get("authToken");
-
- if (!token) {
- return NextResponse.json(
- { error: "Unauthorized: No token provided" },
- { status: 401 }
- );
- }
-
- const decoded = await verifyJwtToken(token.value);
-
- if (!decoded || !decoded.success) {
- return NextResponse.json(
- { error: "Unauthorized: Invalid token" },
- { status: 401 }
- );
- }
-
- // Get note ID from URL
- const url = new URL(request.url);
- const noteId = url.pathname.split("/").pop();
-
- if (!mongoose.Types.ObjectId.isValid(noteId)) {
- return NextResponse.json(
- { error: "Invalid note ID" },
- { status: 400 }
- );
- }
-
- // Find and permanently delete note
- const note = await Notes.findOne({
- _id: noteId,
- user: decoded.userId,
- isDeleted: true,
- });
-
- if (!note) {
- return NextResponse.json(
- { error: "Note not found in trash" },
- { status: 404 }
- );
- }
-
- // Permanently delete the note
- await Notes.findByIdAndDelete(noteId);
-
- return NextResponse.json({
- success: true,
- message: "Note permanently deleted",
- });
- } catch (error) {
- console.error(error);
- return NextResponse.json(
- { error: "Server error" },
- { status: 500 }
- );
- }
-}
diff --git a/savebook/components/notes/Trash.js b/savebook/components/notes/Trash.js
index 1f70e4b..81d3445 100644
--- a/savebook/components/notes/Trash.js
+++ b/savebook/components/notes/Trash.js
@@ -1,11 +1,14 @@
"use client"
-import React, { useEffect, useState } from 'react'
+import React, { useEffect, useState, useContext } from 'react'
import toast from 'react-hot-toast';
import NoteItem from './NoteItem';
import { useAuth } from '@/context/auth/authContext';
+import noteContext from '@/context/noteContext';
export default function Trash() {
const { isAuthenticated, loading } = useAuth();
+ const context = useContext(noteContext);
+ const { decryptNote } = context || {};
const [trashedNotes, setTrashedNotes] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [searchTerm, setSearchTerm] = useState('');
@@ -22,10 +25,17 @@ export default function Trash() {
}
const data = await response.json();
- setTrashedNotes(data.data || []);
+ const notesData = data.data || [];
- if (data.data && data.data.length > 0) {
- toast.success(`Loaded ${data.data.length} deleted notes`);
+ // Decrypt notes if decryptNote function is available
+ const decryptedNotes = decryptNote
+ ? await Promise.all(notesData.map(note => decryptNote(note)))
+ : notesData;
+
+ setTrashedNotes(decryptedNotes);
+
+ if (decryptedNotes.length > 0) {
+ toast.success(`Loaded ${decryptedNotes.length} deleted notes`);
}
} catch (error) {
console.error('Error fetching trash:', error);
@@ -41,7 +51,7 @@ export default function Trash() {
if (isAuthenticated && !loading) {
getTrashedNotes();
}
- }, [isAuthenticated, loading]);
+ }, [isAuthenticated, loading, decryptNote]);
// Restore note from trash
const restoreNote = async (noteId) => {
@@ -133,13 +143,13 @@ export default function Trash() {
{/* Navigation Tabs */}