Skip to content

Commit 53f4c2d

Browse files
author
davidwei
committed
Add feature: Exclude authenticated user's notes from public notes list + unified newline character process
1 parent 4a0f0cd commit 53f4c2d

File tree

8 files changed

+68
-13
lines changed

8 files changed

+68
-13
lines changed

src/HappyNotes.Api/Controllers/NotesController.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ public async Task<ApiResult<PageData<NoteDto>>> MyLatest(int pageSize, int pageN
2929
[EnforcePageSizeLimit(Constants.MaxPageSize)]
3030
public async Task<ApiResult<PageData<NoteDto>>> Latest(int pageSize, int pageNumber)
3131
{
32-
var notes = await noteService.GetPublicNotes(pageSize, pageNumber);
32+
// If the caller is authenticated currentUser.Id will be > 0; exclude their own notes to avoid showing them
33+
long? excludeUserId = currentUser?.Id > 0 ? currentUser.Id : null;
34+
var notes = await noteService.GetPublicNotes(pageSize, pageNumber, excludeUserId);
3335
return new SuccessfulResult<PageData<NoteDto>>(mapper.Map<PageData<NoteDto>>(notes));
3436
}
3537

src/HappyNotes.Common/StringExtensions.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,4 +161,28 @@ public static string RemoveNoteLinks(this string? text)
161161

162162
[GeneratedRegex(@"<\s*(?!https?)([a-zA-Z]+)[^>]*>.*</\s*\1\s*>|<\s*(?!https?)([a-zA-Z]+)[^>]*/>", RegexOptions.IgnoreCase | RegexOptions.Singleline)]
163163
private static partial Regex HtmlTagPattern();
164+
165+
/// <summary>
166+
/// Normalizes various newline formats to Unix-style LF (\n).
167+
/// Handles Windows (CRLF), Unix (LF), old Mac (CR), and Unicode line separators.
168+
/// </summary>
169+
public static string NormalizeNewlines(this string? input)
170+
{
171+
if (string.IsNullOrEmpty(input))
172+
return input ?? string.Empty;
173+
174+
// First, convert Windows CRLF (\r\n) to Unix LF (\n)
175+
string result = input.Replace("\r\n", "\n");
176+
177+
// Then, convert any remaining CR (\r) to LF (\n) - handles old Mac format
178+
result = result.Replace("\r", "\n");
179+
180+
// Replace Unicode Line Separator (U+2028) with a single newline (\n)
181+
result = result.Replace("\u2028", "\n");
182+
183+
// Replace Unicode Paragraph Separator (U+2029) with a double newline (\n\n)
184+
result = result.Replace("\u2029", "\n\n");
185+
186+
return result;
187+
}
164188
}

src/HappyNotes.Repositories/NoteRepository.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,15 @@ public async Task<PageData<Note>> GetUserNotes(long userId, int pageSize, int pa
5050
n => n.CreatedAt, isAsc);
5151
}
5252

