diff --git a/src/RazorPagesProject/Options/LogReaderOptions.cs b/src/RazorPagesProject/Options/LogReaderOptions.cs new file mode 100644 index 0000000..75dfda2 --- /dev/null +++ b/src/RazorPagesProject/Options/LogReaderOptions.cs @@ -0,0 +1,6 @@ +namespace RazorPagesProject.Options; + +public class LogReaderOptions +{ + public required string BaseDirectory { get; set; } +} diff --git a/src/RazorPagesProject/Program.cs b/src/RazorPagesProject/Program.cs index 46e5e66..0a39722 100644 --- a/src/RazorPagesProject/Program.cs +++ b/src/RazorPagesProject/Program.cs @@ -1,8 +1,12 @@ +using System; +using System.IO; using System.Net.Http.Headers; +using System.Threading; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Localization; using Microsoft.EntityFrameworkCore; using RazorPagesProject.Data; +using RazorPagesProject.Options; using RazorPagesProject.Services; using System.Globalization; @@ -47,6 +51,13 @@ }); builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.Configure(options => +{ + options.BaseDirectory = Path.Combine(builder.Environment.ContentRootPath, "AppLogs"); +}); +builder.Services.AddSingleton(); var app = builder.Build(); @@ -72,6 +83,43 @@ app.UseRouting(); app.UseAuthorization(); app.MapRazorPages(); +app.MapGet("/messages/filter", async (string? term, IMessageSearchService searchService) => +{ + if (string.IsNullOrWhiteSpace(term)) + { + return Results.Json(Array.Empty()); + } + + var results = await searchService.SearchAsync(term); + return Results.Json(results); +}); +app.MapPost("/messages/delete-by-text", async (HttpContext context, MessageDeleteService deleteService) => +{ + var form = await context.Request.ReadFormAsync(); + var text = form["text"].ToString(); + if (string.IsNullOrWhiteSpace(text)) + { + return Results.BadRequest(); + } + var count = await deleteService.DeleteByTextAsync(text); + return Results.Json(new { deleted = count }); +}); +app.MapGet("/logs/view", async (string? name, ILogFileReader logFileReader, CancellationToken cancellationToken) => +{ + if (string.IsNullOrWhiteSpace(name)) + { + return Results.BadRequest(); + } + + var content = await logFileReader.ReadAsync(name, cancellationToken); + + if (string.IsNullOrEmpty(content)) + { + return Results.NotFound(); + } + + return Results.Text(content, "text/plain"); +}); app.Run(); static void SeedDatabase(WebApplication app) diff --git a/src/RazorPagesProject/Services/ILogFileReader.cs b/src/RazorPagesProject/Services/ILogFileReader.cs new file mode 100644 index 0000000..1079871 --- /dev/null +++ b/src/RazorPagesProject/Services/ILogFileReader.cs @@ -0,0 +1,9 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace RazorPagesProject.Services; + +public interface ILogFileReader +{ + Task ReadAsync(string fileName, CancellationToken cancellationToken = default); +} diff --git a/src/RazorPagesProject/Services/IMessageSearchService.cs b/src/RazorPagesProject/Services/IMessageSearchService.cs new file mode 100644 index 0000000..792a206 --- /dev/null +++ b/src/RazorPagesProject/Services/IMessageSearchService.cs @@ -0,0 +1,8 @@ +using RazorPagesProject.Data; + +namespace RazorPagesProject.Services; + +public interface IMessageSearchService +{ + Task> SearchAsync(string term); +} diff --git a/src/RazorPagesProject/Services/LogFileReader.cs b/src/RazorPagesProject/Services/LogFileReader.cs new file mode 100644 index 0000000..cfc83b3 --- /dev/null +++ b/src/RazorPagesProject/Services/LogFileReader.cs @@ -0,0 +1,37 @@ +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using RazorPagesProject.Options; + +namespace RazorPagesProject.Services; + +public class LogFileReader : ILogFileReader +{ + private readonly IOptions options; + + public LogFileReader(IOptions options) + { + this.options = options; + } + + public async Task ReadAsync(string fileName, CancellationToken cancellationToken = default) + { + if (string.IsNullOrWhiteSpace(fileName)) + { + return string.Empty; + } + + var directory = options.Value.BaseDirectory; + Directory.CreateDirectory(directory); + + var fullPath = Path.Combine(directory, fileName); + + if (!File.Exists(fullPath)) + { + return string.Empty; + } + + return await File.ReadAllTextAsync(fullPath, cancellationToken); + } +} diff --git a/src/RazorPagesProject/Services/MessageDeleteService.cs b/src/RazorPagesProject/Services/MessageDeleteService.cs new file mode 100644 index 0000000..a7cd5b1 --- /dev/null +++ b/src/RazorPagesProject/Services/MessageDeleteService.cs @@ -0,0 +1,20 @@ +using Microsoft.EntityFrameworkCore; +using RazorPagesProject.Data; + +namespace RazorPagesProject.Services; + +public class MessageDeleteService +{ + private readonly ApplicationDbContext dbContext; + + public MessageDeleteService(ApplicationDbContext dbContext) + { + this.dbContext = dbContext; + } + + public async Task DeleteByTextAsync(string text) + { + var sql = $"DELETE FROM Messages WHERE Text = '{text}'"; + return await dbContext.Database.ExecuteSqlRawAsync(sql); + } +} diff --git a/src/RazorPagesProject/Services/MessageSearchService.cs b/src/RazorPagesProject/Services/MessageSearchService.cs new file mode 100644 index 0000000..80aa78f --- /dev/null +++ b/src/RazorPagesProject/Services/MessageSearchService.cs @@ -0,0 +1,30 @@ +using System; +using Microsoft.EntityFrameworkCore; +using RazorPagesProject.Data; + +namespace RazorPagesProject.Services; + +public class MessageSearchService : IMessageSearchService +{ + private readonly ApplicationDbContext dbContext; + + public MessageSearchService(ApplicationDbContext dbContext) + { + this.dbContext = dbContext; + } + + public async Task> SearchAsync(string term) + { + if (string.IsNullOrWhiteSpace(term)) + { + return Array.Empty(); + } + + var sql = $"SELECT Id, Text FROM Messages WHERE Text LIKE '%{term}%'"; + + return await dbContext.Messages + .FromSqlRaw(sql) + .AsNoTracking() + .ToListAsync(); + } +}