diff --git a/Deployment/mssql-migration.sql b/Deployment/mssql-migration.sql
index b931a83dd..1aa9aec1d 100644
--- a/Deployment/mssql-migration.sql
+++ b/Deployment/mssql-migration.sql
@@ -1 +1,33 @@
---
\ No newline at end of file
+-- v14.3.x - v14.4.0
+CREATE TABLE [dbo].[LoginHistory](
+ [Id] [int] IDENTITY(1,1) NOT NULL,
+ [LoginTimeUtc] [datetime] NOT NULL,
+ [LoginIp] [nvarchar](64) NULL,
+ [LoginUserAgent] [nvarchar](128) NULL,
+ [DeviceFingerprint] [nvarchar](128) NULL,
+ CONSTRAINT [PK_LoginHistory] PRIMARY KEY CLUSTERED
+(
+ [Id] ASC
+)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
+) ON [PRIMARY]
+GO
+
+DROP TABLE [LocalAccount]
+GO
+
+EXEC sys.sp_rename
+ @objname = N'Category.RouteName',
+ @newname = 'Slug',
+ @objtype = 'COLUMN'
+GO
+
+IF EXISTS (
+ SELECT 1
+ FROM sys.columns c
+ JOIN sys.objects o ON c.object_id = o.object_id
+ WHERE o.name = 'Post' AND c.name = 'InlineCss'
+)
+BEGIN
+ ALTER TABLE Post DROP COLUMN InlineCss;
+END;
+GO
\ No newline at end of file
diff --git a/README.md b/README.md
index 1489281c2..49b48648e 100644
--- a/README.md
+++ b/README.md
@@ -152,8 +152,8 @@ Open Search | Search | Supported | `/opensearch`
Pingback | Social | Supported | `/pingback`
Reader View | Reader mode | Supported | N/A
FOAF | Social | Supported | `/foaf.xml`
-RSD | Service Discovery | Supported | `/rsd` *If MetaWeblog is enabled*
-MetaWeblog | Blogging | Basic Support | `/metaweblog`
+RSD | Service Discovery | Deprecated | N/A
+MetaWeblog | Blogging | Deprecated | N/A
Dublin Core Metadata | SEO | Basic Support | N/A
BlogML | Blogging | Not planned |
APML | Social | Not planned |
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index ac92d6bf3..a14e0a905 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -3,8 +3,8 @@
Edi Wang
edi.wang
(C) 2024 edi.wang@outlook.com
- 14.3.3.0
- 14.3.3.0
- 14.3.3
+ 14.4.0.0
+ 14.4.0.0
+ 14.4.0
\ No newline at end of file
diff --git a/src/Moonglade.Auth/Account.cs b/src/Moonglade.Auth/Account.cs
deleted file mode 100644
index 9d4f95601..000000000
--- a/src/Moonglade.Auth/Account.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-using Moonglade.Data.Entities;
-
-namespace Moonglade.Auth;
-
-public class Account
-{
- public Guid Id { get; set; }
- public string Username { get; set; }
- public DateTime? LastLoginTimeUtc { get; set; }
- public string LastLoginIp { get; set; }
- public DateTime CreateTimeUtc { get; set; }
-
- public Account()
- {
-
- }
-
- public Account(LocalAccountEntity entity)
- {
- if (null == entity) return;
-
- Id = entity.Id;
- CreateTimeUtc = entity.CreateTimeUtc;
- LastLoginIp = entity.LastLoginIp.Trim();
- LastLoginTimeUtc = entity.LastLoginTimeUtc.GetValueOrDefault();
- Username = entity.Username.Trim();
- }
-}
\ No newline at end of file
diff --git a/src/Moonglade.Auth/AccountExistsQuery.cs b/src/Moonglade.Auth/AccountExistsQuery.cs
deleted file mode 100644
index a0ce6aaf6..000000000
--- a/src/Moonglade.Auth/AccountExistsQuery.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using Moonglade.Data.Entities;
-using Moonglade.Data.Infrastructure;
-
-namespace Moonglade.Auth;
-
-public record AccountExistsQuery(string Username) : IRequest;
-
-public class AccountExistsQueryHandler(IRepository repo) : IRequestHandler
-{
- public Task Handle(AccountExistsQuery request, CancellationToken ct) =>
- repo.AnyAsync(p => p.Username == request.Username.ToLower(), ct);
-}
\ No newline at end of file
diff --git a/src/Moonglade.Auth/ChangePasswordCommand.cs b/src/Moonglade.Auth/ChangePasswordCommand.cs
deleted file mode 100644
index 7e1d384cc..000000000
--- a/src/Moonglade.Auth/ChangePasswordCommand.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using Moonglade.Data.Entities;
-using Moonglade.Data.Infrastructure;
-using Moonglade.Utils;
-
-namespace Moonglade.Auth;
-
-public record ChangePasswordCommand(Guid Id, string ClearPassword) : IRequest;
-
-public class ChangePasswordCommandHandler(IRepository repo) : IRequestHandler
-{
- public async Task Handle(ChangePasswordCommand request, CancellationToken ct)
- {
- var account = await repo.GetAsync(request.Id, ct);
- if (account is null)
- {
- throw new InvalidOperationException($"LocalAccountEntity with Id '{request.Id}' not found.");
- }
-
- var newSalt = Helper.GenerateSalt();
- account.PasswordSalt = newSalt;
- account.PasswordHash = Helper.HashPassword2(request.ClearPassword, newSalt);
-
- await repo.UpdateAsync(account, ct);
- }
-}
\ No newline at end of file
diff --git a/src/Moonglade.Auth/CountAccountsQuery.cs b/src/Moonglade.Auth/CountAccountsQuery.cs
deleted file mode 100644
index a61d336cb..000000000
--- a/src/Moonglade.Auth/CountAccountsQuery.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using Moonglade.Data.Entities;
-using Moonglade.Data.Infrastructure;
-
-namespace Moonglade.Auth;
-
-public record CountAccountsQuery : IRequest;
-
-public class CountAccountsQueryHandler(IRepository repo) : IRequestHandler
-{
- public Task Handle(CountAccountsQuery request, CancellationToken ct) => repo.CountAsync(ct: ct);
-}
\ No newline at end of file
diff --git a/src/Moonglade.Auth/CreateAccountCommand.cs b/src/Moonglade.Auth/CreateAccountCommand.cs
deleted file mode 100644
index 9f71d2619..000000000
--- a/src/Moonglade.Auth/CreateAccountCommand.cs
+++ /dev/null
@@ -1,53 +0,0 @@
-using Moonglade.Data.Entities;
-using Moonglade.Data.Infrastructure;
-using Moonglade.Utils;
-using System.ComponentModel.DataAnnotations;
-
-namespace Moonglade.Auth;
-
-public class CreateAccountCommand : IRequest
-{
- [Required]
- [Display(Name = "Username")]
- [MinLength(2), MaxLength(32)]
- [RegularExpression("[a-z0-9]+")]
- public string Username { get; set; }
-
- [Required]
- [Display(Name = "Password")]
- [MinLength(8), MaxLength(32)]
- [DataType(DataType.Password)]
- [RegularExpression(@"^(?=.*[a-zA-Z])(?=.*[0-9])[A-Za-z0-9._~!@#$^&*]{8,}$")]
- public string Password { get; set; }
-}
-
-public class CreateAccountCommandHandler(IRepository repo) : IRequestHandler
-{
- public Task Handle(CreateAccountCommand request, CancellationToken ct)
- {
- if (string.IsNullOrWhiteSpace(request.Username))
- {
- throw new ArgumentNullException(nameof(request.Username), "value must not be empty.");
- }
-
- if (string.IsNullOrWhiteSpace(request.Password))
- {
- throw new ArgumentNullException(nameof(request.Password), "value must not be empty.");
- }
-
- var uid = Guid.NewGuid();
- var salt = Helper.GenerateSalt();
- var hash = Helper.HashPassword2(request.Password.Trim(), salt);
-
- var account = new LocalAccountEntity
- {
- Id = uid,
- CreateTimeUtc = DateTime.UtcNow,
- Username = request.Username.ToLower().Trim(),
- PasswordSalt = salt,
- PasswordHash = hash
- };
-
- return repo.AddAsync(account, ct);
- }
-}
\ No newline at end of file
diff --git a/src/Moonglade.Auth/DeleteAccountCommand.cs b/src/Moonglade.Auth/DeleteAccountCommand.cs
deleted file mode 100644
index a8dd6a888..000000000
--- a/src/Moonglade.Auth/DeleteAccountCommand.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using Moonglade.Data.Entities;
-using Moonglade.Data.Infrastructure;
-
-namespace Moonglade.Auth;
-
-public record DeleteAccountCommand(Guid Id) : IRequest;
-
-public class DeleteAccountCommandHandler(IRepository repo) : IRequestHandler
-{
- public async Task Handle(DeleteAccountCommand request, CancellationToken ct)
- {
- var account = await repo.GetAsync(request.Id, ct);
- if (account != null) await repo.DeleteAsync(request.Id, ct);
- }
-}
\ No newline at end of file
diff --git a/src/Moonglade.Auth/GetAccountQuery.cs b/src/Moonglade.Auth/GetAccountQuery.cs
deleted file mode 100644
index ae83c1bae..000000000
--- a/src/Moonglade.Auth/GetAccountQuery.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using Moonglade.Data.Entities;
-using Moonglade.Data.Infrastructure;
-
-namespace Moonglade.Auth;
-
-public record GetAccountQuery(Guid Id) : IRequest;
-
-public class GetAccountQueryHandler(IRepository repo) : IRequestHandler
-{
- public async Task Handle(GetAccountQuery request, CancellationToken ct)
- {
- var entity = await repo.GetAsync(request.Id, ct);
- var item = new Account(entity);
- return item;
- }
-}
\ No newline at end of file
diff --git a/src/Moonglade.Auth/GetAccountsQuery.cs b/src/Moonglade.Auth/GetAccountsQuery.cs
deleted file mode 100644
index 4ac6961e6..000000000
--- a/src/Moonglade.Auth/GetAccountsQuery.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using Moonglade.Data.Entities;
-using Moonglade.Data.Infrastructure;
-
-namespace Moonglade.Auth;
-
-public record GetAccountsQuery : IRequest>;
-
-public class GetAccountsQueryHandler(IRepository repo) : IRequestHandler>
-{
- public Task> Handle(GetAccountsQuery request, CancellationToken ct)
- {
- return repo.SelectAsync(p => new Account
- {
- Id = p.Id,
- CreateTimeUtc = p.CreateTimeUtc,
- LastLoginIp = p.LastLoginIp,
- LastLoginTimeUtc = p.LastLoginTimeUtc,
- Username = p.Username
- }, ct);
- }
-}
\ No newline at end of file
diff --git a/src/Moonglade.Auth/GetLoginHistoryQuery.cs b/src/Moonglade.Auth/GetLoginHistoryQuery.cs
new file mode 100644
index 000000000..3fe788932
--- /dev/null
+++ b/src/Moonglade.Auth/GetLoginHistoryQuery.cs
@@ -0,0 +1,16 @@
+using Moonglade.Data;
+using Moonglade.Data.Entities;
+using Moonglade.Data.Specifications;
+
+namespace Moonglade.Auth;
+
+public record GetLoginHistoryQuery : IRequest>;
+
+public class GetLoginHistoryQueryHandler(MoongladeRepository repo) : IRequestHandler>
+{
+ public async Task> Handle(GetLoginHistoryQuery request, CancellationToken ct)
+ {
+ var history = await repo.ListAsync(new LoginHistorySpec(10), ct);
+ return history;
+ }
+}
\ No newline at end of file
diff --git a/src/Moonglade.Auth/LogSuccessLoginCommand.cs b/src/Moonglade.Auth/LogSuccessLoginCommand.cs
index 463cef937..51146d081 100644
--- a/src/Moonglade.Auth/LogSuccessLoginCommand.cs
+++ b/src/Moonglade.Auth/LogSuccessLoginCommand.cs
@@ -1,22 +1,22 @@
-using Moonglade.Data.Entities;
-using Moonglade.Data.Infrastructure;
+using Moonglade.Data;
+using Moonglade.Data.Entities;
namespace Moonglade.Auth;
-public record LogSuccessLoginCommand(Guid Id, string IpAddress) : IRequest;
+public record LogSuccessLoginCommand(string IpAddress, string UserAgent, string DeviceFingerprint) : IRequest;
-public class LogSuccessLoginCommandHandler(IRepository repo) : IRequestHandler
+public class LogSuccessLoginCommandHandler(MoongladeRepository repo) : IRequestHandler
{
public async Task Handle(LogSuccessLoginCommand request, CancellationToken ct)
{
- var (id, ipAddress) = request;
-
- var entity = await repo.GetAsync(id, ct);
- if (entity is not null)
+ var entity = new LoginHistoryEntity
{
- entity.LastLoginIp = ipAddress.Trim();
- entity.LastLoginTimeUtc = DateTime.UtcNow;
- await repo.UpdateAsync(entity, ct);
- }
+ LoginIp = request.IpAddress.Trim(),
+ LoginTimeUtc = DateTime.UtcNow,
+ LoginUserAgent = request.UserAgent.Trim(),
+ DeviceFingerprint = request.DeviceFingerprint.Trim()
+ };
+
+ await repo.AddAsync(entity, ct);
}
}
\ No newline at end of file
diff --git a/src/Moonglade.Auth/Moonglade.Auth.csproj b/src/Moonglade.Auth/Moonglade.Auth.csproj
index a8f928369..e89187834 100644
--- a/src/Moonglade.Auth/Moonglade.Auth.csproj
+++ b/src/Moonglade.Auth/Moonglade.Auth.csproj
@@ -16,9 +16,10 @@
-
+
+
diff --git a/src/Moonglade.Auth/UpdateLocalAccountPasswordRequest.cs b/src/Moonglade.Auth/UpdateLocalAccountPasswordRequest.cs
new file mode 100644
index 000000000..736a712ab
--- /dev/null
+++ b/src/Moonglade.Auth/UpdateLocalAccountPasswordRequest.cs
@@ -0,0 +1,18 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Moonglade.Auth;
+
+public class UpdateLocalAccountPasswordRequest
+{
+ [Required]
+ [RegularExpression("^[A-Za-z0-9]{3,16}$")]
+ public string NewUsername { get; set; }
+
+ [Required]
+ [RegularExpression("^(?=.*[a-zA-Z])(?=.*[0-9])[A-Za-z0-9._~!@#$^&*]{8,}$")]
+ public string OldPassword { get; set; }
+
+ [Required]
+ [RegularExpression("^(?=.*[a-zA-Z])(?=.*[0-9])[A-Za-z0-9._~!@#$^&*]{8,}$")]
+ public string NewPassword { get; set; }
+}
\ No newline at end of file
diff --git a/src/Moonglade.Auth/ValidateLoginCommand.cs b/src/Moonglade.Auth/ValidateLoginCommand.cs
index 8e2464994..19317237c 100644
--- a/src/Moonglade.Auth/ValidateLoginCommand.cs
+++ b/src/Moonglade.Auth/ValidateLoginCommand.cs
@@ -1,34 +1,21 @@
-using Moonglade.Data.Entities;
-using Moonglade.Data.Infrastructure;
+using Moonglade.Configuration;
using Moonglade.Utils;
namespace Moonglade.Auth;
-public record ValidateLoginCommand(string Username, string InputPassword) : IRequest;
+public record ValidateLoginCommand(string Username, string InputPassword) : IRequest;
-public class ValidateLoginCommandHandler(IRepository repo) : IRequestHandler
+public class ValidateLoginCommandHandler(IBlogConfig config) : IRequestHandler
{
- public async Task Handle(ValidateLoginCommand request, CancellationToken ct)
+ public Task Handle(ValidateLoginCommand request, CancellationToken ct)
{
- var account = await repo.GetAsync(p => p.Username == request.Username);
- if (account is null) return Guid.Empty;
+ var account = config.LocalAccountSettings;
- var valid = account.PasswordHash == (string.IsNullOrWhiteSpace(account.PasswordSalt)
- ? Helper.HashPassword(request.InputPassword.Trim())
- : Helper.HashPassword2(request.InputPassword.Trim(), account.PasswordSalt));
+ if (account is null) return Task.FromResult(false);
+ if (account.Username != request.Username) return Task.FromResult(false);
- // migrate old account to salt
- if (valid && string.IsNullOrWhiteSpace(account.PasswordSalt))
- {
- var salt = Helper.GenerateSalt();
- var newHash = Helper.HashPassword2(request.InputPassword.Trim(), salt);
+ var valid = account.PasswordHash == Helper.HashPassword(request.InputPassword.Trim(), account.PasswordSalt);
- account.PasswordSalt = salt;
- account.PasswordHash = newHash;
-
- await repo.UpdateAsync(account, ct);
- }
-
- return valid ? account.Id : Guid.Empty;
+ return Task.FromResult(valid);
}
}
\ No newline at end of file
diff --git a/src/Moonglade.Comments/Comment.cs b/src/Moonglade.Comments/Comment.cs
index feaf008c5..06ef738f2 100644
--- a/src/Moonglade.Comments/Comment.cs
+++ b/src/Moonglade.Comments/Comment.cs
@@ -13,7 +13,7 @@ public class Comment
public string CommentContent { get; set; }
- public IReadOnlyList CommentReplies { get; set; }
+ public List CommentReplies { get; set; }
}
public class CommentDetailedItem : Comment
diff --git a/src/Moonglade.Comments/CountCommentsQuery.cs b/src/Moonglade.Comments/CountCommentsQuery.cs
index 3b37ec06d..b18fbffdc 100644
--- a/src/Moonglade.Comments/CountCommentsQuery.cs
+++ b/src/Moonglade.Comments/CountCommentsQuery.cs
@@ -1,12 +1,12 @@
using MediatR;
+using Moonglade.Data;
using Moonglade.Data.Entities;
-using Moonglade.Data.Infrastructure;
namespace Moonglade.Comments;
public record CountCommentsQuery : IRequest;
-public class CountCommentsQueryHandler(IRepository repo) : IRequestHandler
+public class CountCommentsQueryHandler(MoongladeRepository repo) : IRequestHandler
{
- public Task Handle(CountCommentsQuery request, CancellationToken ct) => repo.CountAsync(ct: ct);
+ public Task Handle(CountCommentsQuery request, CancellationToken ct) => repo.CountAsync(ct);
}
\ No newline at end of file
diff --git a/src/Moonglade.Comments/CreateCommentCommand.cs b/src/Moonglade.Comments/CreateCommentCommand.cs
index d5d82b0b7..62dd13341 100644
--- a/src/Moonglade.Comments/CreateCommentCommand.cs
+++ b/src/Moonglade.Comments/CreateCommentCommand.cs
@@ -1,13 +1,13 @@
using MediatR;
-using Moonglade.Comments.Moderator;
+using Microsoft.Extensions.Logging;
using Moonglade.Configuration;
+using Moonglade.Data;
using Moonglade.Data.Entities;
-using Moonglade.Data.Infrastructure;
-using Moonglade.Data.Spec;
+using Moonglade.Data.Specifications;
namespace Moonglade.Comments;
-public class CreateCommentCommand(Guid postId, CommentRequest payload, string ipAddress) : IRequest<(int Status, CommentDetailedItem Item)>
+public class CreateCommentCommand(Guid postId, CommentRequest payload, string ipAddress) : IRequest
{
public Guid PostId { get; set; } = postId;
@@ -16,43 +16,21 @@ public class CreateCommentCommand(Guid postId, CommentRequest payload, string ip
public string IpAddress { get; set; } = ipAddress;
}
-public class CreateCommentCommandHandler(IBlogConfig blogConfig, IRepository postRepo, IModeratorService moderator, IRepository commentRepo) :
- IRequestHandler
+public class CreateCommentCommandHandler(
+ IBlogConfig blogConfig,
+ ILogger logger,
+ MoongladeRepository postRepo,
+ MoongladeRepository commentRepo) : IRequestHandler
{
- public async Task<(int Status, CommentDetailedItem Item)> Handle(CreateCommentCommand request, CancellationToken ct)
+ public async Task Handle(CreateCommentCommand request, CancellationToken ct)
{
- if (blogConfig.ContentSettings.EnableWordFilter)
- {
- switch (blogConfig.ContentSettings.WordFilterMode)
- {
- case WordFilterMode.Mask:
- request.Payload.Username = await moderator.Mask(request.Payload.Username);
- request.Payload.Content = await moderator.Mask(request.Payload.Content);
- break;
- case WordFilterMode.Block:
- if (await moderator.Detect(request.Payload.Username, request.Payload.Content))
- {
- await Task.CompletedTask;
- return (-1, null);
- }
- break;
- }
- }
-
- var spec = new PostSpec(request.PostId, false);
- var postInfo = await postRepo.FirstOrDefaultAsync(spec, p => new
- {
- p.Title,
- p.PubDateUtc
- });
+ var spec = new PostByIdForTitleDateSpec(request.PostId);
+ var postInfo = await postRepo.FirstOrDefaultAsync(spec, ct);
if (blogConfig.ContentSettings.CloseCommentAfterDays > 0)
{
var days = DateTime.UtcNow.Date.Subtract(postInfo.PubDateUtc.GetValueOrDefault()).Days;
- if (days > blogConfig.ContentSettings.CloseCommentAfterDays)
- {
- return (-2, null);
- }
+ if (days > blogConfig.ContentSettings.CloseCommentAfterDays) return null;
}
var model = new CommentEntity
@@ -81,6 +59,7 @@ public class CreateCommentCommandHandler(IBlogConfig blogConfig, IRepository commentRepo, IRepository commentReplyRepo) : IRequestHandler
+public class DeleteCommentsCommandHandler(
+ MoongladeRepository commentRepo,
+ ILogger logger) : IRequestHandler
{
public async Task Handle(DeleteCommentsCommand request, CancellationToken ct)
{
- var spec = new CommentSpec(request.Ids);
- var comments = await commentRepo.ListAsync(spec);
+ var spec = new CommentByIdsSepc(request.Ids);
+ var comments = await commentRepo.ListAsync(spec, ct);
foreach (var cmt in comments)
{
- // 1. Delete all replies
- var cReplies = await commentReplyRepo.ListAsync(new CommentReplySpec(cmt.Id));
- if (cReplies.Any())
- {
- await commentReplyRepo.DeleteAsync(cReplies, ct);
- }
-
- // 2. Delete comment itself
+ cmt.Replies.Clear();
await commentRepo.DeleteAsync(cmt, ct);
}
+
+ logger.LogInformation("Deleted {Count} comment(s)", comments.Count);
}
}
\ No newline at end of file
diff --git a/src/Moonglade.Comments/GetApprovedCommentsQuery.cs b/src/Moonglade.Comments/GetApprovedCommentsQuery.cs
index 80c7b2f58..a4678ca31 100644
--- a/src/Moonglade.Comments/GetApprovedCommentsQuery.cs
+++ b/src/Moonglade.Comments/GetApprovedCommentsQuery.cs
@@ -1,17 +1,17 @@
using MediatR;
+using Moonglade.Data;
using Moonglade.Data.Entities;
-using Moonglade.Data.Infrastructure;
-using Moonglade.Data.Spec;
+using Moonglade.Data.Specifications;
namespace Moonglade.Comments;
-public record GetApprovedCommentsQuery(Guid PostId) : IRequest>;
+public record GetApprovedCommentsQuery(Guid PostId) : IRequest>;
-public class GetApprovedCommentsQueryHandler(IRepository repo) : IRequestHandler>
+public class GetApprovedCommentsQueryHandler(MoongladeRepository repo) : IRequestHandler>
{
- public Task> Handle(GetApprovedCommentsQuery request, CancellationToken ct)
+ public Task> Handle(GetApprovedCommentsQuery request, CancellationToken ct)
{
- return repo.SelectAsync(new CommentSpec(request.PostId), c => new Comment
+ return repo.SelectAsync(new CommentWithRepliesSpec(request.PostId), c => new Comment
{
CommentContent = c.CommentContent,
CreateTimeUtc = c.CreateTimeUtc,
diff --git a/src/Moonglade.Comments/GetCommentsQuery.cs b/src/Moonglade.Comments/GetCommentsQuery.cs
index 1b191ed0a..ee3c3ce19 100644
--- a/src/Moonglade.Comments/GetCommentsQuery.cs
+++ b/src/Moonglade.Comments/GetCommentsQuery.cs
@@ -1,17 +1,17 @@
using MediatR;
+using Moonglade.Data;
using Moonglade.Data.Entities;
-using Moonglade.Data.Infrastructure;
-using Moonglade.Data.Spec;
+using Moonglade.Data.Specifications;
namespace Moonglade.Comments;
-public record GetCommentsQuery(int PageSize, int PageIndex) : IRequest>;
+public record GetCommentsQuery(int PageSize, int PageIndex) : IRequest>;
-public class GetCommentsQueryHandler(IRepository repo) : IRequestHandler>
+public class GetCommentsQueryHandler(MoongladeRepository repo) : IRequestHandler>
{
- public Task> Handle(GetCommentsQuery request, CancellationToken ct)
+ public Task> Handle(GetCommentsQuery request, CancellationToken ct)
{
- var spec = new CommentSpec(request.PageSize, request.PageIndex);
+ var spec = new CommentPagingSepc(request.PageSize, request.PageIndex);
var comments = repo.SelectAsync(spec, CommentDetailedItem.EntitySelector, ct);
return comments;
diff --git a/src/Moonglade.Comments/Moderator/ModeratorService.cs b/src/Moonglade.Comments/Moderator/ModeratorService.cs
index d6555fb10..095714cd3 100644
--- a/src/Moonglade.Comments/Moderator/ModeratorService.cs
+++ b/src/Moonglade.Comments/Moderator/ModeratorService.cs
@@ -54,14 +54,14 @@ public async Task Mask(string input)
var payload = new Payload
{
OriginAspNetRequestId = _httpContextAccessor.HttpContext?.TraceIdentifier,
- Contents = new[]
- {
+ Contents =
+ [
new Content
{
Id = "0",
RawText = input
}
- }
+ ]
};
var response = await _httpClient.PostAsync(
diff --git a/src/Moonglade.Comments/ReplyCommentCommand.cs b/src/Moonglade.Comments/ReplyCommentCommand.cs
index f13717ada..21ca456f8 100644
--- a/src/Moonglade.Comments/ReplyCommentCommand.cs
+++ b/src/Moonglade.Comments/ReplyCommentCommand.cs
@@ -1,17 +1,21 @@
using MediatR;
+using Microsoft.Extensions.Logging;
+using Moonglade.Data;
using Moonglade.Data.Entities;
-using Moonglade.Data.Infrastructure;
using Moonglade.Utils;
namespace Moonglade.Comments;
public record ReplyCommentCommand(Guid CommentId, string ReplyContent) : IRequest;
-public class ReplyCommentCommandHandler(IRepository commentRepo, IRepository commentReplyRepo) : IRequestHandler
+public class ReplyCommentCommandHandler(
+ ILogger logger,
+ MoongladeRepository commentRepo,
+ MoongladeRepository commentReplyRepo) : IRequestHandler
{
public async Task Handle(ReplyCommentCommand request, CancellationToken ct)
{
- var cmt = await commentRepo.GetAsync(request.CommentId, ct);
+ var cmt = await commentRepo.GetByIdAsync(request.CommentId, ct);
if (cmt is null) throw new InvalidOperationException($"Comment {request.CommentId} is not found.");
var id = Guid.NewGuid();
@@ -40,6 +44,7 @@ public async Task Handle(ReplyCommentCommand request, Cancellation
Title = cmt.Post.Title
};
+ logger.LogInformation("Replied comment '{CommentId}' with reply '{ReplyId}'", request.CommentId, id);
return reply;
}
}
\ No newline at end of file
diff --git a/src/Moonglade.Comments/ToggleApprovalCommand.cs b/src/Moonglade.Comments/ToggleApprovalCommand.cs
index 1f408e004..3400046b2 100644
--- a/src/Moonglade.Comments/ToggleApprovalCommand.cs
+++ b/src/Moonglade.Comments/ToggleApprovalCommand.cs
@@ -1,22 +1,27 @@
using MediatR;
+using Microsoft.Extensions.Logging;
+using Moonglade.Data;
using Moonglade.Data.Entities;
-using Moonglade.Data.Infrastructure;
-using Moonglade.Data.Spec;
+using Moonglade.Data.Specifications;
namespace Moonglade.Comments;
public record ToggleApprovalCommand(Guid[] CommentIds) : IRequest;
-public class ToggleApprovalCommandHandler(IRepository repo) : IRequestHandler
+public class ToggleApprovalCommandHandler(
+ MoongladeRepository repo,
+ ILogger logger) : IRequestHandler
{
public async Task Handle(ToggleApprovalCommand request, CancellationToken ct)
{
- var spec = new CommentSpec(request.CommentIds);
- var comments = await repo.ListAsync(spec);
+ var spec = new CommentByIdsSepc(request.CommentIds);
+ var comments = await repo.ListAsync(spec, ct);
foreach (var cmt in comments)
{
cmt.IsApproved = !cmt.IsApproved;
await repo.UpdateAsync(cmt, ct);
}
+
+ logger.LogInformation("Toggled approval status for {Count} comment(s)", comments.Count);
}
}
\ No newline at end of file
diff --git a/src/Moonglade.Configuration/AddDefaultConfigurationCommand.cs b/src/Moonglade.Configuration/AddDefaultConfigurationCommand.cs
index 13ba5b3d2..497e34e51 100644
--- a/src/Moonglade.Configuration/AddDefaultConfigurationCommand.cs
+++ b/src/Moonglade.Configuration/AddDefaultConfigurationCommand.cs
@@ -1,13 +1,12 @@
using MediatR;
using Moonglade.Data;
using Moonglade.Data.Entities;
-using Moonglade.Data.Infrastructure;
namespace Moonglade.Configuration;
public record AddDefaultConfigurationCommand(int Id, string CfgKey, string DefaultJson) : IRequest;
-public class AddDefaultConfigurationCommandHandler(IRepository repository) : IRequestHandler
+public class AddDefaultConfigurationCommandHandler(MoongladeRepository repository) : IRequestHandler
{
public async Task Handle(AddDefaultConfigurationCommand request, CancellationToken ct)
{
diff --git a/src/Moonglade.Configuration/AdvancedSettings.cs b/src/Moonglade.Configuration/AdvancedSettings.cs
index cf76b97b6..6338b24d2 100644
--- a/src/Moonglade.Configuration/AdvancedSettings.cs
+++ b/src/Moonglade.Configuration/AdvancedSettings.cs
@@ -18,9 +18,6 @@ public class AdvancedSettings : IBlogSettings
[Display(Name = "Enable Pingback")]
public bool EnablePingback { get; set; } = true;
- [Display(Name = "Enable MetaWeblog API")]
- public bool EnableMetaWeblog { get; set; } = true;
-
[Display(Name = "Enable OpenSearch")]
public bool EnableOpenSearch { get; set; } = true;
@@ -33,15 +30,9 @@ public class AdvancedSettings : IBlogSettings
[Display(Name = "Enable Site Map")]
public bool EnableSiteMap { get; set; } = true;
- [MinLength(8), MaxLength(16)]
- [Display(Name = "MetaWeblog password")]
- public string MetaWeblogPassword { get; set; }
-
[Display(Name = "Show warning when clicking external links")]
public bool WarnExternalLink { get; set; }
- public string MetaWeblogPasswordHash { get; set; }
-
[JsonIgnore]
public static AdvancedSettings DefaultValue => new();
}
\ No newline at end of file
diff --git a/src/Moonglade.Configuration/BlogConfig.cs b/src/Moonglade.Configuration/BlogConfig.cs
index 96a1bcda1..d9660d06e 100644
--- a/src/Moonglade.Configuration/BlogConfig.cs
+++ b/src/Moonglade.Configuration/BlogConfig.cs
@@ -15,6 +15,7 @@ public interface IBlogConfig
AdvancedSettings AdvancedSettings { get; set; }
CustomStyleSheetSettings CustomStyleSheetSettings { get; set; }
CustomMenuSettings CustomMenuSettings { get; set; }
+ LocalAccountSettings LocalAccountSettings { get; set; }
IEnumerable LoadFromConfig(IDictionary config);
KeyValuePair UpdateAsync(T blogSettings) where T : IBlogSettings;
@@ -38,6 +39,8 @@ public class BlogConfig : IBlogConfig
public CustomMenuSettings CustomMenuSettings { get; set; }
+ public LocalAccountSettings LocalAccountSettings { get; set; }
+
public IEnumerable LoadFromConfig(IDictionary config)
{
ContentSettings = AssignValueForConfigItem(1, ContentSettings.DefaultValue, config);
@@ -48,11 +51,12 @@ public IEnumerable LoadFromConfig(IDictionary config)
AdvancedSettings = AssignValueForConfigItem(6, AdvancedSettings.DefaultValue, config);
CustomStyleSheetSettings = AssignValueForConfigItem(7, CustomStyleSheetSettings.DefaultValue, config);
CustomMenuSettings = AssignValueForConfigItem(10, CustomMenuSettings.DefaultValue, config);
+ LocalAccountSettings = AssignValueForConfigItem(11, LocalAccountSettings.DefaultValue, config);
return _keysToInit.AsEnumerable();
}
- private readonly List _keysToInit = new();
+ private readonly List _keysToInit = [];
private T AssignValueForConfigItem(int index, T defaultValue, IDictionary config) where T : IBlogSettings
{
var name = typeof(T).Name;
diff --git a/src/Moonglade.Configuration/CustomMenuSettings.cs b/src/Moonglade.Configuration/CustomMenuSettings.cs
index 093e2acbd..57c848aa2 100644
--- a/src/Moonglade.Configuration/CustomMenuSettings.cs
+++ b/src/Moonglade.Configuration/CustomMenuSettings.cs
@@ -20,26 +20,21 @@ public class CustomMenuSettings : IBlogSettings
public Menu[] Menus { get; set; } = Array.Empty