Skip to content

Commit 0e3a24a

Browse files
Copilotfboucher
andcommitted
Add edit and delete functionality for notes
Co-authored-by: fboucher <2404846+fboucher@users.noreply.github.com>
1 parent 7af043a commit 0e3a24a

File tree

6 files changed

+304
-1
lines changed

6 files changed

+304
-1
lines changed

src/NoteBookmark.Api.Tests/Endpoints/NoteEndpointsTests.cs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,111 @@ public async Task UpdatePostReadStatus_UpdatesAllPostsWithNotes()
158158
// This would require additional verification logic based on the actual implementation
159159
}
160160

161+
[Fact]
162+
public async Task GetNote_WithValidNoteId_ReturnsNote()
163+
{
164+
// Arrange
165+
var testPost = await CreateAndSaveTestPost();
166+
var testNote = CreateTestNote();
167+
testNote.PostId = testPost.RowKey;
168+
await _client.PostAsJsonAsync("/api/notes/note", testNote);
169+
170+
// Act
171+
var response = await _client.GetAsync($"/api/notes/note/{testNote.RowKey}");
172+
173+
// Assert
174+
response.StatusCode.Should().Be(HttpStatusCode.OK);
175+
176+
var retrievedNote = await response.Content.ReadFromJsonAsync<Note>();
177+
retrievedNote.Should().NotBeNull();
178+
retrievedNote!.RowKey.Should().Be(testNote.RowKey);
179+
retrievedNote.Comment.Should().Be(testNote.Comment);
180+
}
181+
182+
[Fact]
183+
public async Task GetNote_WithInvalidNoteId_ReturnsNotFound()
184+
{
185+
// Arrange
186+
var nonExistentNoteId = "non-existent-note-id";
187+
188+
// Act
189+
var response = await _client.GetAsync($"/api/notes/note/{nonExistentNoteId}");
190+
191+
// Assert
192+
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
193+
}
194+
195+
[Fact]
196+
public async Task UpdateNote_WithValidNote_ReturnsOk()
197+
{
198+
// Arrange
199+
var testPost = await CreateAndSaveTestPost();
200+
var testNote = CreateTestNote();
201+
testNote.PostId = testPost.RowKey;
202+
await _client.PostAsJsonAsync("/api/notes/note", testNote);
203+
204+
// Update the note
205+
testNote.Comment = "Updated comment";
206+
testNote.Tags = "updated, tags";
207+
208+
// Act
209+
var response = await _client.PutAsJsonAsync("/api/notes/note", testNote);
210+
211+
// Assert
212+
response.StatusCode.Should().Be(HttpStatusCode.OK);
213+
214+
var updatedNote = await response.Content.ReadFromJsonAsync<Note>();
215+
updatedNote.Should().NotBeNull();
216+
updatedNote!.Comment.Should().Be("Updated comment");
217+
updatedNote.Tags.Should().Be("updated, tags");
218+
}
219+
220+
[Fact]
221+
public async Task UpdateNote_WithInvalidNote_ReturnsBadRequest()
222+
{
223+
// Arrange
224+
var invalidNote = new Note(); // Missing required comment
225+
226+
// Act
227+
var response = await _client.PutAsJsonAsync("/api/notes/note", invalidNote);
228+
229+
// Assert
230+
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
231+
}
232+
233+
[Fact]
234+
public async Task DeleteNote_WithValidNoteId_ReturnsOk()
235+
{
236+
// Arrange
237+
var testPost = await CreateAndSaveTestPost();
238+
var testNote = CreateTestNote();
239+
testNote.PostId = testPost.RowKey;
240+
await _client.PostAsJsonAsync("/api/notes/note", testNote);
241+
242+
// Act
243+
var response = await _client.DeleteAsync($"/api/notes/note/{testNote.RowKey}");
244+
245+
// Assert
246+
response.StatusCode.Should().Be(HttpStatusCode.OK);
247+
248+
// Verify the note is deleted
249+
var getResponse = await _client.GetAsync($"/api/notes/note/{testNote.RowKey}");
250+
getResponse.StatusCode.Should().Be(HttpStatusCode.NotFound);
251+
}
252+
253+
[Fact]
254+
public async Task DeleteNote_WithInvalidNoteId_ReturnsNotFound()
255+
{
256+
// Arrange
257+
var nonExistentNoteId = "non-existent-note-id";
258+
259+
// Act
260+
var response = await _client.DeleteAsync($"/api/notes/note/{nonExistentNoteId}");
261+
262+
// Assert
263+
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
264+
}
265+
161266
// Helper methods
162267
private async Task SeedTestNotes()
163268
{

src/NoteBookmark.Api/DataStorageService.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,26 @@ public void CreateNote(Note note)
179179
}
180180
}
181181