53-
public async Task<PageData<Note>> GetPublicNotes(int pageSize, int pageNumber, bool isAsc = false)
53+
public async Task<PageData<Note>> GetPublicNotes(int pageSize, int pageNumber, bool isAsc = false, long? excludeUserId = null)
5454
{
55+
if (excludeUserId.HasValue)
56+
{
57+
return await _GetPageDataAsync(pageSize, pageNumber,
58+
n => n.DeletedAt == null && n.IsPrivate == false && n.UserId != excludeUserId.Value,
59+
n => n.CreatedAt, isAsc);
60+
}
61+
5562
return await _GetPageDataAsync(pageSize, pageNumber,
5663
n => n.DeletedAt == null && n.IsPrivate == false,
5764
n => n.CreatedAt, isAsc);

src/HappyNotes.Repositories/interfaces/INoteRepository.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public interface INoteRepository : IRepositoryBase<Note>
1111
Task<PageData<Note>> GetUserTagNotes(long userId, string tag, int pageSize, int pageNumber, bool isAsc = false);
1212
Task<PageData<Note>> GetUserNotes(long userId, int pageSize, int pageNumber, bool includePrivate = false,
1313
bool isAsc = false);
14-
Task<PageData<Note>> GetPublicNotes(int pageSize, int pageNumber, bool isAsc = false);
14+
Task<PageData<Note>> GetPublicNotes(int pageSize, int pageNumber, bool isAsc = false, long? excludeUserId = null);
1515
Task<PageData<Note>> GetLinkedNotes(long userId, long noteId, int max = 100);
1616

1717
Task<PageData<Note>> GetUserDeletedNotes(long userId, int pageSize, int pageNumber);

src/HappyNotes.Services/NoteService.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ TimeProvider timeProvider
2727
{
2828
public async Task<long> Post(long userId, PostNoteRequest request)
2929
{
30-
var fullContent = request.Content?.Trim() ?? string.Empty;
30+
var fullContent = request.Content?.NormalizeNewlines().Trim() ?? string.Empty;
3131
if (string.IsNullOrEmpty(request.Content))
3232
{
3333
throw new ArgumentException("Nothing was submitted");
@@ -74,7 +74,7 @@ await longNoteRepository.InsertAsync(new LongNote
7474
}
7575
catch (Exception ex)
7676
{
77-
logger.LogError(ex, "Failed to sync new note to service {ServiceType}, note ID: {NoteId}",
77+
logger.LogError(ex, "Failed to sync new note to service {ServiceType}, note ID: {NoteId}",
7878
syncNoteService.GetType().Name, note.Id);
7979
}
8080
});
@@ -97,7 +97,7 @@ public async Task<bool> Update(long userId, long id, PostNoteRequest request)
9797
}
9898

9999
var newNote = mapper.Map<PostNoteRequest, Note>(request);
100-
var fullContent = request.Content ?? string.Empty;
100+
var fullContent = (request.Content ?? string.Empty).NormalizeNewlines();
101101

102102
// the following is a hack for user shukebeta only
103103
if (existingNote.UserId == 1 && fullContent.IsHtml())
@@ -147,7 +147,7 @@ public async Task<bool> Update(long userId, long id, PostNoteRequest request)
147147
}
148148
catch (Exception ex)
149149
{
150-
logger.LogError(ex, "Failed to sync edit note to service {ServiceType}, note ID: {NoteId}",
150+
logger.LogError(ex, "Failed to sync edit note to service {ServiceType}, note ID: {NoteId}",
151151
syncNoteService.GetType().Name, newNote.Id);
152152
}
153153
});
@@ -174,15 +174,15 @@ public async Task<PageData<Note>> GetUserNotes(long userId, int pageSize, int pa
174174
return await noteRepository.GetUserNotes(userId, pageSize, pageNumber, includePrivate);
175175
}
176176

177-
public async Task<PageData<Note>> GetPublicNotes(int pageSize, int pageNumber)
177+
public async Task<PageData<Note>> GetPublicNotes(int pageSize, int pageNumber, long? excludeUserId = null)
178178
{
179179
if (pageNumber > Constants.PublicNotesMaxPage)
180180
{
181181
throw new Exception(
182182
$"We only provide at most {Constants.PublicNotesMaxPage} page of public notes at the moment");
183183
}
184184

185-
var notes = await noteRepository.GetPublicNotes(pageSize, pageNumber);
185+
var notes = await noteRepository.GetPublicNotes(pageSize, pageNumber, false, excludeUserId);
186186
if (notes.TotalCount > Constants.PublicNotesMaxPage * pageSize)
187187
{
188188
notes.TotalCount = Constants.PublicNotesMaxPage * pageSize;
@@ -328,7 +328,7 @@ public async Task<bool> Delete(long userId, long id)
328328
}
329329
catch (Exception ex)
330330
{
331-
logger.LogError(ex, "Failed to sync delete note to service {ServiceType}, note ID: {NoteId}",
331+
logger.LogError(ex, "Failed to sync delete note to service {ServiceType}, note ID: {NoteId}",
332332
syncNoteService.GetType().Name, note.Id);
333333
}
334334
});
@@ -377,7 +377,7 @@ public async Task<bool> Undelete(long userId, long id)
377377
}
378378
catch (Exception ex)
379379
{
380-
logger.LogError(ex, "Failed to sync undelete note to service {ServiceType}, note ID: {NoteId}",
380+
logger.LogError(ex, "Failed to sync undelete note to service {ServiceType}, note ID: {NoteId}",
381381
syncNoteService.GetType().Name, undeletedNote.Id);
382382
}
383383
});

