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
105 changes: 105 additions & 0 deletions src/NoteBookmark.Api.Tests/Endpoints/NoteEndpointsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Note>();
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<Note>();
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()
{
Expand Down
20 changes: 20 additions & 0 deletions src/NoteBookmark.Api/DataStorageService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,26 @@ public void CreateNote(Note note)
}
}

public Note? GetNote(string rowKey)
{
var tblNote = GetNoteTable();
var result = tblNote.Query<Note>(filter: $"RowKey eq '{rowKey}'");
Note? note = result.FirstOrDefault<Note>();
return note;
}

public bool DeleteNote(string rowKey)
{
var tblNote = GetNoteTable();
var existingNote = tblNote.Query<Note>(filter: $"RowKey eq '{rowKey}'").FirstOrDefault();
if (existingNote != null)
{
tblNote.DeleteEntity(existingNote.PartitionKey, existingNote.RowKey);
return true;
}
return false;
}


public async Task<Settings> GetSettings()
{
Expand Down
49 changes: 49 additions & 0 deletions src/NoteBookmark.Api/NoteEnpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Created<Note>, BadRequest> CreateNote(Note note,
Expand Down Expand Up @@ -115,4 +124,44 @@ private static async Task<Results<Ok, BadRequest>> UpdatePostReadStatus(TableSer
return TypedResults.BadRequest();
}
}

static Results<Ok<Note>, 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<Ok<Note>, 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<Ok, NotFound> DeleteNote(string rowKey,
TableServiceClient tblClient,
BlobServiceClient blobClient)
{
var dataStorageService = new DataStorageService(tblClient, blobClient);
var result = dataStorageService.DeleteNote(rowKey);
return result ? TypedResults.Ok() : TypedResults.NotFound();
}
}
71 changes: 61 additions & 10 deletions src/NoteBookmark.BlazorApp/Components/Pages/Posts.razor
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
}
else
{
<FluentButton IconEnd="@(new Icons.Filled.Size20.NoteEdit())" />
<FluentButton OnClick="@(() => EditNoteForPost(context!.NoteId))" IconEnd="@(new Icons.Filled.Size20.NoteEdit())" />
}
</TemplateColumn>
<PropertyColumn Title="Title" Property="@(c => c!.Title)" Sortable="true" Filtered="!string.IsNullOrWhiteSpace(titleFilter)" >
Expand Down Expand Up @@ -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<NoteDialog>(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()
Expand Down
35 changes: 32 additions & 3 deletions src/NoteBookmark.BlazorApp/Components/Shared/NoteDialog.razor
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@
OnClick="@CancelAsync">
Cancel
</FluentButton>
@if (_isEditMode)
{
<FluentButton Appearance="Appearance.Accent"
Color="Color.Danger"
OnClick="@DeleteAsync">
Delete
</FluentButton>
}
</FluentDialogFooter>

@code {
Expand All @@ -68,19 +76,35 @@
private Domain.Note _note = default!;

private List<string> _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 });
}
}

Expand All @@ -89,5 +113,10 @@
await Dialog.CancelAsync();
}

private async Task DeleteAsync()
{
await Dialog.CloseAsync(new NoteDialogResult { Action = "Delete", Note = _note });
}


}
18 changes: 18 additions & 0 deletions src/NoteBookmark.BlazorApp/PostNoteClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,24 @@ public async Task CreateNote(Note note)
response.EnsureSuccessStatusCode();
}

public async Task<Note?> GetNote(string noteId)
{
var note = await httpClient.GetFromJsonAsync<Note>($"api/notes/note/{noteId}");
return note;
}

public async Task<bool> UpdateNote(Note note)
{
var response = await httpClient.PutAsJsonAsync("api/notes/note", note);
return response.IsSuccessStatusCode;
}

public async Task<bool> DeleteNote(string noteId)
{
var response = await httpClient.DeleteAsync($"api/notes/note/{noteId}");
return response.IsSuccessStatusCode;
}

public async Task<ReadingNotes> CreateReadingNotes()
{
var rnCounter = await httpClient.GetStringAsync("api/settings/GetNextReadingNotesCounter");
Expand Down
7 changes: 7 additions & 0 deletions src/NoteBookmark.Domain/NoteDialogResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace NoteBookmark.Domain;

public class NoteDialogResult
{
public string Action { get; set; } = "Save";
public Note? Note { get; set; }
}
Loading