diff --git a/src/NoteBookmark.Api.Tests/Endpoints/NoteEndpointsTests.cs b/src/NoteBookmark.Api.Tests/Endpoints/NoteEndpointsTests.cs index c1bd0be..565d0da 100644 --- a/src/NoteBookmark.Api.Tests/Endpoints/NoteEndpointsTests.cs +++ b/src/NoteBookmark.Api.Tests/Endpoints/NoteEndpointsTests.cs @@ -158,6 +158,111 @@ public async Task UpdatePostReadStatus_UpdatesAllPostsWithNotes() // This would require additional verification logic based on the actual implementation } + [Fact] + public async Task GetNote_WithValidNoteId_ReturnsNote() + { + // Arrange + var testPost = await CreateAndSaveTestPost(); + var testNote = CreateTestNote(); + testNote.PostId = testPost.RowKey; + await _client.PostAsJsonAsync("/api/notes/note", testNote); + + // Act + var response = await _client.GetAsync($"/api/notes/note/{testNote.RowKey}"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + + var retrievedNote = await response.Content.ReadFromJsonAsync(); + retrievedNote.Should().NotBeNull(); + retrievedNote!.RowKey.Should().Be(testNote.RowKey); + retrievedNote.Comment.Should().Be(testNote.Comment); + } + + [Fact] + public async Task GetNote_WithInvalidNoteId_ReturnsNotFound() + { + // Arrange + var nonExistentNoteId = "non-existent-note-id"; + + // Act + var response = await _client.GetAsync($"/api/notes/note/{nonExistentNoteId}"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.NotFound); + } + + [Fact] + public async Task UpdateNote_WithValidNote_ReturnsOk() + { + // Arrange + var testPost = await CreateAndSaveTestPost(); + var testNote = CreateTestNote(); + testNote.PostId = testPost.RowKey; + await _client.PostAsJsonAsync("/api/notes/note", testNote); + + // Update the note + testNote.Comment = "Updated comment"; + testNote.Tags = "updated, tags"; + + // Act + var response = await _client.PutAsJsonAsync("/api/notes/note", testNote); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + + var updatedNote = await response.Content.ReadFromJsonAsync(); + updatedNote.Should().NotBeNull(); + updatedNote!.Comment.Should().Be("Updated comment"); + updatedNote.Tags.Should().Be("updated, tags"); + } + + [Fact] + public async Task UpdateNote_WithInvalidNote_ReturnsBadRequest() + { + // Arrange + var invalidNote = new Note(); // Missing required comment + + // Act + var response = await _client.PutAsJsonAsync("/api/notes/note", invalidNote); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.BadRequest); + } + + [Fact] + public async Task DeleteNote_WithValidNoteId_ReturnsOk() + { + // Arrange + var testPost = await CreateAndSaveTestPost(); + var testNote = CreateTestNote(); + testNote.PostId = testPost.RowKey; + await _client.PostAsJsonAsync("/api/notes/note", testNote); + + // Act + var response = await _client.DeleteAsync($"/api/notes/note/{testNote.RowKey}"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + + // Verify the note is deleted + var getResponse = await _client.GetAsync($"/api/notes/note/{testNote.RowKey}"); + getResponse.StatusCode.Should().Be(HttpStatusCode.NotFound); + } + + [Fact] + public async Task DeleteNote_WithInvalidNoteId_ReturnsNotFound() + { + // Arrange + var nonExistentNoteId = "non-existent-note-id"; + + // Act + var response = await _client.DeleteAsync($"/api/notes/note/{nonExistentNoteId}"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.NotFound); + } + // Helper methods private async Task SeedTestNotes() { diff --git a/src/NoteBookmark.Api/DataStorageService.cs b/src/NoteBookmark.Api/DataStorageService.cs index 3fbb8fa..c744eb4 100644 --- a/src/NoteBookmark.Api/DataStorageService.cs +++ b/src/NoteBookmark.Api/DataStorageService.cs @@ -179,6 +179,26 @@ public void CreateNote(Note note) } } + public Note? GetNote(string rowKey) + { + var tblNote = GetNoteTable(); + var result = tblNote.Query(filter: $"RowKey eq '{rowKey}'"); + Note? note = result.FirstOrDefault(); + return note; + } + + public bool DeleteNote(string rowKey) + { + var tblNote = GetNoteTable(); + var existingNote = tblNote.Query(filter: $"RowKey eq '{rowKey}'").FirstOrDefault(); + if (existingNote != null) + { + tblNote.DeleteEntity(existingNote.PartitionKey, existingNote.RowKey); + return true; + } + return false; + } + public async Task GetSettings() { diff --git a/src/NoteBookmark.Api/NoteEnpoints.cs b/src/NoteBookmark.Api/NoteEnpoints.cs index 77d36f3..59bc41b 100644 --- a/src/NoteBookmark.Api/NoteEnpoints.cs +++ b/src/NoteBookmark.Api/NoteEnpoints.cs @@ -27,6 +27,15 @@ public static void MapNoteEndpoints(this IEndpointRouteBuilder app) endpoints.MapGet("/UpdatePostReadStatus", UpdatePostReadStatus) .WithDescription("Update the read status of all posts to true if they have a note referencing them."); + + endpoints.MapGet("/note/{rowKey}", GetNote) + .WithDescription("Get a specific note by its row key."); + + endpoints.MapPut("/note", UpdateNote) + .WithDescription("Update an existing note"); + + endpoints.MapDelete("/note/{rowKey}", DeleteNote) + .WithDescription("Delete a note"); } static Results, BadRequest> CreateNote(Note note, @@ -115,4 +124,44 @@ private static async Task> UpdatePostReadStatus(TableSer return TypedResults.BadRequest(); } } + + static Results, NotFound> GetNote(string rowKey, + TableServiceClient tblClient, + BlobServiceClient blobClient) + { + var dataStorageService = new DataStorageService(tblClient, blobClient); + var note = dataStorageService.GetNote(rowKey); + return note == null ? TypedResults.NotFound() : TypedResults.Ok(note); + } + + static Results, BadRequest> UpdateNote(Note note, + TableServiceClient tblClient, + BlobServiceClient blobClient) + { + try + { + if (!note.Validate()) + { + return TypedResults.BadRequest(); + } + + var dataStorageService = new DataStorageService(tblClient, blobClient); + dataStorageService.CreateNote(note); + return TypedResults.Ok(note); + } + catch (Exception ex) + { + Console.WriteLine($"An error occurred while updating a note: {ex.Message}"); + return TypedResults.BadRequest(); + } + } + + static Results DeleteNote(string rowKey, + TableServiceClient tblClient, + BlobServiceClient blobClient) + { + var dataStorageService = new DataStorageService(tblClient, blobClient); + var result = dataStorageService.DeleteNote(rowKey); + return result ? TypedResults.Ok() : TypedResults.NotFound(); + } } diff --git a/src/NoteBookmark.BlazorApp/Components/Pages/Posts.razor b/src/NoteBookmark.BlazorApp/Components/Pages/Posts.razor index e43e68f..1d2672d 100644 --- a/src/NoteBookmark.BlazorApp/Components/Pages/Posts.razor +++ b/src/NoteBookmark.BlazorApp/Components/Pages/Posts.razor @@ -35,7 +35,7 @@ } else { - + } @@ -98,23 +98,74 @@ }); var result = await dialog.Result; - if (!result.Cancelled && result.Data != null) + if (!result.Cancelled && result.Data is NoteDialogResult dialogResult) { - var note = (Note)result.Data; - await client.CreateNote(note); - ShowConfirmationMessage(); - await LoadPosts(); + if (dialogResult.Action == "Save" && dialogResult.Note != null) + { + await client.CreateNote(dialogResult.Note); + toastService.ShowSuccess("Note created successfully!"); + await LoadPosts(); + } } } - private void ShowConfirmationMessage() + private void EditNote(string postId) { - toastService.ShowSuccess("Note created successfully!"); + Navigation.NavigateTo($"posteditor/{postId}"); } - private void EditNote(string postId) + private async Task EditNoteForPost(string noteId) { - Navigation.NavigateTo($"posteditor/{postId}"); + try + { + var existingNote = await client.GetNote(noteId); + if (existingNote == null) + { + toastService.ShowError("Note not found."); + return; + } + + IDialogReference dialog = await DialogService.ShowDialogAsync(existingNote, new DialogParameters(){ + Title = "Edit note", + PreventDismissOnOverlayClick = true, + PreventScroll = true, + }); + + var result = await dialog.Result; + if (!result.Cancelled && result.Data is NoteDialogResult dialogResult) + { + if (dialogResult.Action == "Delete" && dialogResult.Note != null) + { + var deleteResult = await client.DeleteNote(dialogResult.Note.RowKey); + if (deleteResult) + { + toastService.ShowSuccess("Note deleted successfully!"); + await LoadPosts(); + } + else + { + toastService.ShowError("Failed to delete note. Please try again."); + } + } + else if (dialogResult.Action == "Save" && dialogResult.Note != null) + { + var updateResult = await client.UpdateNote(dialogResult.Note); + if (updateResult) + { + toastService.ShowSuccess("Note updated successfully!"); + await LoadPosts(); + } + else + { + toastService.ShowError("Failed to update note. Please try again."); + } + } + } + } + catch (Exception) + { + toastService.ShowError("An error occurred. Please try again."); + } } private async Task AddNewPost() diff --git a/src/NoteBookmark.BlazorApp/Components/Shared/NoteDialog.razor b/src/NoteBookmark.BlazorApp/Components/Shared/NoteDialog.razor index 396cb5b..a3821be 100644 --- a/src/NoteBookmark.BlazorApp/Components/Shared/NoteDialog.razor +++ b/src/NoteBookmark.BlazorApp/Components/Shared/NoteDialog.razor @@ -56,6 +56,14 @@ OnClick="@CancelAsync"> Cancel + @if (_isEditMode) + { + + Delete + + } @code { @@ -68,19 +76,35 @@ private Domain.Note _note = default!; private List _categories = NoteCategories.GetCategories(); + private bool _isEditMode = false; protected override void OnInitialized() { - _note = new Note{PostId = Content.PostId}; + // Check if we're editing an existing note or creating a new one + _isEditMode = !string.IsNullOrEmpty(Content.RowKey) && !Content.RowKey.Equals(Guid.Empty.ToString(), StringComparison.OrdinalIgnoreCase); + + if (_isEditMode) + { + // Editing mode - use the existing note data + _note = Content; + } + else + { + // Create mode - create a new note with the PostId + _note = new Note { PostId = Content.PostId }; + } } private async Task SaveAsync() { - _note.DateAdded = DateTime.UtcNow; + if (!_isEditMode) + { + _note.DateAdded = DateTime.UtcNow; + } if (_note.Validate()) { - await Dialog.CloseAsync(_note); + await Dialog.CloseAsync(new NoteDialogResult { Action = "Save", Note = _note }); } } @@ -89,5 +113,10 @@ await Dialog.CancelAsync(); } + private async Task DeleteAsync() + { + await Dialog.CloseAsync(new NoteDialogResult { Action = "Delete", Note = _note }); + } + } diff --git a/src/NoteBookmark.BlazorApp/PostNoteClient.cs b/src/NoteBookmark.BlazorApp/PostNoteClient.cs index 65e6a49..b6d734f 100644 --- a/src/NoteBookmark.BlazorApp/PostNoteClient.cs +++ b/src/NoteBookmark.BlazorApp/PostNoteClient.cs @@ -31,6 +31,24 @@ public async Task CreateNote(Note note) response.EnsureSuccessStatusCode(); } + public async Task GetNote(string noteId) + { + var note = await httpClient.GetFromJsonAsync($"api/notes/note/{noteId}"); + return note; + } + + public async Task UpdateNote(Note note) + { + var response = await httpClient.PutAsJsonAsync("api/notes/note", note); + return response.IsSuccessStatusCode; + } + + public async Task DeleteNote(string noteId) + { + var response = await httpClient.DeleteAsync($"api/notes/note/{noteId}"); + return response.IsSuccessStatusCode; + } + public async Task CreateReadingNotes() { var rnCounter = await httpClient.GetStringAsync("api/settings/GetNextReadingNotesCounter"); diff --git a/src/NoteBookmark.Domain/NoteDialogResult.cs b/src/NoteBookmark.Domain/NoteDialogResult.cs new file mode 100644 index 0000000..691f609 --- /dev/null +++ b/src/NoteBookmark.Domain/NoteDialogResult.cs @@ -0,0 +1,7 @@ +namespace NoteBookmark.Domain; + +public class NoteDialogResult +{ + public string Action { get; set; } = "Save"; + public Note? Note { get; set; } +}