182+
public Note? GetNote(string rowKey)
183+
{
184+
var tblNote = GetNoteTable();
185+
var result = tblNote.Query<Note>(filter: $"RowKey eq '{rowKey}'");
186+
Note? note = result.FirstOrDefault<Note>();
187+
return note;
188+
}
189+
190+
public bool DeleteNote(string rowKey)
191+
{
192+
var tblNote = GetNoteTable();
193+
var existingNote = tblNote.Query<Note>(filter: $"RowKey eq '{rowKey}'").FirstOrDefault();
194+
if (existingNote != null)
195+
{
196+
tblNote.DeleteEntity(existingNote.PartitionKey, existingNote.RowKey);
197+
return true;
198+
}
199+
return false;
200+
}
201+
182202

183203
public async Task<Settings> GetSettings()
184204
{

src/NoteBookmark.Api/NoteEnpoints.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,15 @@ public static void MapNoteEndpoints(this IEndpointRouteBuilder app)
2727

2828
endpoints.MapGet("/UpdatePostReadStatus", UpdatePostReadStatus)
2929
.WithDescription("Update the read status of all posts to true if they have a note referencing them.");
30+
31+
endpoints.MapGet("/note/{rowKey}", GetNote)
32+
.WithDescription("Get a specific note by its row key.");
33+
34+
endpoints.MapPut("/note", UpdateNote)
35+
.WithDescription("Update an existing note");
36+
37+
endpoints.MapDelete("/note/{rowKey}", DeleteNote)
38+
.WithDescription("Delete a note");
3039
}
3140

3241
static Results<Created<Note>, BadRequest> CreateNote(Note note,
@@ -115,4 +124,44 @@ private static async Task<Results<Ok, BadRequest>> UpdatePostReadStatus(TableSer
115124
return TypedResults.BadRequest();
116125
}
117126
}
127+
128+
static Results<Ok<Note>, NotFound> GetNote(string rowKey,
129+
TableServiceClient tblClient,
130+
BlobServiceClient blobClient)
131+
{
132+
var dataStorageService = new DataStorageService(tblClient, blobClient);
133+
var note = dataStorageService.GetNote(rowKey);
134+
return note == null ? TypedResults.NotFound() : TypedResults.Ok(note);
135+
}
136+
137+
static Results<Ok<Note>, BadRequest> UpdateNote(Note note,
138+
TableServiceClient tblClient,
139+
BlobServiceClient blobClient)
140+
{
141+
try
142+
{
143+
if (!note.Validate())
144+
{
145+
return TypedResults.BadRequest();
146+
}
147+
148+
var dataStorageService = new DataStorageService(tblClient, blobClient);
149+
dataStorageService.CreateNote(note);
150+
return TypedResults.Ok(note);
151+
}
152+
catch (Exception ex)
153+
{
154+
Console.WriteLine($"An error occurred while updating a note: {ex.Message}");
155+
return TypedResults.BadRequest();
156+
}
157+
}
158+
159+
static Results<Ok, NotFound> DeleteNote(string rowKey,
160+
TableServiceClient tblClient,
161+
BlobServiceClient blobClient)
162+
{
163+
var dataStorageService = new DataStorageService(tblClient, blobClient);
164+
var result = dataStorageService.DeleteNote(rowKey);
165+
return result ? TypedResults.Ok() : TypedResults.NotFound();
166+
}
118167
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
@page "/noteeditor/{noteId}"
2+
3+
@using NoteBookmark.BlazorApp
4+
@using NoteBookmark.Domain
5+
@inject PostNoteClient client
6+
@inject NavigationManager Navigation
7+
@inject IToastService toastService
8+
9+
@rendermode InteractiveServer
10+
11+
<PageTitle>Edit Note</PageTitle>
12+
13+
<h1>Edit Note</h1>
14+
15+
@if (note == null)
16+
{
17+
<p><em>Loading...</em></p>
18+
}
19+
else
20+
{
21+
<EditForm Model="@note" OnValidSubmit="SaveNote">
22+
<DataAnnotationsValidator />
23+
<ValidationSummary />
24+
25+
<FluentStack Orientation="Orientation.Vertical">
26+
<div>
27+
<FluentSelect @bind-Value="note.Category" TOption="string" Label="Category">
28+
@foreach (var category in _categories)
29+
{
30+
<FluentOption TOption="string" Value="@category">@category</FluentOption>
31+
}
32+
</FluentSelect>
33+
</div>
34+
35+
<div>
36+
<FluentTextArea Name="Comment" Label="Comment" @bind-Value="note.Comment" Required="true" />
37+
</div>
38+
39+
<div>
40+
<FluentTextField Name="Tags" Label="Tags" @bind-Value="note.Tags" />
41+
</div>
42+
</FluentStack>
43+
44+
<FluentStack Orientation="Orientation.Horizontal" style="margin-top: 20px;">
45+
<FluentButton Type="ButtonType.Submit" Appearance="Appearance.Accent">Save</FluentButton>
46+
<FluentButton OnClick="BackToList" Appearance="Appearance.Neutral">Back to List</FluentButton>
47+
<FluentButton OnClick="DeleteNoteAsync" Appearance="Appearance.Accent" Color="Color.Danger">Delete</FluentButton>
48+
</FluentStack>
49+
</EditForm>
50+
}
51+
52+
53+
@code {
54+
[Parameter]
55+
public string? noteId { get; set; }
56+
57+
private Note? note;
58+
private List<string> _categories = NoteCategories.GetCategories();
59+
60+
protected override async Task OnInitializedAsync()
61+
{
62+
if (!string.IsNullOrEmpty(noteId))
63+
{
64+
note = await client.GetNote(noteId);
65+
}
66+
}
67+
68+
private async Task SaveNote()
69+
{
70+
if (note != null)
71+
{
72+
var result = await client.UpdateNote(note);
73+
if (result)
74+
{
75+
toastService.ShowSuccess("Note updated successfully!");
76+
Navigation.NavigateTo("/posts");
77+
}
78+
else
79+
{
80+
toastService.ShowError("Failed to update note. Please try again.");
81+
}
82+
}
83+
}
84+
85+
private void BackToList()
86+
{
87+
Navigation.NavigateTo("/posts");
88+
}
89+
90+
private async Task DeleteNoteAsync()
91+
{
92+
if (note != null)
93+
{
94+
var result = await client.DeleteNote(note.RowKey);
95+
if (result)
96+
{
97+
toastService.ShowSuccess("Note deleted successfully!");
98+
Navigation.NavigateTo("/posts");
99+
}
100+
else
101+
{
102+
toastService.ShowError("Failed to delete note. Please try again.");
103+
}
104+
}
105+
}
106+
}