src/HappyNotes.Services/interfaces/INoteService.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@ public interface INoteService
1616
Task<PageData<Note>> GetUserNotes(long userId, int pageSize, int pageNumber, bool includePrivate = false);
1717
/// <summary>
1818
/// Latest public notes / random browsing
19+
/// If excludeUserId is provided, notes from that user will be excluded (useful to avoid showing caller's own public notes).
1920
/// </summary>
2021
/// <param name="pageSize"></param>
2122
/// <param name="pageNumber"></param>
23+
/// <param name="excludeUserId">Optional user id to exclude from results</param>
2224
/// <returns></returns>
23-
Task<PageData<Note>> GetPublicNotes(int pageSize, int pageNumber);
25+
Task<PageData<Note>> GetPublicNotes(int pageSize, int pageNumber, long? excludeUserId = null);
2426

2527
Task<PageData<Note>> GetUserTagNotes(long userId, int pageSize, int pageNumber, string tag);
2628
Task<PageData<Note>> GetUserKeywordNotes(long userId, int pageSize, int pageNumber, string keyword, NoteFilterType filter = NoteFilterType.Normal);

tests/HappyNotes.Common.Tests/StringExtensionsTests.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,4 +157,24 @@ public void RemoveNoteLinksTest(string input, string expected)
157157
Assert.That(result, Is.EqualTo(expected));
158158
}
159159

160+
161+
[TestCase(null, "")]
162+
[TestCase("", "")]
163+
[TestCase("Hello\nWorld", "Hello\nWorld")] // Unix LF - unchanged
164+
[TestCase("Hello\r\nWorld", "Hello\nWorld")] // Windows CRLF → LF
165+
[TestCase("Hello\rWorld", "Hello\nWorld")] // Old Mac CR → LF
166+
[TestCase("Hello\u2028World", "Hello\nWorld")] // Unicode Line Separator → LF
167+
[TestCase("Hello\u2029World", "Hello\n\nWorld")] // Unicode Paragraph Separator → LF LF
168+
[TestCase("Line1\r\nLine2\rLine3\nLine4", "Line1\nLine2\nLine3\nLine4")] // Mixed formats
169+
[TestCase("A\r\n\r\nB", "A\n\nB")] // Multiple Windows line breaks
170+
[TestCase("A\r\rB", "A\n\nB")] // Multiple old Mac line breaks
171+
[TestCase("Mixed\r\n\rformats\u2028here", "Mixed\n\nformats\nhere")] // All types mixed
172+
public void NormalizeNewlinesTest(string input, string expected)
173+
{
174+
// Act
175+
var result = input.NormalizeNewlines();
176+
177+
// Assert
178+
Assert.That(result, Is.EqualTo(expected));
179+
}
160180
}

tests/HappyNotes.Services.Tests/NoteServiceTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ public async Task GetPublicNotes_WithinMaxPage_ReturnsPageData()
298298
TotalCount = 1
299299
};
300300

301-
_mockNoteRepository.Setup(r => r.GetPublicNotes(pageSize, pageNumber, false))
301+
_mockNoteRepository.Setup(r => r.GetPublicNotes(pageSize, pageNumber, false, It.IsAny<long?>()))
302302
.ReturnsAsync(expectedNotes);
303303

304304
// Act

0 commit comments

Comments
 (0)