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
20 changes: 16 additions & 4 deletions savebook/app/api/notes/[id]/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions savebook/app/api/notes/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
63 changes: 63 additions & 0 deletions savebook/app/api/notes/trash/[id]/route.js
Original file line number Diff line number Diff line change
@@ -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 }
);
}
}
66 changes: 66 additions & 0 deletions savebook/app/api/notes/trash/restore/[id]/route.js
Original file line number Diff line number Diff line change
@@ -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 }
);
}
}
58 changes: 58 additions & 0 deletions savebook/app/api/notes/trash/route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
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 }
);
}
}

15 changes: 15 additions & 0 deletions savebook/components/notes/Notes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) => {
Expand Down Expand Up @@ -560,6 +561,9 @@ export default function Notes() {
</p>
</div>

{activeTab === 'trash' ? (
<Trash />
) : (
<div className="lg:flex lg:items-start lg:gap-8">
<div className="flex-1">
{/* Search and Filter Section */}
Expand Down Expand Up @@ -705,6 +709,16 @@ export default function Notes() {
<span>Whiteboards</span>
<span className="text-xs">{totalWhiteboards}</span>
</button>
<button
onClick={() => setActiveTab('trash')}
className={`flex items-center justify-between px-4 py-3 rounded-xl text-sm font-medium transition-all ${activeTab === 'trash'
? 'bg-red-600 text-white'
: 'bg-gray-800 text-gray-300 hover:bg-gray-700'
}`}
>
<span>🗑️ Trash</span>
<span className="text-xs">Recover</span>
</button>
</div>

<div className="border-t border-gray-700 pt-4 space-y-3">
Expand All @@ -721,6 +735,7 @@ export default function Notes() {
</div>
</aside>
</div>
)}
</div>
</div>
</>
Expand Down
Loading