Compare commits
No commits in common. "79968ab79b660d3c8b3e8b1b3d9e795eb9b4fc9c" and "acb7ada0ad15c0e11aa779810d2a300f5dcfda57" have entirely different histories.
79968ab79b
...
acb7ada0ad
3
.gitignore
vendored
3
.gitignore
vendored
@ -261,6 +261,5 @@ paket-files/
|
|||||||
__pycache__/
|
__pycache__/
|
||||||
*.pyc
|
*.pyc
|
||||||
|
|
||||||
/.cursor
|
|
||||||
/.vscode
|
|
||||||
/staging
|
/staging
|
||||||
15
CHANGELOG.md
15
CHANGELOG.md
@ -1,23 +1,10 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## v1.6.5 - 2026-02-02
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Replaced explicit `ArgumentNullException` throws with `ArgumentNullException.ThrowIfNull` in `ExpressionExtensions`, `NetworkConnection`, `Base32Encoder`, `StringExtensions.CSVToDataTable`, `FileLoggerProvider`, and `JsonFileLoggerProvider`.
|
|
||||||
- **Base32Encoder**: empty input now throws `ArgumentException` instead of `ArgumentNullException` for clearer semantics.
|
|
||||||
- **StringExtensions.CSVToDataTable**: null file path throws via `ThrowIfNull`; empty/whitespace path throws `ArgumentException`.
|
|
||||||
- **ObjectExtensions**: `DeepClone` returns `default` for null input; `DeepEqual` explicitly treats (null, null) as true and (null, non-null) as false. Replaced obsolete `FormatterServices.GetUninitializedObject` with `RuntimeHelpers.GetUninitializedObject`. Fixed nullability in `ReferenceEqualityComparer` to match `IEqualityComparer<object>`.
|
|
||||||
- **TotpGenerator**: recovery code generation uses range syntax (`code[..4]`, `code[4..8]`) instead of `Substring`.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- **ExceptionExtensions.ExtractMessages**: null check added to avoid `NullReferenceException` when passed null.
|
|
||||||
- **BaseFileLogger.RemoveExpiredLogFiles**: guard added before `Substring(4)` so malformed log file names do not throw.
|
|
||||||
|
|
||||||
## v1.6.4 - 2026-02-21
|
## v1.6.4 - 2026-02-21
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@ -46,14 +46,7 @@ This project uses the following commit message format:
|
|||||||
| `(feature):` | New feature or enhancement |
|
| `(feature):` | New feature or enhancement |
|
||||||
| `(bugfix):` | Bug fix |
|
| `(bugfix):` | Bug fix |
|
||||||
| `(refactor):` | Code refactoring without functional changes |
|
| `(refactor):` | Code refactoring without functional changes |
|
||||||
| `(perf):` | Performance improvement without changing behavior |
|
| `(chore):` | Maintenance tasks (dependencies, CI, documentation) |
|
||||||
| `(test):` | Add or update tests |
|
|
||||||
| `(docs):` | Documentation-only changes |
|
|
||||||
| `(build):` | Build system, dependencies, packaging, or project file changes |
|
|
||||||
| `(ci):` | CI/CD pipeline or automation changes |
|
|
||||||
| `(style):` | Formatting or non-functional code style changes |
|
|
||||||
| `(revert):` | Revert a previous commit |
|
|
||||||
| `(chore):` | General maintenance tasks that do not fit the types above |
|
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
@ -61,13 +54,6 @@ This project uses the following commit message format:
|
|||||||
(feature): add support for custom JWT claims
|
(feature): add support for custom JWT claims
|
||||||
(bugfix): fix multithreading issue in file logger
|
(bugfix): fix multithreading issue in file logger
|
||||||
(refactor): simplify expression extension methods
|
(refactor): simplify expression extension methods
|
||||||
(perf): reduce allocations in Base32 encoder
|
|
||||||
(test): add coverage for IQueryable predicate composition
|
|
||||||
(docs): clarify release workflow prerequisites
|
|
||||||
(build): update package metadata in MaksIT.Core.csproj
|
|
||||||
(ci): update GitHub Actions workflow for .NET 10
|
|
||||||
(style): normalize using directives in extension tests
|
|
||||||
(revert): revert breaking change in network connection handling
|
|
||||||
(chore): update copyright year to 2026
|
(chore): update copyright year to 2026
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="150" height="20" role="img" aria-label="Branch Coverage: 50.3%">
|
<svg xmlns="http://www.w3.org/2000/svg" width="150" height="20" role="img" aria-label="Branch Coverage: 49.6%">
|
||||||
<title>Branch Coverage: 50.3%</title>
|
<title>Branch Coverage: 49.6%</title>
|
||||||
<linearGradient id="s" x2="0" y2="100%">
|
<linearGradient id="s" x2="0" y2="100%">
|
||||||
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
|
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
|
||||||
<stop offset="1" stop-opacity=".1"/>
|
<stop offset="1" stop-opacity=".1"/>
|
||||||
@ -15,7 +15,7 @@
|
|||||||
<g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="11">
|
<g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="11">
|
||||||
<text aria-hidden="true" x="53.75" y="15" fill="#010101" fill-opacity=".3">Branch Coverage</text>
|
<text aria-hidden="true" x="53.75" y="15" fill="#010101" fill-opacity=".3">Branch Coverage</text>
|
||||||
<text x="53.75" y="14" fill="#fff">Branch Coverage</text>
|
<text x="53.75" y="14" fill="#fff">Branch Coverage</text>
|
||||||
<text aria-hidden="true" x="128.75" y="15" fill="#010101" fill-opacity=".3">50.3%</text>
|
<text aria-hidden="true" x="128.75" y="15" fill="#010101" fill-opacity=".3">49.6%</text>
|
||||||
<text x="128.75" y="14" fill="#fff">50.3%</text>
|
<text x="128.75" y="14" fill="#fff">49.6%</text>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
@ -1,21 +1,21 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="137" height="20" role="img" aria-label="Line Coverage: 60.1%">
|
<svg xmlns="http://www.w3.org/2000/svg" width="134.5" height="20" role="img" aria-label="Line Coverage: 60%">
|
||||||
<title>Line Coverage: 60.1%</title>
|
<title>Line Coverage: 60%</title>
|
||||||
<linearGradient id="s" x2="0" y2="100%">
|
<linearGradient id="s" x2="0" y2="100%">
|
||||||
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
|
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
|
||||||
<stop offset="1" stop-opacity=".1"/>
|
<stop offset="1" stop-opacity=".1"/>
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
<clipPath id="r">
|
<clipPath id="r">
|
||||||
<rect width="137" height="20" rx="3" fill="#fff"/>
|
<rect width="134.5" height="20" rx="3" fill="#fff"/>
|
||||||
</clipPath>
|
</clipPath>
|
||||||
<g clip-path="url(#r)">
|
<g clip-path="url(#r)">
|
||||||
<rect width="94.5" height="20" fill="#555"/>
|
<rect width="94.5" height="20" fill="#555"/>
|
||||||
<rect x="94.5" width="42.5" height="20" fill="#97ca00"/>
|
<rect x="94.5" width="40" height="20" fill="#97ca00"/>
|
||||||
<rect width="137" height="20" fill="url(#s)"/>
|
<rect width="134.5" height="20" fill="url(#s)"/>
|
||||||
</g>
|
</g>
|
||||||
<g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="11">
|
<g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="11">
|
||||||
<text aria-hidden="true" x="47.25" y="15" fill="#010101" fill-opacity=".3">Line Coverage</text>
|
<text aria-hidden="true" x="47.25" y="15" fill="#010101" fill-opacity=".3">Line Coverage</text>
|
||||||
<text x="47.25" y="14" fill="#fff">Line Coverage</text>
|
<text x="47.25" y="14" fill="#fff">Line Coverage</text>
|
||||||
<text aria-hidden="true" x="115.75" y="15" fill="#010101" fill-opacity=".3">60.1%</text>
|
<text aria-hidden="true" x="114.5" y="15" fill="#010101" fill-opacity=".3">60%</text>
|
||||||
<text x="115.75" y="14" fill="#fff">60.1%</text>
|
<text x="114.5" y="14" fill="#fff">60%</text>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
@ -77,10 +77,4 @@ public class ExceptionExtensionsTests {
|
|||||||
Assert.Single(messages);
|
Assert.Single(messages);
|
||||||
Assert.Equal("", messages[0]);
|
Assert.Equal("", messages[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void ExtractMessages_WhenNull_ThrowsArgumentNullException() {
|
|
||||||
Exception? exception = null;
|
|
||||||
Assert.Throws<ArgumentNullException>(() => exception!.ExtractMessages());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
using System.Linq;
|
using System.Linq.Expressions;
|
||||||
using System.Linq.Expressions;
|
|
||||||
|
|
||||||
using MaksIT.Core.Extensions;
|
using MaksIT.Core.Extensions;
|
||||||
|
|
||||||
@ -24,28 +23,6 @@ public class ExpressionExtensionsTests {
|
|||||||
Assert.False(compiledPredicate(new TestEntity { Age = 20, Name = "Bob" }));
|
Assert.False(compiledPredicate(new TestEntity { Age = 20, Name = "Bob" }));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Ensures AndAlso produces an expression that works with IQueryable.Where (as used by EF Core).
|
|
||||||
/// </summary>
|
|
||||||
[Fact]
|
|
||||||
public void AndAlso_ShouldWorkWithIQueryableWhere() {
|
|
||||||
var source = new List<TestEntity> {
|
|
||||||
new() { Age = 20, Name = "Alice" },
|
|
||||||
new() { Age = 17, Name = "Alice" },
|
|
||||||
new() { Age = 20, Name = "Bob" },
|
|
||||||
new() { Age = 25, Name = "Amy" }
|
|
||||||
};
|
|
||||||
Expression<Func<TestEntity, bool>> first = x => x.Age > 18;
|
|
||||||
Expression<Func<TestEntity, bool>> second = x => (x.Name ?? "").StartsWith("A");
|
|
||||||
var combined = first.AndAlso(second);
|
|
||||||
|
|
||||||
var result = source.AsQueryable().Where(combined).ToList();
|
|
||||||
|
|
||||||
Assert.Equal(2, result.Count);
|
|
||||||
Assert.Contains(result, e => e.Name == "Alice" && e.Age == 20);
|
|
||||||
Assert.Contains(result, e => e.Name == "Amy" && e.Age == 25);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void OrElse_ShouldCombineTwoPredicatesWithOrCondition() {
|
public void OrElse_ShouldCombineTwoPredicatesWithOrCondition() {
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
@ -235,13 +235,6 @@ namespace MaksIT.Core.Tests.Extensions {
|
|||||||
Assert.Same(s, s2);
|
Assert.Same(s, s2);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void DeepClone_WhenNull_ReturnsDefault() {
|
|
||||||
Person? source = null;
|
|
||||||
var result = source.DeepClone();
|
|
||||||
Assert.Null(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void DeepEqual_ShouldReturnTrue_ForEqualGraphs() {
|
public void DeepEqual_ShouldReturnTrue_ForEqualGraphs() {
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
namespace MaksIT.Core.Extensions;
|
namespace MaksIT.Core.Extensions;
|
||||||
|
|
||||||
public static class ExceptionExtensions {
|
public static class ExceptionExtensions {
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -7,7 +7,6 @@ public static class ExceptionExtensions {
|
|||||||
/// <param name="exception">The exception to extract messages from.</param>
|
/// <param name="exception">The exception to extract messages from.</param>
|
||||||
/// <returns>A list of exception messages.</returns>
|
/// <returns>A list of exception messages.</returns>
|
||||||
public static List<string> ExtractMessages(this Exception exception) {
|
public static List<string> ExtractMessages(this Exception exception) {
|
||||||
ArgumentNullException.ThrowIfNull(exception);
|
|
||||||
var messages = new List<string>();
|
var messages = new List<string>();
|
||||||
var current = exception;
|
var current = exception;
|
||||||
|
|
||||||
|
|||||||
@ -1,19 +1,10 @@
|
|||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
|
|
||||||
|
|
||||||
namespace MaksIT.Core.Extensions;
|
namespace MaksIT.Core.Extensions;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Extension methods for combining and negating expression predicates.
|
|
||||||
/// AndAlso and OrElse use parameter replacement (no Expression.Invoke), so the result
|
|
||||||
/// is safe for use with IQueryable and EF Core (translatable to SQL).
|
|
||||||
/// </summary>
|
|
||||||
public static class ExpressionExtensions {
|
public static class ExpressionExtensions {
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Combines two predicates with AND. Uses a single parameter and Expression.AndAlso;
|
|
||||||
/// safe for IQueryable/EF Core (no Invoke).
|
|
||||||
/// </summary>
|
|
||||||
public static Expression<Func<T, bool>> AndAlso<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second) {
|
public static Expression<Func<T, bool>> AndAlso<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second) {
|
||||||
ArgumentNullException.ThrowIfNull(first);
|
ArgumentNullException.ThrowIfNull(first);
|
||||||
ArgumentNullException.ThrowIfNull(second);
|
ArgumentNullException.ThrowIfNull(second);
|
||||||
@ -26,10 +17,6 @@ public static class ExpressionExtensions {
|
|||||||
return Expression.Lambda<Func<T, bool>>(combinedBody, parameter);
|
return Expression.Lambda<Func<T, bool>>(combinedBody, parameter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Combines two predicates with OR. Uses a single parameter and Expression.OrElse;
|
|
||||||
/// safe for IQueryable/EF Core (no Invoke).
|
|
||||||
/// </summary>
|
|
||||||
public static Expression<Func<T, bool>> OrElse<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second) {
|
public static Expression<Func<T, bool>> OrElse<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second) {
|
||||||
ArgumentNullException.ThrowIfNull(first);
|
ArgumentNullException.ThrowIfNull(first);
|
||||||
ArgumentNullException.ThrowIfNull(second);
|
ArgumentNullException.ThrowIfNull(second);
|
||||||
@ -43,7 +30,7 @@ public static class ExpressionExtensions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Expression<Func<T, bool>> Not<T>(this Expression<Func<T, bool>> expression) {
|
public static Expression<Func<T, bool>> Not<T>(this Expression<Func<T, bool>> expression) {
|
||||||
ArgumentNullException.ThrowIfNull(expression);
|
if (expression == null) throw new ArgumentNullException(nameof(expression));
|
||||||
|
|
||||||
var parameter = expression.Parameters[0];
|
var parameter = expression.Parameters[0];
|
||||||
var body = Expression.Not(expression.Body);
|
var body = Expression.Not(expression.Body);
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
@ -41,7 +42,6 @@ public static class ObjectExtensions {
|
|||||||
/// Creates a deep clone of the object, preserving reference identity and supporting cycles.
|
/// Creates a deep clone of the object, preserving reference identity and supporting cycles.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static T DeepClone<T>(this T source) {
|
public static T DeepClone<T>(this T source) {
|
||||||
if (source is null) return default!;
|
|
||||||
return (T)DeepCloneInternal(source, new Dictionary<object, object>(ReferenceEqualityComparer.Instance));
|
return (T)DeepCloneInternal(source, new Dictionary<object, object>(ReferenceEqualityComparer.Instance));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,9 +49,7 @@ public static class ObjectExtensions {
|
|||||||
/// Deeply compares two objects for structural equality (fields, including private ones).
|
/// Deeply compares two objects for structural equality (fields, including private ones).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool DeepEqual<T>(this T a, T b) {
|
public static bool DeepEqual<T>(this T a, T b) {
|
||||||
if (a is null && b is null) return true;
|
return DeepEqualInternal(a, b, new HashSet<(object, object)>(ReferencePairComparer.Instance));
|
||||||
if (a is null || b is null) return false;
|
|
||||||
return DeepEqualInternal(a!, b!, new HashSet<(object, object)>(ReferencePairComparer.Instance));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -86,7 +84,7 @@ public static class ObjectExtensions {
|
|||||||
return CloneStruct(source, type, visited);
|
return CloneStruct(source, type, visited);
|
||||||
|
|
||||||
// Reference type: allocate uninitialized object, then copy fields
|
// Reference type: allocate uninitialized object, then copy fields
|
||||||
var clone = RuntimeHelpers.GetUninitializedObject(type);
|
var clone = FormatterServices.GetUninitializedObject(type);
|
||||||
visited[source] = clone;
|
visited[source] = clone;
|
||||||
CopyAllFields(source, clone, type, visited);
|
CopyAllFields(source, clone, type, visited);
|
||||||
return clone;
|
return clone;
|
||||||
@ -251,8 +249,8 @@ public static class ObjectExtensions {
|
|||||||
|
|
||||||
private sealed class ReferenceEqualityComparer : IEqualityComparer<object> {
|
private sealed class ReferenceEqualityComparer : IEqualityComparer<object> {
|
||||||
public static readonly ReferenceEqualityComparer Instance = new ReferenceEqualityComparer();
|
public static readonly ReferenceEqualityComparer Instance = new ReferenceEqualityComparer();
|
||||||
public new bool Equals(object? x, object? y) => ReferenceEquals(x, y);
|
public new bool Equals(object x, object y) => ReferenceEquals(x, y);
|
||||||
public int GetHashCode(object? obj) => RuntimeHelpers.GetHashCode(obj!);
|
public int GetHashCode(object obj) => RuntimeHelpers.GetHashCode(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class ReferencePairComparer : IEqualityComparer<(object, object)> {
|
private sealed class ReferencePairComparer : IEqualityComparer<(object, object)> {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
@ -246,8 +246,8 @@ namespace MaksIT.Core.Extensions {
|
|||||||
public static string ToKebabCase(this string input) => input.ToCase(StringCaseStyle.KebabCase);
|
public static string ToKebabCase(this string input) => input.ToCase(StringCaseStyle.KebabCase);
|
||||||
|
|
||||||
public static DataTable CSVToDataTable(this string filePath) {
|
public static DataTable CSVToDataTable(this string filePath) {
|
||||||
ArgumentNullException.ThrowIfNull(filePath);
|
if (string.IsNullOrEmpty(filePath))
|
||||||
if (string.IsNullOrWhiteSpace(filePath)) throw new ArgumentException("File path cannot be empty.", nameof(filePath));
|
throw new ArgumentNullException(nameof(filePath));
|
||||||
|
|
||||||
using var sr = new StreamReader(filePath);
|
using var sr = new StreamReader(filePath);
|
||||||
|
|
||||||
|
|||||||
@ -78,8 +78,7 @@ public abstract class BaseFileLogger : ILogger, IDisposable {
|
|||||||
|
|
||||||
foreach (var logFile in logFiles) {
|
foreach (var logFile in logFiles) {
|
||||||
var fileName = Path.GetFileNameWithoutExtension(logFile);
|
var fileName = Path.GetFileNameWithoutExtension(logFile);
|
||||||
if (fileName.Length >= 4 && fileName.StartsWith("log_", StringComparison.Ordinal) &&
|
if (DateTime.TryParseExact(fileName.Substring(4), "yyyy-MM-dd", null, System.Globalization.DateTimeStyles.None, out var logDate)) {
|
||||||
DateTime.TryParseExact(fileName.Substring(4), "yyyy-MM-dd", null, System.Globalization.DateTimeStyles.None, out var logDate)) {
|
|
||||||
if (logDate < expirationDate) {
|
if (logDate < expirationDate) {
|
||||||
File.Delete(logFile);
|
File.Delete(logFile);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,8 +8,7 @@ public class FileLoggerProvider : ILoggerProvider {
|
|||||||
private readonly TimeSpan _retentionPeriod;
|
private readonly TimeSpan _retentionPeriod;
|
||||||
|
|
||||||
public FileLoggerProvider(string folderPath, TimeSpan? retentionPeriod = null) {
|
public FileLoggerProvider(string folderPath, TimeSpan? retentionPeriod = null) {
|
||||||
ArgumentNullException.ThrowIfNull(folderPath);
|
_folderPath = folderPath ?? throw new ArgumentNullException(nameof(folderPath));
|
||||||
_folderPath = folderPath;
|
|
||||||
_retentionPeriod = retentionPeriod ?? TimeSpan.FromDays(7); // Default retention period is 7 days
|
_retentionPeriod = retentionPeriod ?? TimeSpan.FromDays(7); // Default retention period is 7 days
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,8 +8,7 @@ public class JsonFileLoggerProvider : ILoggerProvider {
|
|||||||
private readonly TimeSpan _retentionPeriod;
|
private readonly TimeSpan _retentionPeriod;
|
||||||
|
|
||||||
public JsonFileLoggerProvider(string folderPath, TimeSpan? retentionPeriod = null) {
|
public JsonFileLoggerProvider(string folderPath, TimeSpan? retentionPeriod = null) {
|
||||||
ArgumentNullException.ThrowIfNull(folderPath);
|
_folderPath = folderPath ?? throw new ArgumentNullException(nameof(folderPath));
|
||||||
_folderPath = folderPath;
|
|
||||||
_retentionPeriod = retentionPeriod ?? TimeSpan.FromDays(7); // Default retention period is 7 days
|
_retentionPeriod = retentionPeriod ?? TimeSpan.FromDays(7); // Default retention period is 7 days
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
<!-- NuGet package metadata -->
|
<!-- NuGet package metadata -->
|
||||||
<PackageId>MaksIT.Core</PackageId>
|
<PackageId>MaksIT.Core</PackageId>
|
||||||
<Version>1.6.5</Version>
|
<Version>1.6.4</Version>
|
||||||
<Authors>Maksym Sadovnychyy</Authors>
|
<Authors>Maksym Sadovnychyy</Authors>
|
||||||
<Company>MAKS-IT</Company>
|
<Company>MAKS-IT</Company>
|
||||||
<Product>MaksIT.Core</Product>
|
<Product>MaksIT.Core</Product>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@ -27,9 +27,9 @@ public class NetworkConnection : IDisposable {
|
|||||||
throw new PlatformNotSupportedException("NetworkConnection is only supported on Windows.");
|
throw new PlatformNotSupportedException("NetworkConnection is only supported on Windows.");
|
||||||
}
|
}
|
||||||
|
|
||||||
ArgumentNullException.ThrowIfNull(logger);
|
if (logger == null) throw new ArgumentNullException(nameof(logger));
|
||||||
ArgumentNullException.ThrowIfNull(networkName);
|
if (networkName == null) throw new ArgumentNullException(nameof(networkName));
|
||||||
ArgumentNullException.ThrowIfNull(credentials);
|
if (credentials == null) throw new ArgumentNullException(nameof(credentials));
|
||||||
|
|
||||||
var netResource = new NetResource {
|
var netResource = new NetResource {
|
||||||
Scope = ResourceScope.GlobalNetwork,
|
Scope = ResourceScope.GlobalNetwork,
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
|
||||||
@ -14,8 +14,9 @@ public static class Base32Encoder {
|
|||||||
[NotNullWhen(false)] out string? errorMessage
|
[NotNullWhen(false)] out string? errorMessage
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
ArgumentNullException.ThrowIfNull(data);
|
if (data == null || data.Length == 0) {
|
||||||
if (data.Length == 0) throw new ArgumentException("Data cannot be empty.", nameof(data));
|
throw new ArgumentNullException(nameof(data));
|
||||||
|
}
|
||||||
|
|
||||||
var result = new StringBuilder();
|
var result = new StringBuilder();
|
||||||
int buffer = data[0];
|
int buffer = data[0];
|
||||||
@ -65,8 +66,9 @@ public static class Base32Encoder {
|
|||||||
[NotNullWhen(false)] out string? errorMessage
|
[NotNullWhen(false)] out string? errorMessage
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
ArgumentNullException.ThrowIfNull(base32);
|
if (string.IsNullOrEmpty(base32)) {
|
||||||
if (string.IsNullOrWhiteSpace(base32)) throw new ArgumentException("Base32 string cannot be empty.", nameof(base32));
|
throw new ArgumentNullException(nameof(base32));
|
||||||
|
}
|
||||||
|
|
||||||
base32 = base32.TrimEnd(PaddingChar.ToCharArray());
|
base32 = base32.TrimEnd(PaddingChar.ToCharArray());
|
||||||
int byteCount = base32.Length * 5 / 8;
|
int byteCount = base32.Length * 5 / 8;
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
namespace MaksIT.Core.Security;
|
namespace MaksIT.Core.Security;
|
||||||
@ -132,8 +132,8 @@ public static class TotpGenerator {
|
|||||||
recoveryCodes = new List<string>();
|
recoveryCodes = new List<string>();
|
||||||
|
|
||||||
for (int i = 0; i < defaultCodeCount; i++) {
|
for (int i = 0; i < defaultCodeCount; i++) {
|
||||||
var code = Guid.NewGuid().ToString("N")[..8]; // Generate an 8-character code
|
var code = Guid.NewGuid().ToString("N").Substring(0, 8); // Generate an 8-character code
|
||||||
var formattedCode = $"{code[..4]}-{code[4..8]}"; // Format as XXXX-XXXX
|
var formattedCode = $"{code.Substring(0, 4)}-{code.Substring(4, 4)}"; // Format as XXXX-XXXX
|
||||||
recoveryCodes.Add(formattedCode);
|
recoveryCodes.Add(formattedCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,15 +1,9 @@
|
|||||||
using System.Linq.Dynamic.Core;
|
using System.Linq.Dynamic.Core;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using MaksIT.Core.Abstractions.Webapi;
|
using MaksIT.Core.Abstractions.Webapi;
|
||||||
using MaksIT.Core.Extensions;
|
using MaksIT.Core.Extensions;
|
||||||
|
|
||||||
namespace MaksIT.Core.Webapi.Models;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Base request model for paged list operations with optional filter and sort.
|
|
||||||
/// BuildFilterExpression produces expressions suitable for IQueryable/EF Core (single parameter, translatable operations).
|
|
||||||
/// </summary>
|
|
||||||
public class PagedRequest : RequestModelBase {
|
public class PagedRequest : RequestModelBase {
|
||||||
public int PageSize { get; set; } = 100;
|
public int PageSize { get; set; } = 100;
|
||||||
public int PageNumber { get; set; } = 1;
|
public int PageNumber { get; set; } = 1;
|
||||||
@ -22,11 +16,6 @@ public class PagedRequest : RequestModelBase {
|
|||||||
return BuildFilterExpression<T>(Filters);
|
return BuildFilterExpression<T>(Filters);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Parses the filter string into an Expression<Func<T, bool>> for use with IQueryable/EF Core.
|
|
||||||
/// Uses a single parameter; avoid filter syntax that would require non-translatable operations.
|
|
||||||
/// Supported: property access, ==, !=, &&, ||, !, Contains, StartsWith, EndsWith, ToLower() for strings.
|
|
||||||
/// </summary>
|
|
||||||
public virtual Expression<Func<T, bool>> BuildFilterExpression<T>(string? filters) {
|
public virtual Expression<Func<T, bool>> BuildFilterExpression<T>(string? filters) {
|
||||||
if (string.IsNullOrWhiteSpace(filters))
|
if (string.IsNullOrWhiteSpace(filters))
|
||||||
return x => true; // Returns an expression that doesn't filter anything.
|
return x => true; // Returns an expression that doesn't filter anything.
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user