src/NoteBookmark.BlazorApp/Components/Pages/Posts.razor

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
}
3636
else
3737
{
38-
<FluentButton IconEnd="@(new Icons.Filled.Size20.NoteEdit())" />
38+
<FluentButton OnClick="@(() => EditNoteForPost(context!.NoteId))" IconEnd="@(new Icons.Filled.Size20.NoteEdit())" />
3939
}
4040
</TemplateColumn>
4141
<PropertyColumn Title="Title" Property="@(c => c!.Title)" Sortable="true" Filtered="!string.IsNullOrWhiteSpace(titleFilter)" >
@@ -117,6 +117,11 @@
117117
Navigation.NavigateTo($"posteditor/{postId}");
118118
}
119119

120+
private void EditNoteForPost(string noteId)
121+
{
122+
Navigation.NavigateTo($"noteeditor/{noteId}");
123+
}
124+
120125
private async Task AddNewPost()
121126
{
122127
if (!string.IsNullOrEmpty(newPostUrl))

src/NoteBookmark.BlazorApp/PostNoteClient.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,24 @@ public async Task CreateNote(Note note)
3131
response.EnsureSuccessStatusCode();
3232
}
3333

34+
public async Task<Note?> GetNote(string noteId)
35+
{
36+
var note = await httpClient.GetFromJsonAsync<Note>($"api/notes/note/{noteId}");
37+
return note;
38+
}
39+
40+
public async Task<bool> UpdateNote(Note note)
41+
{
42+
var response = await httpClient.PutAsJsonAsync("api/notes/note", note);
43+
return response.IsSuccessStatusCode;
44+
}
45+
46+
public async Task<bool> DeleteNote(string noteId)
47+
{
48+
var response = await httpClient.DeleteAsync($"api/notes/note/{noteId}");
49+
return response.IsSuccessStatusCode;
50+
}
51+
3452
public async Task<ReadingNotes> CreateReadingNotes()
3553
{
3654
var rnCounter = await httpClient.GetStringAsync("api/settings/GetNextReadingNotesCounter");

0 commit comments

Comments
 (0)