Skip to content
Closed
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
29 changes: 28 additions & 1 deletion app/api/notes/[noteId]/route.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,36 @@
import { NextRequest, NextResponse } from "next/server";
import { withApiAuth } from "@/app/_utils/api-utils";
import { updateNote, deleteNote } from "@/app/_server/actions/note";
import { getNoteById, updateNote, deleteNote } from "@/app/_server/actions/note";

export const dynamic = "force-dynamic";

export async function GET(request: NextRequest, props: { params: Promise<{ noteId: string }> }) {
const params = await props.params;
return withApiAuth(request, async (user) => {
try {
const note = await getNoteById(params.noteId, undefined, user.username);
if (!note) {
return NextResponse.json({ error: "Note not found" }, { status: 404 });
}

return NextResponse.json({
id: note.uuid || note.id,
title: note.title,
category: note.category || "Uncategorized",
content: note.content,
createdAt: note.createdAt,
updatedAt: note.updatedAt,
});
} catch (error) {
console.error("API Error:", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 }
);
}
});
}

export async function PUT(request: NextRequest, props: { params: Promise<{ noteId: string }> }) {
const params = await props.params;
return withApiAuth(request, async (user) => {
Expand Down
25 changes: 22 additions & 3 deletions howto/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,26 @@ Retrieves all notes/documents for the authenticated user.

**Note**: All notes include a `category` field for organization. If no category is specified when creating a note, it defaults to "Uncategorized".

### 10. Create Note
### 11. Get Note by ID

**GET** `/api/notes/{noteId}`

Retrieves a single note by its UUID.

**Response:**

```json
{
"id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"title": "My Note",
"category": "Personal",
"content": "Note content here...",
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-01T00:00:00.000Z"
}
```

### 12. Create Note

**POST** `/api/notes`

Expand Down Expand Up @@ -545,7 +564,7 @@ Creates a new note for the authenticated user.
}
```

### 11. Update Note
### 13. Update Note

**PUT** `/api/notes/{noteId}`

Expand Down Expand Up @@ -586,7 +605,7 @@ Updates an existing note for the authenticated user.
}
```

### 12. Delete Note
### 14. Delete Note

**DELETE** `/api/notes/{noteId}`

Expand Down
50 changes: 49 additions & 1 deletion tests/api/notes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
mockUser,
mockAuthenticateApiKey,
mockGetUserNotes,
mockGetNoteById,
mockCreateNote,
mockUpdateNote,
mockDeleteNote,
Expand All @@ -12,7 +13,7 @@ import {
} from "./setup"

import { GET, POST } from "@/app/api/notes/route"
import { PUT, DELETE } from "@/app/api/notes/[noteId]/route"
import { GET as GET_NOTE, PUT, DELETE } from "@/app/api/notes/[noteId]/route"

describe("Notes API", () => {
beforeEach(() => {
Expand Down Expand Up @@ -126,6 +127,53 @@ describe("Notes API", () => {
})
})

describe("GET /api/notes/:id", () => {
it("should return a single note", async () => {
const mockNote = {
id: "note-1",
uuid: "uuid-1",
title: "Test Note",
content: "Test content",
category: "Work",
owner: "testuser",
createdAt: "2024-01-01T00:00:00.000Z",
updatedAt: "2024-01-01T00:00:00.000Z",
}
mockGetNoteById.mockResolvedValue(mockNote)

const request = createMockRequest("GET", "http://localhost:3000/api/notes/uuid-1")
const response = await GET_NOTE(request, { params: Promise.resolve({ noteId: "uuid-1" }) })
const data = await getResponseJson(response)

expect(response.status).toBe(200)
expect(data.id).toBe("uuid-1")
expect(data.title).toBe("Test Note")
expect(data.category).toBe("Work")
})

it("should return 404 for non-existent note", async () => {
mockGetNoteById.mockResolvedValue(undefined)

const request = createMockRequest("GET", "http://localhost:3000/api/notes/nonexistent")
const response = await GET_NOTE(request, { params: Promise.resolve({ noteId: "nonexistent" }) })
const data = await getResponseJson(response)

expect(response.status).toBe(404)
expect(data.error).toBe("Note not found")
})

it("should return 401 for unauthorized requests", async () => {
mockAuthenticateApiKey.mockResolvedValue(null)

const request = createMockRequest("GET", "http://localhost:3000/api/notes/uuid-1")
const response = await GET_NOTE(request, { params: Promise.resolve({ noteId: "uuid-1" }) })
const data = await getResponseJson(response)

expect(response.status).toBe(401)
expect(data.error).toBe("Unauthorized")
})
})

describe("PUT /api/notes/:id", () => {
it("should update a note", async () => {
const existingNote = {
Expand Down
3 changes: 3 additions & 0 deletions tests/api/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export const mockUser = {

export const mockAuthenticateApiKey = vi.fn();
export const mockGetUserNotes = vi.fn();
export const mockGetNoteById = vi.fn();
export const mockCreateNote = vi.fn();
export const mockUpdateNote = vi.fn();
export const mockDeleteNote = vi.fn();
Expand Down Expand Up @@ -38,6 +39,7 @@ vi.mock("@/app/_server/actions/api", () => ({

vi.mock("@/app/_server/actions/note", () => ({
getUserNotes: (...args: any[]) => mockGetUserNotes(...args),
getNoteById: (...args: any[]) => mockGetNoteById(...args),
createNote: (...args: any[]) => mockCreateNote(...args),
updateNote: (...args: any[]) => mockUpdateNote(...args),
deleteNote: (...args: any[]) => mockDeleteNote(...args),
Expand Down Expand Up @@ -89,6 +91,7 @@ export function resetApiMocks() {
vi.clearAllMocks();
mockAuthenticateApiKey.mockReset();
mockGetUserNotes.mockReset();
mockGetNoteById.mockReset();
mockCreateNote.mockReset();
mockUpdateNote.mockReset();
mockDeleteNote.mockReset();
Expand Down