Skip to content

Commit 35f3688

Browse files
authored
Merge pull request #90 from fboucher/copilot/edit-delete-notes-on-post
Add note editing and deletion capabilities
2 parents ae83451 + 72cdb7e commit 35f3688

File tree

7 files changed

+292
-13
lines changed

7 files changed

+292
-13
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
}

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

Lines changed: 61 additions & 10 deletions
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)" >
@@ -98,23 +98,74 @@
9898
});
9999

100100
var result = await dialog.Result;
101-
if (!result.Cancelled && result.Data != null)
101+
if (!result.Cancelled && result.Data is NoteDialogResult dialogResult)
102102
{
103-
var note = (Note)result.Data;
104-
await client.CreateNote(note);
105-
ShowConfirmationMessage();
106-
await LoadPosts();
103+
if (dialogResult.Action == "Save" && dialogResult.Note != null)
104+
{
105+
await client.CreateNote(dialogResult.Note);
106+
toastService.ShowSuccess("Note created successfully!");
107+
await LoadPosts();
108+
}
107109
}
108110
}
109111

110-
private void ShowConfirmationMessage()
112+
private void EditNote(string postId)
111113
{
112-
toastService.ShowSuccess("Note created successfully!");
114+
Navigation.NavigateTo($"posteditor/{postId}");
113115
}
114116

115-
private void EditNote(string postId)
117+
private async Task EditNoteForPost(string noteId)
116118
{
117-
Navigation.NavigateTo($"posteditor/{postId}");
119+
try
120+
{
121+
var existingNote = await client.GetNote(noteId);
122+
if (existingNote == null)
123+
{
124+
toastService.ShowError("Note not found.");
125+
return;
126+
}
127+
128+
IDialogReference dialog = await DialogService.ShowDialogAsync<NoteDialog>(existingNote, new DialogParameters(){
129+
Title = "Edit note",
130+
PreventDismissOnOverlayClick = true,
131+
PreventScroll = true,
132+
});
133+
134+
var result = await dialog.Result;
135+
if (!result.Cancelled && result.Data is NoteDialogResult dialogResult)
136+
{
137+
if (dialogResult.Action == "Delete" && dialogResult.Note != null)
138+
{
139+
var deleteResult = await client.DeleteNote(dialogResult.Note.RowKey);
140+
if (deleteResult)
141+
{
142+
toastService.ShowSuccess("Note deleted successfully!");
143+
await LoadPosts();
144+
}
145+
else
146+
{
147+
toastService.ShowError("Failed to delete note. Please try again.");
148+
}
149+
}
150+
else if (dialogResult.Action == "Save" && dialogResult.Note != null)
151+
{
152+
var updateResult = await client.UpdateNote(dialogResult.Note);
153+
if (updateResult)
154+
{
155+
toastService.ShowSuccess("Note updated successfully!");
156+
await LoadPosts();
157+
}
158+
else
159+
{
160+
toastService.ShowError("Failed to update note. Please try again.");
161+
}
162+
}
163+
}
164+
}
165+
catch (Exception)
166+
{
167+
toastService.ShowError("An error occurred. Please try again.");
168+
}
118169
}
119170

120171
private async Task AddNewPost()

src/NoteBookmark.BlazorApp/Components/Shared/NoteDialog.razor

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,14 @@
5656
OnClick="@CancelAsync">
5757
Cancel
5858
</FluentButton>
59+
@if (_isEditMode)
60+
{
61+
<FluentButton Appearance="Appearance.Accent"
62+
Color="Color.Danger"
63+
OnClick="@DeleteAsync">
64+
Delete
65+
</FluentButton>
66+
}
5967
</FluentDialogFooter>
6068

6169
@code {
@@ -68,19 +76,35 @@
6876
private Domain.Note _note = default!;
6977

7078
private List<string> _categories = NoteCategories.GetCategories();
79+
private bool _isEditMode = false;
7180

7281
protected override void OnInitialized()
7382
{
74-
_note = new Note{PostId = Content.PostId};
83+
// Check if we're editing an existing note or creating a new one
84+
_isEditMode = !string.IsNullOrEmpty(Content.RowKey) && !Content.RowKey.Equals(Guid.Empty.ToString(), StringComparison.OrdinalIgnoreCase);
85+
86+
if (_isEditMode)
87+
{
88+
// Editing mode - use the existing note data
89+
_note = Content;
90+
}
91+
else
92+
{
93+
// Create mode - create a new note with the PostId
94+
_note = new Note { PostId = Content.PostId };
95+
}
7596
}
7697

7798
private async Task SaveAsync()
7899
{
79-
_note.DateAdded = DateTime.UtcNow;
100+
if (!_isEditMode)
101+
{
102+
_note.DateAdded = DateTime.UtcNow;
103+
}
80104

81105
if (_note.Validate())
82106
{
83-
await Dialog.CloseAsync(_note);
107+
await Dialog.CloseAsync(new NoteDialogResult { Action = "Save", Note = _note });
84108
}
85109
}
86110

@@ -89,5 +113,10 @@
89113
await Dialog.CancelAsync();
90114
}
91115

116+
private async Task DeleteAsync()
117+
{
118+
await Dialog.CloseAsync(new NoteDialogResult { Action = "Delete", Note = _note });
119+
}
120+
92121

93122
}

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");
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace NoteBookmark.Domain;
2+
3+
public class NoteDialogResult
4+
{
5+
public string Action { get; set; } = "Save";
6+
public Note? Note { get; set; }
7+
}

0 commit comments

Comments
 (0)