From a52bd073e00f1553e8d298d5a16f92a8117f30a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B0=D1=81=D0=B8=D0=BB=D0=B8=D0=B9?= Date: Sat, 1 Oct 2022 16:31:54 +0500 Subject: [PATCH 01/12] =?UTF-8?q?=D0=98=D1=81=D0=BF=D0=B0=D1=80=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BE?= =?UTF-8?q?=D0=BA=20CPP-=D0=BF=D0=BE=D0=B4=D1=85=D0=BE=D0=B4=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Services/Thoughts.Services/InSQL/SqlShortUrlManagerService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Services/Thoughts.Services/InSQL/SqlShortUrlManagerService.cs b/Services/Thoughts.Services/InSQL/SqlShortUrlManagerService.cs index b2eee9d..45ec8bb 100644 --- a/Services/Thoughts.Services/InSQL/SqlShortUrlManagerService.cs +++ b/Services/Thoughts.Services/InSQL/SqlShortUrlManagerService.cs @@ -161,7 +161,7 @@ public async Task UpdateUrlAsync(int Id, string UrlString, CancellationTok } catch (OperationCanceledException e) { - _logger.LogError($"Обновление короткой ссылки Id:{Id} вызвало исключение DbUpdateConcurrencyException: {e.ToString()}"); + _logger.LogError($"Обновление короткой ссылки Id:{Id} вызвало исключение OperationCanceledException: {e.ToString()}"); return false; } return true; From 1b21fe9552d9e6f569c54cb259e5cf6c63016c24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B0=D1=81=D0=B8=D0=BB=D0=B8=D0=B9?= Date: Sat, 1 Oct 2022 16:39:33 +0500 Subject: [PATCH 02/12] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BF=D1=80=D0=BE=D0=B1=D0=BB=D0=B5?= =?UTF-8?q?=D0=BC=D0=B0=20=D1=81=20=D0=BA=D0=BE=D0=B4=D0=B8=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=BA=D0=BE=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Services/Thoughts.WebAPI/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Services/Thoughts.WebAPI/Program.cs b/Services/Thoughts.WebAPI/Program.cs index dd4e531..dfd7a75 100644 --- a/Services/Thoughts.WebAPI/Program.cs +++ b/Services/Thoughts.WebAPI/Program.cs @@ -17,7 +17,7 @@ switch (db_type) { - default: throw new InvalidOperationException($"Òèï ÁÄ {db_type} íå ïîääåðæèâàåòñÿ"); + default: throw new InvalidOperationException($"Тип БД {db_type} не поддерживается"); case "Sqlite": builder.Services.AddThoughtsDbSqlite(configuration.GetConnectionString("Sqlite")); From 9806a502ae0c6735d1e4ee8994bfc962e4063d86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B0=D1=81=D0=B8=D0=BB=D0=B8=D0=B9?= Date: Sat, 1 Oct 2022 17:39:46 +0500 Subject: [PATCH 03/12] =?UTF-8?q?=D0=A3=D1=81=D1=82=D1=80=D0=B0=D0=BD?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BF=D1=80=D0=BE=D0=B1=D0=BB=D0=B5?= =?UTF-8?q?=D0=BC=20=D1=81=D0=BB=D0=B8=D1=8F=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Services/Thoughts.WebAPI/Program.cs | 37 +++++++++++++++++++++++------ UI/Thoughts.UI.MVC/appsettings.json | 2 +- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/Services/Thoughts.WebAPI/Program.cs b/Services/Thoughts.WebAPI/Program.cs index dfd7a75..33efce8 100644 --- a/Services/Thoughts.WebAPI/Program.cs +++ b/Services/Thoughts.WebAPI/Program.cs @@ -1,40 +1,63 @@ +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.Versioning; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Thoughts.DAL.Sqlite; using Thoughts.DAL.SqlServer; +using Thoughts.Interfaces; using Thoughts.Interfaces.Base; using Thoughts.Services.InSQL; +using Thoughts.WebAPI; using Thoughts.WebAPI.Infrastructure.Extensions; var builder = WebApplication.CreateBuilder(args); var configuration = builder.Configuration; +var services = builder.Services; +services.AddControllers(); - -builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(); +services.AddApiVersioning(opt => +{ + opt.DefaultApiVersion = new ApiVersion(1, 0); + opt.AssumeDefaultVersionWhenUnspecified = true; + opt.ReportApiVersions = true; + opt.ApiVersionReader = ApiVersionReader.Combine(new UrlSegmentApiVersionReader(), + new HeaderApiVersionReader("x-api-version"), + new MediaTypeApiVersionReader("x-api-version")); +}); +services.AddVersionedApiExplorer(setup => +{ + setup.GroupNameFormat = "'v'VVV"; + setup.SubstituteApiVersionInUrl = true; +}); +services.AddEndpointsApiExplorer(); +services.AddSwaggerGen(); +services.ConfigureOptions(); var db_type = configuration["Database"]; switch (db_type) { - default: throw new InvalidOperationException($"Тип БД {db_type} не поддерживается"); + default: throw new InvalidOperationException($"Òèï ÁÄ {db_type} íå ïîääåðæèâàåòñÿ"); case "Sqlite": - builder.Services.AddThoughtsDbSqlite(configuration.GetConnectionString("Sqlite")); + services.AddThoughtsDbSqlite(configuration.GetConnectionString("Sqlite")); break; case "SqlServer": - builder.Services.AddThoughtsDbSqlServer(configuration.GetConnectionString("SqlServer")); + services.AddThoughtsDbSqlServer(configuration.GetConnectionString("SqlServer")); break; } +services.AddScoped(); builder.Services.AddTransient(); builder.Services.AddTransient(); -builder.Services.AddControllers(); var app = builder.Build(); +var apiVersionDescriptionProvider = app.Services.GetRequiredService(); + await app.InitializeDatabase(); if (app.Environment.IsDevelopment()) diff --git a/UI/Thoughts.UI.MVC/appsettings.json b/UI/Thoughts.UI.MVC/appsettings.json index 52df621..2850ac8 100644 --- a/UI/Thoughts.UI.MVC/appsettings.json +++ b/UI/Thoughts.UI.MVC/appsettings.json @@ -16,7 +16,7 @@ } }, "AllowedHosts": "*", - "WebApiUrl": "http://localhost:5001" + "WebApiUrl": "http://localhost:5001", "UploadFileOptions": { "StoredFilesPath": "c:\\files\\", "PermittedExtensions": [ ".txt", ".pdf", ".png", ".jpg", ".jpeg", ".gif", ".zip" ], From 223e499d73ae0eb6237b84de579a0796bff999fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B0=D1=81=D0=B8=D0=BB=D0=B8=D0=B9?= Date: Sat, 1 Oct 2022 17:50:47 +0500 Subject: [PATCH 04/12] =?UTF-8?q?=D0=9F=D1=80=D0=BC=D0=B5=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D0=BE=D0=BD?= =?UTF-8?q?=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=B4=D0=BB?= =?UTF-8?q?=D1=8F=20Web=20API=20=D1=81=D0=B5=D1=80=D0=B2=D0=B8=D1=81=D0=B0?= =?UTF-8?q?=20=D0=BA=D0=BE=D1=80=D0=BE=D1=82=D0=BA=D0=B8=D1=85=20=D1=81?= =?UTF-8?q?=D1=81=D1=8B=D0=BB=D0=BE=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Services/Thoughts.Interfaces.Base/WebApiControllersPath.cs | 2 +- .../Thoughts.WebAPI/Controllers/ShortUrlManagerController.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Services/Thoughts.Interfaces.Base/WebApiControllersPath.cs b/Services/Thoughts.Interfaces.Base/WebApiControllersPath.cs index b72c7d3..2782ba4 100644 --- a/Services/Thoughts.Interfaces.Base/WebApiControllersPath.cs +++ b/Services/Thoughts.Interfaces.Base/WebApiControllersPath.cs @@ -8,6 +8,6 @@ namespace Thoughts.Interfaces.Base { static public class WebApiControllersPath { - public const string ShortUrl = "api/url"; + public const string ShortUrl = "api/v{version:apiVersion}/url"; } } diff --git a/Services/Thoughts.WebAPI/Controllers/ShortUrlManagerController.cs b/Services/Thoughts.WebAPI/Controllers/ShortUrlManagerController.cs index 4168c17..ab2869f 100644 --- a/Services/Thoughts.WebAPI/Controllers/ShortUrlManagerController.cs +++ b/Services/Thoughts.WebAPI/Controllers/ShortUrlManagerController.cs @@ -5,6 +5,7 @@ namespace Thoughts.WebAPI.Controllers { + [ApiVersion("1.0")] [Route(WebApiControllersPath.ShortUrl)] [ApiController] public class ShortUrlManagerController : ControllerBase From 8fb07bb5fee9664532c4c3a5a2b370a68d4b79c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B0=D1=81=D0=B8=D0=BB=D0=B8=D0=B9?= Date: Sat, 1 Oct 2022 18:52:42 +0500 Subject: [PATCH 05/12] =?UTF-8?q?=D0=9C=D0=B5=D1=82=D0=BE=D0=B4=20=D1=81?= =?UTF-8?q?=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=BA=D0=BE=D1=80?= =?UTF-8?q?=D0=BE=D1=82=D0=BA=D0=BE=D0=B9=20=D1=81=D1=81=D1=8B=D0=BB=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=B2=D0=BE=D0=B7=D0=B2=D1=80=D0=B0=D1=89=D0=B0=D0=B5?= =?UTF-8?q?=D1=82=20=D0=B8=D0=B4=D0=B5=D0=BD=D1=82=D0=B8=D1=84=D0=B8=D0=BA?= =?UTF-8?q?=D0=B0=D1=82=D0=BE=D1=80=20=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD?= =?UTF-8?q?=D0=BD=D0=BE=D0=B9=20=D1=81=D1=81=D1=8B=D0=BB=D0=BA=D0=B8.=20?= =?UTF-8?q?=D0=9E=D1=82=D0=B4=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B=D0=B9=20=D0=BC?= =?UTF-8?q?=D0=B5=D1=82=D0=BE=D0=B4=20=D0=B4=D0=BB=D1=8F=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=BB=D1=83=D1=87=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=85=D0=B5=D1=88?= =?UTF-8?q?=D0=B0=20=D0=BA=D0=BE=D1=80=D0=BE=D1=82=D0=BA=D0=BE=D0=B9=20?= =?UTF-8?q?=D1=81=D1=81=D1=8B=D0=BB=D0=BA=D0=B8=20=D0=BF=D0=BE=20=D0=B8?= =?UTF-8?q?=D0=B4=D0=B5=D0=BD=D1=82=D0=B8=D1=84=D0=B8=D0=BA=D0=B0=D1=82?= =?UTF-8?q?=D0=BE=D1=80=D1=83.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../IShortUrlManager.cs | 17 +++++++++-- .../InSQL/SqlShortUrlManagerService.cs | 22 ++++++++++++--- .../ShortUrl/ShortUrlClient.cs | 11 ++++++-- .../Controllers/ShortUrlManagerController.cs | 28 +++++++++++++------ .../Controllers/ShortUrlController.cs | 4 +-- .../WebModels/ShortUrlWebModel.cs | 6 ++-- 6 files changed, 67 insertions(+), 21 deletions(-) diff --git a/Services/Thoughts.Interfaces.Base/IShortUrlManager.cs b/Services/Thoughts.Interfaces.Base/IShortUrlManager.cs index 1525542..0a3bf1e 100644 --- a/Services/Thoughts.Interfaces.Base/IShortUrlManager.cs +++ b/Services/Thoughts.Interfaces.Base/IShortUrlManager.cs @@ -12,6 +12,7 @@ public interface IShortUrlManager /// Получить оригинальный Url по псевдониму /// /// Псевдоним ссылки + /// Токен отмены /// Оригинальный Url Task GetUrlAsync(string Alias, CancellationToken Cancel = default); @@ -19,20 +20,31 @@ public interface IShortUrlManager /// Получить оригинальный Url по идентификатору /// /// Идентификатор короткой ссылки + /// Токен отмены /// Оригинальный Url Task GetUrlByIdAsync(int Id, CancellationToken Cancel = default); + /// + /// Получить псевдоним короткой ссылки по ее идентификатору + /// + /// Идентификатор короткой ссылки + /// Токен отмены + /// Псевдоним ссылки + Task GetAliasByIdAsync(int Id, CancellationToken Cancel = default); + /// /// Добавить короткую ссылку /// /// Добавляемый Url - /// Псевдоним ссылки - Task AddUrlAsync(string Url, CancellationToken Cancel = default); + /// Токен отмены + /// Идентификатор добавленной короткой ссылки + Task AddUrlAsync(string Url, CancellationToken Cancel = default); /// /// Удалить короткую ссылку по идентификатору /// /// Идентификатор короткой ссылки + /// Токен отмены /// Результат удаления Task DeleteUrlAsync(int Id, CancellationToken Cancel = default); @@ -41,6 +53,7 @@ public interface IShortUrlManager /// /// Идентификатор короткой ссылки /// Новый Url + /// Токен отмены /// Результат обновления Task UpdateUrlAsync(int Id, string Url, CancellationToken Cancel = default); } diff --git a/Services/Thoughts.Services/InSQL/SqlShortUrlManagerService.cs b/Services/Thoughts.Services/InSQL/SqlShortUrlManagerService.cs index 45ec8bb..9995fab 100644 --- a/Services/Thoughts.Services/InSQL/SqlShortUrlManagerService.cs +++ b/Services/Thoughts.Services/InSQL/SqlShortUrlManagerService.cs @@ -25,7 +25,7 @@ public SqlShortUrlManagerService(ThoughtsDB Db, ILogger Logg _logger = Logger; } - public async Task AddUrlAsync(string UrlString, CancellationToken Cancel = default) + public async Task AddUrlAsync(string UrlString, CancellationToken Cancel = default) { _logger.LogInformation($"Создание короткой ссылки для Url:{UrlString}"); @@ -34,7 +34,7 @@ public async Task AddUrlAsync(string UrlString, CancellationToken Cancel if (url is null) { _logger.LogInformation($"Короткая ссылка не создана. Некоректный Url:{UrlString}"); - return String.Empty; + return 0; } var shortUrl = await _db.ShortUrls. @@ -46,7 +46,7 @@ public async Task AddUrlAsync(string UrlString, CancellationToken Cancel if (shortUrl is not null) { _logger.LogInformation($"Короткая ссылка {shortUrl.Alias} уже существует для Url:{shortUrl.OriginalUrl}"); - return shortUrl.Alias; + return shortUrl.Id; } shortUrl = new() @@ -59,7 +59,7 @@ public async Task AddUrlAsync(string UrlString, CancellationToken Cancel _logger.LogInformation($"Создана короткая ссылка {shortUrl.Alias} для Url:{shortUrl.OriginalUrl}"); - return shortUrl.Alias; + return shortUrl.Id; } public async Task DeleteUrlAsync(int Id, CancellationToken Cancel = default) @@ -124,6 +124,20 @@ public async Task DeleteUrlAsync(int Id, CancellationToken Cancel = defaul return result.OriginalUrl; } + public async Task GetAliasByIdAsync(int Id, CancellationToken Cancel = default) + { + var result = await _db.ShortUrls. + FirstOrDefaultAsync( + u => u.Id == Id, + Cancel + ). + ConfigureAwait(false); + if (result is null) + return null ; + + return result.Alias; + } + public async Task UpdateUrlAsync(int Id, string UrlString, CancellationToken Cancel = default) { _logger.LogInformation($"Обновление короткой ссылки Id:{Id}. Новый Url:{UrlString}"); diff --git a/Services/Thoughts.WebAPI.Clients/ShortUrl/ShortUrlClient.cs b/Services/Thoughts.WebAPI.Clients/ShortUrl/ShortUrlClient.cs index 263897d..f9660af 100644 --- a/Services/Thoughts.WebAPI.Clients/ShortUrl/ShortUrlClient.cs +++ b/Services/Thoughts.WebAPI.Clients/ShortUrl/ShortUrlClient.cs @@ -18,10 +18,10 @@ public ShortUrlClient(IConfiguration Configuration):base(Configuration,WebApiCon } - public async Task AddUrlAsync(string Url, CancellationToken Cancel = default) + public async Task AddUrlAsync(string Url, CancellationToken Cancel = default) { var response = await PostAsync($"{WebApiControllersPath.ShortUrl}", Url); - return await response.Content.ReadAsAsync(); + return await response.Content.ReadAsAsync(); } public async Task DeleteUrlAsync(int Id, CancellationToken Cancel = default) @@ -30,6 +30,12 @@ public async Task DeleteUrlAsync(int Id, CancellationToken Cancel = defaul return response.IsSuccessStatusCode; } + public async Task GetAliasByIdAsync(int Id, CancellationToken Cancel = default) + { + var response = await GetAsync($"{WebApiControllersPath.ShortUrl}/alias/{Id}"); + return response; + } + public async Task GetUrlAsync(string Alias, CancellationToken Cancel = default) { var response = await GetAsync($"{WebApiControllersPath.ShortUrl}?Alias={Alias}"); @@ -47,5 +53,6 @@ public async Task UpdateUrlAsync(int Id, string Url, CancellationToken Can var response = await PostAsync($"{WebApiControllersPath.ShortUrl}/{Id}", Url); return await response.Content.ReadAsAsync(); } + } } diff --git a/Services/Thoughts.WebAPI/Controllers/ShortUrlManagerController.cs b/Services/Thoughts.WebAPI/Controllers/ShortUrlManagerController.cs index ab2869f..1588bf7 100644 --- a/Services/Thoughts.WebAPI/Controllers/ShortUrlManagerController.cs +++ b/Services/Thoughts.WebAPI/Controllers/ShortUrlManagerController.cs @@ -17,7 +17,7 @@ public ShortUrlManagerController(IShortUrlManager ShortUrlManager) _shortUrlManager = ShortUrlManager; } - // GET: api/url?Alias=... + // GET: api/v1/url?Alias=... [HttpGet] public async Task> GetUrl(string Alias) { @@ -28,7 +28,7 @@ public async Task> GetUrl(string Alias) return result; } - // GET: api/url/10 + // GET: api/v1/url/10 [HttpGet("{Id}")] public async Task> GetUrlById(int Id) { @@ -39,19 +39,31 @@ public async Task> GetUrlById(int Id) return result; } + //GET: api/v1/url/alias/10 + [HttpGet("alias/{Id}")] + public async Task> GetAliasById(int Id) + { + var result = await _shortUrlManager.GetAliasByIdAsync(Id); - // POST api/url + if (String.IsNullOrEmpty(result)) + return BadRequest(); + + return result; + } + + // POST api/v1/url [HttpPost] - public async Task> AddUrl([FromBody]string Url) + public async Task> AddUrl([FromBody]string Url) { var result = await _shortUrlManager.AddUrlAsync(Url); - if (String.IsNullOrEmpty(result)) + if (result==0) return BadRequest(); - return $"{result}"; + //return $"{result}"; + return result; } - // DELETE api/url/10 + // DELETE api/v1/url/10 [HttpDelete("{Id}")] public async Task> DeleteUrl(int Id) { @@ -59,7 +71,7 @@ public async Task> DeleteUrl(int Id) return result ? result : BadRequest(); } - // POST api/url/10 + // POST api/v1/url/10 [HttpPost("{Id}")] public async Task> UpdateUrl(int Id, [FromBody] string Url) { diff --git a/UI/Thoughts.UI.MVC/Controllers/ShortUrlController.cs b/UI/Thoughts.UI.MVC/Controllers/ShortUrlController.cs index 4e3341a..fbfd65c 100644 --- a/UI/Thoughts.UI.MVC/Controllers/ShortUrlController.cs +++ b/UI/Thoughts.UI.MVC/Controllers/ShortUrlController.cs @@ -28,11 +28,11 @@ public async Task GetUrl(string Alias) public async Task AddUrl(string Url) { var result = await _shortUrlManager.AddUrlAsync(Url); - if (String.IsNullOrEmpty(result)) + if (result==0) return BadRequest(); ShortUrlWebModel shortUrlWebModel = new() { - Alias = result, + Id = result, OriginalUrl = Url }; return View(shortUrlWebModel); diff --git a/UI/Thoughts.UI.MVC/WebModels/ShortUrlWebModel.cs b/UI/Thoughts.UI.MVC/WebModels/ShortUrlWebModel.cs index 45cb8ab..ed98495 100644 --- a/UI/Thoughts.UI.MVC/WebModels/ShortUrlWebModel.cs +++ b/UI/Thoughts.UI.MVC/WebModels/ShortUrlWebModel.cs @@ -1,8 +1,8 @@ namespace Thoughts.UI.MVC.WebModels { public class ShortUrlWebModel - { - public string OriginalUrl { get; set; } - public string Alias { get; set; } + { + public int Id { get; set; } + public string OriginalUrl { get; set; } } } From 6e02004a203f089efec2bc5a1bd1356060a53564 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B0=D1=81=D0=B8=D0=BB=D0=B8=D0=B9?= Date: Sat, 1 Oct 2022 18:58:28 +0500 Subject: [PATCH 06/12] =?UTF-8?q?=D0=98=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20=D1=82=D0=B8=D0=BF=D1=8B=20=D0=B2=D0=BE=D0=B7?= =?UTF-8?q?=D0=B2=D1=80=D0=B0=D1=89=D0=B0=D0=B5=D0=BC=D1=8B=D1=85=20=D0=B7?= =?UTF-8?q?=D0=BD=D0=B0=D1=87=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=BC=D0=B5=D1=82?= =?UTF-8?q?=D0=BE=D0=B4=D0=BE=D0=B2=20Web=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Thoughts.WebAPI/Controllers/ShortUrlManagerController.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Services/Thoughts.WebAPI/Controllers/ShortUrlManagerController.cs b/Services/Thoughts.WebAPI/Controllers/ShortUrlManagerController.cs index 1588bf7..8b5be26 100644 --- a/Services/Thoughts.WebAPI/Controllers/ShortUrlManagerController.cs +++ b/Services/Thoughts.WebAPI/Controllers/ShortUrlManagerController.cs @@ -48,7 +48,7 @@ public async Task> GetAliasById(int Id) if (String.IsNullOrEmpty(result)) return BadRequest(); - return result; + return AcceptedAtAction(nameof(GetUrl), new { Alias = result }, result); } // POST api/v1/url @@ -59,8 +59,7 @@ public async Task> AddUrl([FromBody]string Url) if (result==0) return BadRequest(); - //return $"{result}"; - return result; + return CreatedAtAction(nameof(GetAliasById), new { Id = result }, result); } // DELETE api/v1/url/10 From 1db78313fbc2659f7f86e6dc3139e794beb558cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B0=D1=81=D0=B8=D0=BB=D0=B8=D0=B9?= Date: Sat, 1 Oct 2022 20:40:45 +0500 Subject: [PATCH 07/12] =?UTF-8?q?=D0=98=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=20=D0=BF=D1=83=D1=82=D1=8C=20=D0=BA=20WebAPI,=20=D0=B2?= =?UTF-8?q?=20=D0=BF=D1=83=D1=82=D1=8C=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D1=8F?= =?UTF-8?q?=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WebApiControllersPath.cs | 2 ++ .../ShortUrl/ShortUrlClient.cs | 14 +++++++------- .../{ => v1}/ShortUrlManagerController.cs | 12 ++++++------ 3 files changed, 15 insertions(+), 13 deletions(-) rename Services/Thoughts.WebAPI/Controllers/{ => v1}/ShortUrlManagerController.cs (86%) diff --git a/Services/Thoughts.Interfaces.Base/WebApiControllersPath.cs b/Services/Thoughts.Interfaces.Base/WebApiControllersPath.cs index 2782ba4..36b340d 100644 --- a/Services/Thoughts.Interfaces.Base/WebApiControllersPath.cs +++ b/Services/Thoughts.Interfaces.Base/WebApiControllersPath.cs @@ -9,5 +9,7 @@ namespace Thoughts.Interfaces.Base static public class WebApiControllersPath { public const string ShortUrl = "api/v{version:apiVersion}/url"; + + public const string ShortUrlV1 = "api/v1/url"; } } diff --git a/Services/Thoughts.WebAPI.Clients/ShortUrl/ShortUrlClient.cs b/Services/Thoughts.WebAPI.Clients/ShortUrl/ShortUrlClient.cs index f9660af..9c2e4e9 100644 --- a/Services/Thoughts.WebAPI.Clients/ShortUrl/ShortUrlClient.cs +++ b/Services/Thoughts.WebAPI.Clients/ShortUrl/ShortUrlClient.cs @@ -13,44 +13,44 @@ namespace Thoughts.WebAPI.Clients.ShortUrl { public class ShortUrlClient: BaseClient, IShortUrlManager { - public ShortUrlClient(IConfiguration Configuration):base(Configuration,WebApiControllersPath.ShortUrl) + public ShortUrlClient(IConfiguration Configuration):base(Configuration,WebApiControllersPath.ShortUrlV1) { } public async Task AddUrlAsync(string Url, CancellationToken Cancel = default) { - var response = await PostAsync($"{WebApiControllersPath.ShortUrl}", Url); + var response = await PostAsync($"{WebApiControllersPath.ShortUrlV1}", Url); return await response.Content.ReadAsAsync(); } public async Task DeleteUrlAsync(int Id, CancellationToken Cancel = default) { - var response = await DeleteAsync($"{WebApiControllersPath.ShortUrl}/{Id}"); + var response = await DeleteAsync($"{WebApiControllersPath.ShortUrlV1}/{Id}"); return response.IsSuccessStatusCode; } public async Task GetAliasByIdAsync(int Id, CancellationToken Cancel = default) { - var response = await GetAsync($"{WebApiControllersPath.ShortUrl}/alias/{Id}"); + var response = await GetAsync($"{WebApiControllersPath.ShortUrlV1}/alias/{Id}"); return response; } public async Task GetUrlAsync(string Alias, CancellationToken Cancel = default) { - var response = await GetAsync($"{WebApiControllersPath.ShortUrl}?Alias={Alias}"); + var response = await GetAsync($"{WebApiControllersPath.ShortUrlV1}?Alias={Alias}"); return response; } public async Task GetUrlByIdAsync(int Id, CancellationToken Cancel = default) { - var response = await GetAsync($"{WebApiControllersPath.ShortUrl}/{Id}"); + var response = await GetAsync($"{WebApiControllersPath.ShortUrlV1}/{Id}"); return response; } public async Task UpdateUrlAsync(int Id, string Url, CancellationToken Cancel = default) { - var response = await PostAsync($"{WebApiControllersPath.ShortUrl}/{Id}", Url); + var response = await PostAsync($"{WebApiControllersPath.ShortUrlV1}/{Id}", Url); return await response.Content.ReadAsAsync(); } diff --git a/Services/Thoughts.WebAPI/Controllers/ShortUrlManagerController.cs b/Services/Thoughts.WebAPI/Controllers/v1/ShortUrlManagerController.cs similarity index 86% rename from Services/Thoughts.WebAPI/Controllers/ShortUrlManagerController.cs rename to Services/Thoughts.WebAPI/Controllers/v1/ShortUrlManagerController.cs index 8b5be26..9a09d62 100644 --- a/Services/Thoughts.WebAPI/Controllers/ShortUrlManagerController.cs +++ b/Services/Thoughts.WebAPI/Controllers/v1/ShortUrlManagerController.cs @@ -3,7 +3,7 @@ using Thoughts.Interfaces.Base; -namespace Thoughts.WebAPI.Controllers +namespace Thoughts.WebAPI.Controllers.v1 { [ApiVersion("1.0")] [Route(WebApiControllersPath.ShortUrl)] @@ -45,7 +45,7 @@ public async Task> GetAliasById(int Id) { var result = await _shortUrlManager.GetAliasByIdAsync(Id); - if (String.IsNullOrEmpty(result)) + if (string.IsNullOrEmpty(result)) return BadRequest(); return AcceptedAtAction(nameof(GetUrl), new { Alias = result }, result); @@ -53,10 +53,10 @@ public async Task> GetAliasById(int Id) // POST api/v1/url [HttpPost] - public async Task> AddUrl([FromBody]string Url) + public async Task> AddUrl([FromBody] string Url) { var result = await _shortUrlManager.AddUrlAsync(Url); - if (result==0) + if (result == 0) return BadRequest(); return CreatedAtAction(nameof(GetAliasById), new { Id = result }, result); @@ -66,7 +66,7 @@ public async Task> AddUrl([FromBody]string Url) [HttpDelete("{Id}")] public async Task> DeleteUrl(int Id) { - var result= await _shortUrlManager.DeleteUrlAsync(Id); + var result = await _shortUrlManager.DeleteUrlAsync(Id); return result ? result : BadRequest(); } @@ -74,7 +74,7 @@ public async Task> DeleteUrl(int Id) [HttpPost("{Id}")] public async Task> UpdateUrl(int Id, [FromBody] string Url) { - var result=await _shortUrlManager.UpdateUrlAsync(Id, Url); + var result = await _shortUrlManager.UpdateUrlAsync(Id, Url); return result ? result : BadRequest(); } } From 11757a50fa87286e02dddcc2d97a342d28a2531d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B0=D1=81=D0=B8=D0=BB=D0=B8=D0=B9?= Date: Sat, 1 Oct 2022 22:20:28 +0500 Subject: [PATCH 08/12] =?UTF-8?q?=D0=9F=D1=80=D0=B8=D0=B2=D0=B5=D0=B4?= =?UTF-8?q?=D0=B5=D0=BD=D1=8B=20=D0=B2=20=D0=BF=D0=BE=D1=80=D1=8F=D0=B4?= =?UTF-8?q?=D0=BE=D0=BA=20=D0=BC=D0=B0=D1=80=D1=88=D1=80=D1=83=D1=82=D1=8B?= =?UTF-8?q?=20=D0=BA=D0=BE=D0=BD=D1=82=D1=80=D0=BE=D0=BB=D0=BB=D0=B5=D1=80?= =?UTF-8?q?=D0=B0.=20=D0=A2=D0=B5=D0=BF=D0=B5=D1=80=D1=8C=20=D0=B4=D0=BE?= =?UTF-8?q?=D1=81=D1=82=D1=83=D0=BF=20=D0=BA=20=D0=B4=D0=B5=D0=B9=D1=81?= =?UTF-8?q?=D1=82=D0=B2=D0=B8=D1=8F=D0=BC=20=D0=BA=D0=BE=D0=BD=D1=82=D1=80?= =?UTF-8?q?=D0=BE=D0=BB=D0=BB=D0=B5=D1=80=D0=B0=20=D0=BF=D1=80=D0=BE=D0=B8?= =?UTF-8?q?=D1=81=D1=85=D0=BE=D0=B4=D0=B8=D1=82=20=D0=BF=D0=BE=20=D1=81?= =?UTF-8?q?=D1=82=D0=B0=D0=BD=D0=B4=D0=B0=D1=80=D1=82=D0=BD=D0=BE=D0=BC?= =?UTF-8?q?=D1=83=20=D0=BC=D0=B0=D1=80=D1=88=D1=80=D1=83=D1=82=D1=83=20"ht?= =?UTF-8?q?tps://localhost/url/action/id=3F",=20=D0=BF=D0=B5=D1=80=D0=B5?= =?UTF-8?q?=D1=85=D0=BE=D0=B4=20=D0=BF=D0=BE=20=D0=BA=D0=BE=D1=80=D0=BE?= =?UTF-8?q?=D1=82=D0=BA=D0=BE=D0=B9=20=D1=81=D1=81=D1=8B=D0=BB=D0=BA=D0=B5?= =?UTF-8?q?=20"https://localhost/url/17D9...",=20=D1=81=D0=BE=D0=B7=D0=B4?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=BA=D0=BE=D1=80=D0=BE=D1=82=D0=BA?= =?UTF-8?q?=D0=BE=D0=B9=20=D1=81=D1=81=D1=8B=D0=BB=D0=BA=D0=B8=20=D0=BF?= =?UTF-8?q?=D0=BE=20POST=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE=D1=81=D1=83=20"h?= =?UTF-8?q?ttps://localhost/url/"=20=D1=81=20=D0=BF=D0=B5=D1=80=D0=B5?= =?UTF-8?q?=D0=B4=D0=B0=D1=87=D0=B5=D0=B9=20=D1=81=D1=81=D1=8B=D0=BB=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=B2=20=D1=82=D0=B5=D0=BB=D0=B5=20=D0=B7=D0=B0=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D1=81=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/ShortUrlController.cs | 54 ++++++++++++++----- UI/Thoughts.UI.MVC/Program.cs | 8 +-- .../Views/ShortUrl/AddUrl.cshtml | 11 ---- .../Views/ShortUrl/GetUrl.cshtml | 9 ---- UI/Thoughts.UI.MVC/Views/Url/AddUrl.cshtml | 12 +++++ .../Views/Url/GetAliasById.cshtml | 8 +++ .../Views/Url/GetUrlById.cshtml | 8 +++ .../Views/{ShortUrl => Url}/Test.cshtml | 2 +- .../WebModels/ShortUrlWebModel.cs | 4 +- 9 files changed, 78 insertions(+), 38 deletions(-) delete mode 100644 UI/Thoughts.UI.MVC/Views/ShortUrl/AddUrl.cshtml delete mode 100644 UI/Thoughts.UI.MVC/Views/ShortUrl/GetUrl.cshtml create mode 100644 UI/Thoughts.UI.MVC/Views/Url/AddUrl.cshtml create mode 100644 UI/Thoughts.UI.MVC/Views/Url/GetAliasById.cshtml create mode 100644 UI/Thoughts.UI.MVC/Views/Url/GetUrlById.cshtml rename UI/Thoughts.UI.MVC/Views/{ShortUrl => Url}/Test.cshtml (72%) diff --git a/UI/Thoughts.UI.MVC/Controllers/ShortUrlController.cs b/UI/Thoughts.UI.MVC/Controllers/ShortUrlController.cs index fbfd65c..e410491 100644 --- a/UI/Thoughts.UI.MVC/Controllers/ShortUrlController.cs +++ b/UI/Thoughts.UI.MVC/Controllers/ShortUrlController.cs @@ -4,41 +4,71 @@ namespace Thoughts.UI.MVC.Controllers { - [Route("url")] - public class ShortUrlController : Controller + public class UrlController : Controller { private readonly IShortUrlManager _shortUrlManager; - public ShortUrlController(IShortUrlManager ShortUrlManager) + public UrlController(IShortUrlManager ShortUrlManager) { _shortUrlManager = ShortUrlManager; } + // GET -> https://localhost:5010/url/17D91E667026F10A16241F0784608CF1 [Route("url/{Alias}")] - [HttpGet("{Alias}")] - public async Task GetUrl(string Alias) + [HttpGet] + public async Task RedirectByAlias(string Alias) { - var url=await _shortUrlManager.GetUrlAsync(Alias); - if(url is null) + var url = await _shortUrlManager.GetUrlAsync(Alias); + if (url is null) return NotFound(); return Redirect(url.AbsoluteUri); } + // GET -> https://localhost:5010/url/GetUrlById/10 + [Route("url/GetUrlById/{Id}")] + [HttpGet] + public async Task GetUrlById(int Id) + { + var url = await _shortUrlManager.GetUrlByIdAsync(Id); + if (url is null) + return NotFound(); + return View(url); + } + + // GET -> https://localhost:5010/url/GetAliasById/10 + [Route("url/GetAliasById/{Id}")] + [HttpGet] + public async Task GetAliasById(int Id) + { + var alias = await _shortUrlManager.GetAliasByIdAsync(Id); + if (String.IsNullOrEmpty(alias)) + return NotFound(); + var shortUrl= Url.ActionLink(action: nameof(RedirectByAlias), controller: "url", values: new { Alias = alias }); + return View(model:shortUrl); + } + + // POST -> https://localhost:5010/url/ + [Route("url")] [HttpPost] - public async Task AddUrl(string Url) + public async Task AddUrl(string url) { - var result = await _shortUrlManager.AddUrlAsync(Url); - if (result==0) + var result = await _shortUrlManager.AddUrlAsync(url); + if (result == 0) return BadRequest(); + var getUrl = Url.ActionLink(action:nameof(GetUrlById), controller:"url", values: new { Id=result }); + var getAlias = Url.ActionLink(action: nameof(GetAliasById),controller: "url",values: new { Id = result }); ShortUrlWebModel shortUrlWebModel = new() { Id = result, - OriginalUrl = Url + OriginalUrl = url, + GetUrl= getUrl!, + GetAlias=getAlias! }; return View(shortUrlWebModel); } - [Route("Test")] + // GET -> https://localhost:5010/url/Test + [Route("url/Test")] public IActionResult Test() { return View(); diff --git a/UI/Thoughts.UI.MVC/Program.cs b/UI/Thoughts.UI.MVC/Program.cs index 6f8c56e..0e3753e 100644 --- a/UI/Thoughts.UI.MVC/Program.cs +++ b/UI/Thoughts.UI.MVC/Program.cs @@ -68,10 +68,10 @@ app.UseEndpoints(endpoints => { - endpoints.MapControllerRoute( - name: "shortUrl", - pattern: "url/{Alias?}", - defaults: new { controller = "ShortUrl", action = "GetUrl" }); + //endpoints.MapControllerRoute( + // name: "shortUrl", + // pattern: "url/{Alias?}", + // defaults: new { controller = "ShortUrl", action = "GetUrl" }); endpoints.MapControllerRoute( name: "default", diff --git a/UI/Thoughts.UI.MVC/Views/ShortUrl/AddUrl.cshtml b/UI/Thoughts.UI.MVC/Views/ShortUrl/AddUrl.cshtml deleted file mode 100644 index ed38a73..0000000 --- a/UI/Thoughts.UI.MVC/Views/ShortUrl/AddUrl.cshtml +++ /dev/null @@ -1,11 +0,0 @@ -@model ShortUrlWebModel - -@{ - ViewBag.Title = "Переход по ссылке"; -} - -
-

@Html.RouteLink("Короткая ссылка","shortUrl", new {Alias=@Model.Alias})

- - Исходная ссылка: @Model.OriginalUrl -
diff --git a/UI/Thoughts.UI.MVC/Views/ShortUrl/GetUrl.cshtml b/UI/Thoughts.UI.MVC/Views/ShortUrl/GetUrl.cshtml deleted file mode 100644 index ae8793b..0000000 --- a/UI/Thoughts.UI.MVC/Views/ShortUrl/GetUrl.cshtml +++ /dev/null @@ -1,9 +0,0 @@ -@model Uri? - -@{ - ViewBag.Title = "Переход по ссылке"; -} - -
- @Model -
diff --git a/UI/Thoughts.UI.MVC/Views/Url/AddUrl.cshtml b/UI/Thoughts.UI.MVC/Views/Url/AddUrl.cshtml new file mode 100644 index 0000000..1185da1 --- /dev/null +++ b/UI/Thoughts.UI.MVC/Views/Url/AddUrl.cshtml @@ -0,0 +1,12 @@ +@model ShortUrlWebModel + +@{ + ViewBag.Title = "Переход по ссылке"; +} + +
+

Id созданной ссылки: @Model.Id

+

Исходная ссылка: @Model.OriginalUrl

+

Ссылка для получения Url по Id короткой ссылки: @Model.GetUrl

+

Ссылка для получения короткой ссылки по Id: @Model.GetAlias

+
diff --git a/UI/Thoughts.UI.MVC/Views/Url/GetAliasById.cshtml b/UI/Thoughts.UI.MVC/Views/Url/GetAliasById.cshtml new file mode 100644 index 0000000..ae6ad3d --- /dev/null +++ b/UI/Thoughts.UI.MVC/Views/Url/GetAliasById.cshtml @@ -0,0 +1,8 @@ +@model string? + +@{ + ViewBag.Title = "Переход по ссылке"; +} + +

Короткая ссылка @Model

+ diff --git a/UI/Thoughts.UI.MVC/Views/Url/GetUrlById.cshtml b/UI/Thoughts.UI.MVC/Views/Url/GetUrlById.cshtml new file mode 100644 index 0000000..3cfd2c7 --- /dev/null +++ b/UI/Thoughts.UI.MVC/Views/Url/GetUrlById.cshtml @@ -0,0 +1,8 @@ +@model Uri? + +@{ + ViewBag.Title = "Переход по ссылке"; +} + +

Оригинальная ссылка @Model?.AbsoluteUri

+ diff --git a/UI/Thoughts.UI.MVC/Views/ShortUrl/Test.cshtml b/UI/Thoughts.UI.MVC/Views/Url/Test.cshtml similarity index 72% rename from UI/Thoughts.UI.MVC/Views/ShortUrl/Test.cshtml rename to UI/Thoughts.UI.MVC/Views/Url/Test.cshtml index 6a901ed..b2b18e8 100644 --- a/UI/Thoughts.UI.MVC/Views/ShortUrl/Test.cshtml +++ b/UI/Thoughts.UI.MVC/Views/Url/Test.cshtml @@ -2,7 +2,7 @@ ViewBag.Title = "Проверка POST запроса"; } -
+ diff --git a/UI/Thoughts.UI.MVC/WebModels/ShortUrlWebModel.cs b/UI/Thoughts.UI.MVC/WebModels/ShortUrlWebModel.cs index ed98495..e7dd497 100644 --- a/UI/Thoughts.UI.MVC/WebModels/ShortUrlWebModel.cs +++ b/UI/Thoughts.UI.MVC/WebModels/ShortUrlWebModel.cs @@ -3,6 +3,8 @@ public class ShortUrlWebModel { public int Id { get; set; } - public string OriginalUrl { get; set; } + public string OriginalUrl { get; set; } + public string GetUrl { get; set; } + public string GetAlias { get; set; } } } From a491be7d0c6329d5d758214644b6647c7785005c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B0=D1=81=D0=B8=D0=BB=D0=B8=D0=B9?= Date: Sun, 6 Nov 2022 19:55:10 +0500 Subject: [PATCH 09/12] =?UTF-8?q?=D0=92=20Web=20API=20=D0=BA=D0=BE=D0=BD?= =?UTF-8?q?=D1=82=D1=80=D0=BE=D0=BB=D0=BB=D0=B5=D1=80=D0=B5=20=D0=BC=D0=B5?= =?UTF-8?q?=D1=82=D0=BE=D0=B4=D1=8B=20=D0=B2=20=D1=81=D0=BB=D1=83=D1=87?= =?UTF-8?q?=D0=B0=D0=B5=20=D0=BD=D0=B5=D1=83=D0=B4=D0=B0=D1=87=D0=B8=20?= =?UTF-8?q?=D0=B2=D0=BE=D0=B7=D0=B2=D1=80=D0=B0=D1=89=D0=B0=D1=8E=D1=82=20?= =?UTF-8?q?NotFound.=20=D0=9C=D0=B5=D1=82=D0=BE=D0=B4=20AddUrl=20=D0=BF?= =?UTF-8?q?=D1=80=D0=B8=20=D0=BD=D0=B5=D1=83=D0=B4=D0=B0=D1=87=D0=B5=20?= =?UTF-8?q?=D0=B2=D0=BE=D0=B7=D0=B2=D1=80=D0=B0=D1=89=D0=B0=D0=B5=D1=82=20?= =?UTF-8?q?BadRequest?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/v1/ShortUrlManagerController.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Services/Thoughts.WebAPI/Controllers/v1/ShortUrlManagerController.cs b/Services/Thoughts.WebAPI/Controllers/v1/ShortUrlManagerController.cs index 9a09d62..4e56373 100644 --- a/Services/Thoughts.WebAPI/Controllers/v1/ShortUrlManagerController.cs +++ b/Services/Thoughts.WebAPI/Controllers/v1/ShortUrlManagerController.cs @@ -23,7 +23,7 @@ public async Task> GetUrl(string Alias) { var result = await _shortUrlManager.GetUrlAsync(Alias); if (result is null) - return BadRequest(); + return NotFound(); return result; } @@ -34,7 +34,7 @@ public async Task> GetUrlById(int Id) { var result = await _shortUrlManager.GetUrlByIdAsync(Id); if (result is null) - return BadRequest(); + return NotFound(); return result; } @@ -46,7 +46,7 @@ public async Task> GetAliasById(int Id) var result = await _shortUrlManager.GetAliasByIdAsync(Id); if (string.IsNullOrEmpty(result)) - return BadRequest(); + return NotFound(); return AcceptedAtAction(nameof(GetUrl), new { Alias = result }, result); } @@ -67,7 +67,7 @@ public async Task> AddUrl([FromBody] string Url) public async Task> DeleteUrl(int Id) { var result = await _shortUrlManager.DeleteUrlAsync(Id); - return result ? result : BadRequest(); + return result ? result : NotFound(); } // POST api/v1/url/10 @@ -75,7 +75,7 @@ public async Task> DeleteUrl(int Id) public async Task> UpdateUrl(int Id, [FromBody] string Url) { var result = await _shortUrlManager.UpdateUrlAsync(Id, Url); - return result ? result : BadRequest(); + return result ? result : NotFound(); } } } From 64cf93ff18ec7d67f50bdef74e568f8ccf0f83ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B0=D1=81=D0=B8=D0=BB=D0=B8=D0=B9?= Date: Sun, 6 Nov 2022 21:53:39 +0500 Subject: [PATCH 10/12] =?UTF-8?q?=D0=9E=D0=B3=D1=80=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D1=87=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BA=D0=BE=D0=BB=D0=B8=D1=87?= =?UTF-8?q?=D0=B5=D1=81=D1=82=D0=B2=D0=B0=20=D1=81=D0=B8=D0=BC=D0=B2=D0=BE?= =?UTF-8?q?=D0=BB=D0=BE=D0=B2=20=D0=B4=D0=BB=D1=8F=20=D0=BA=D0=BE=D1=80?= =?UTF-8?q?=D0=BE=D1=82=D0=BA=D0=BE=D0=B9=20=D1=81=D1=81=D1=8B=D0=BB=D0=BA?= =?UTF-8?q?=D0=B8.=20=D0=94=D0=BB=D1=8F=20=D0=BA=D0=BE=D1=80=D0=BE=D1=82?= =?UTF-8?q?=D0=BA=D0=BE=D0=B9=20=D1=81=D1=81=D1=8B=D0=BB=D0=BA=D0=B8=20?= =?UTF-8?q?=D0=B2=20=D0=91=D0=94=20=D1=81=D0=BE=D1=85=D1=80=D0=B0=D0=BD?= =?UTF-8?q?=D1=8F=D0=B5=D1=82=D1=81=D1=8F=20MD5-=D1=85=D0=B5=D1=88=20?= =?UTF-8?q?=D0=B8=D0=B7=2032=20=D1=81=D0=B8=D0=BC=D0=B2=D0=BE=D0=BB=D0=BE?= =?UTF-8?q?=D0=B2=20-=20=D1=8D=D1=82=D0=BE=20=D1=8F=D0=B2=D0=BB=D1=8F?= =?UTF-8?q?=D0=B5=D1=82=D1=81=D1=8F=20=D0=BF=D1=81=D0=B5=D0=B2=D0=B4=D0=BE?= =?UTF-8?q?=D0=BD=D0=B8=D0=BC=D0=BE=D0=BC=20=D1=81=D1=81=D1=8B=D0=BB=D0=BA?= =?UTF-8?q?=D0=B8.=20=D0=94=D0=BB=D1=8F=20=D0=BF=D0=BE=D0=BB=D1=83=D1=87?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BE=D1=80=D0=B8=D0=B3=D0=B8=D0=BD?= =?UTF-8?q?=D0=B0=D0=BB=D1=8C=D0=BD=D0=BE=D0=B9=20=D1=81=D1=81=D1=8B=D0=BB?= =?UTF-8?q?=D0=BA=D0=B8=20=D0=B2=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE=D1=81?= =?UTF-8?q?=D0=B5=20=D0=BA=20WebAPI=20=D0=BC=D0=BE=D0=B6=D0=BD=D0=BE=20?= =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D0=B4=D0=B0=D0=B2=D0=B0=D1=82=D1=8C=20?= =?UTF-8?q?=D0=BA=D0=B0=D0=BA=20=D0=BF=D1=81=D0=B5=D0=B2=D0=B4=D0=BE=D0=BD?= =?UTF-8?q?=D0=B8=D0=BC=20=D1=86=D0=B5=D0=BB=D0=B8=D0=BA=D0=BE=D0=BC=20(ap?= =?UTF-8?q?i/v1/url=3FAlias=3DD0D16672757FADB7FA0056C62114C533),=20=D1=82?= =?UTF-8?q?=D0=B0=D0=BA=20=D0=B8=20=D0=BD=D0=B0=D1=87=D0=B0=D0=BB=D1=8C?= =?UTF-8?q?=D0=BD=D1=83=D1=8E=20=D0=B5=D0=B3=D0=BE=20=D1=87=D0=B0=D1=81?= =?UTF-8?q?=D1=82=D1=8C=20(api/v1/url=3FAlias=3DD0D16),=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=B8=D1=81=D0=BA=20=D0=B2=20=D0=91=D0=94=20=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D0=B8=D1=81=D1=85=D0=BE=D0=B4=D0=B8=D1=82=20=D0=BF=D0=BE=20?= =?UTF-8?q?=D1=81=D0=BE=D0=B2=D0=BF=D0=B0=D0=B4=D0=B5=D0=BD=D0=B8=D1=8E=20?= =?UTF-8?q?=D0=BD=D0=B0=D1=87=D0=B0=D0=BB=D0=B0=20=D1=81=D1=82=D1=80=D0=BE?= =?UTF-8?q?=D0=BA=D0=B8.=20=D0=9F=D1=80=D0=B8=20=D0=B3=D0=B5=D0=BD=D0=B5?= =?UTF-8?q?=D1=80=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B8=20=D0=BF?= =?UTF-8?q?=D1=81=D0=B5=D0=B2=D0=B4=D0=BE=D0=BD=D0=B8=D0=BC=D0=B0,=20?= =?UTF-8?q?=D0=BF=D0=BE=20=D1=83=D0=BC=D0=BE=D0=BB=D1=87=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D1=8E=20=D0=B2=D0=BE=D0=B7=D0=B2=D1=80=D0=B0=D1=89=D0=B0=D0=B5?= =?UTF-8?q?=D1=82=D1=81=D1=8F=20=D0=BF=D0=BE=D0=BB=D0=BD=D1=8B=D0=B9=20?= =?UTF-8?q?=D0=BF=D1=81=D0=B5=D0=B2=D0=B4=D0=BE=D0=BD=D0=B8=D0=BC=2032=20?= =?UTF-8?q?=D1=81=D0=B8=D0=BC=D0=B2=D0=BE=D0=BB=D0=B0.=20=D0=95=D1=81?= =?UTF-8?q?=D0=BB=D0=B8=20=D1=82=D0=B0=D0=BA=D0=B0=D1=8F=20=D0=B4=D0=BB?= =?UTF-8?q?=D0=B8=D0=BD=D0=B0=20=D0=B8=D0=B7=D0=B1=D1=8B=D1=82=D0=BE=D1=87?= =?UTF-8?q?=D0=BD=D0=B0,=20=D1=82=D0=BE=20=D0=B5=D0=B5=20=D0=BC=D0=BE?= =?UTF-8?q?=D0=B6=D0=BD=D0=BE=20=D0=BE=D0=B3=D1=80=D0=B0=D0=BD=D0=B8=D1=87?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20=D1=83=D0=BA=D0=B0=D0=B7=D0=B0=D0=B2=20?= =?UTF-8?q?=D0=BF=D0=B0=D1=80=D0=B0=D0=BC=D0=B5=D1=82=D1=80=20"ShortUrlMax?= =?UTF-8?q?Length"=20=D0=B2=20=D1=84=D0=B0=D0=B9=D0=BB=D0=B5=20appsettings?= =?UTF-8?q?.json.=20=D0=9B=D0=B8=D0=B1=D0=BE=20=D1=8D=D1=82=D0=BE=20=D0=BE?= =?UTF-8?q?=D0=B3=D1=80=D0=B0=D0=BD=D0=B8=D1=87=D0=B5=D0=BD=D0=B8=D0=B5=20?= =?UTF-8?q?=D0=BC=D0=BE=D0=B6=D0=BD=D0=BE=20=D1=83=D0=BA=D0=B0=D0=B7=D0=B0?= =?UTF-8?q?=D1=82=D1=8C=20=D0=B2=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE=D1=81?= =?UTF-8?q?=D0=B5=20=D0=BA=20WebAPI=20(api/v1/url/alias/1084=3FLength=3D7)?= =?UTF-8?q?.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../IShortUrlManager.cs | 3 ++- .../InSQL/SqlShortUrlManagerService.cs | 9 ++++++--- .../ShortUrl/ShortUrlClient.cs | 4 ++-- .../v1/ShortUrlManagerController.cs | 18 +++++++++++++---- Services/Thoughts.WebAPI/Program.cs | 2 +- Services/Thoughts.WebAPI/appsettings.json | 3 ++- .../Controllers/ShortUrlController.cs | 20 ++++++++++--------- 7 files changed, 38 insertions(+), 21 deletions(-) diff --git a/Services/Thoughts.Interfaces.Base/IShortUrlManager.cs b/Services/Thoughts.Interfaces.Base/IShortUrlManager.cs index 0a3bf1e..9343bb7 100644 --- a/Services/Thoughts.Interfaces.Base/IShortUrlManager.cs +++ b/Services/Thoughts.Interfaces.Base/IShortUrlManager.cs @@ -28,9 +28,10 @@ public interface IShortUrlManager /// Получить псевдоним короткой ссылки по ее идентификатору /// /// Идентификатор короткой ссылки + /// Количество символов в возвращаемом псевдониме /// Токен отмены /// Псевдоним ссылки - Task GetAliasByIdAsync(int Id, CancellationToken Cancel = default); + Task GetAliasByIdAsync(int Id, int Length = 0, CancellationToken Cancel = default); /// /// Добавить короткую ссылку diff --git a/Services/Thoughts.Services/InSQL/SqlShortUrlManagerService.cs b/Services/Thoughts.Services/InSQL/SqlShortUrlManagerService.cs index 9995fab..2c30c74 100644 --- a/Services/Thoughts.Services/InSQL/SqlShortUrlManagerService.cs +++ b/Services/Thoughts.Services/InSQL/SqlShortUrlManagerService.cs @@ -101,7 +101,7 @@ public async Task DeleteUrlAsync(int Id, CancellationToken Cancel = defaul { var result = await _db.ShortUrls. FirstOrDefaultAsync( - u => u.Alias == Alias, + u => u.Alias.StartsWith(Alias), Cancel ). ConfigureAwait(false); @@ -124,7 +124,7 @@ public async Task DeleteUrlAsync(int Id, CancellationToken Cancel = defaul return result.OriginalUrl; } - public async Task GetAliasByIdAsync(int Id, CancellationToken Cancel = default) + public async Task GetAliasByIdAsync(int Id, int Length, CancellationToken Cancel = default) { var result = await _db.ShortUrls. FirstOrDefaultAsync( @@ -133,7 +133,10 @@ public async Task GetAliasByIdAsync(int Id, CancellationToken Cancel = d ). ConfigureAwait(false); if (result is null) - return null ; + return null; + + if (Length>0) + return result.Alias.Substring(0, result.Alias.Length < Length ? result.Alias.Length : Length); return result.Alias; } diff --git a/Services/Thoughts.WebAPI.Clients/ShortUrl/ShortUrlClient.cs b/Services/Thoughts.WebAPI.Clients/ShortUrl/ShortUrlClient.cs index 9c2e4e9..103fe81 100644 --- a/Services/Thoughts.WebAPI.Clients/ShortUrl/ShortUrlClient.cs +++ b/Services/Thoughts.WebAPI.Clients/ShortUrl/ShortUrlClient.cs @@ -30,9 +30,9 @@ public async Task DeleteUrlAsync(int Id, CancellationToken Cancel = defaul return response.IsSuccessStatusCode; } - public async Task GetAliasByIdAsync(int Id, CancellationToken Cancel = default) + public async Task GetAliasByIdAsync(int Id, int Length, CancellationToken Cancel = default) { - var response = await GetAsync($"{WebApiControllersPath.ShortUrlV1}/alias/{Id}"); + var response = await GetAsync($"{WebApiControllersPath.ShortUrlV1}/alias/{Id}?Length={Length}"); return response; } diff --git a/Services/Thoughts.WebAPI/Controllers/v1/ShortUrlManagerController.cs b/Services/Thoughts.WebAPI/Controllers/v1/ShortUrlManagerController.cs index 4e56373..715ac92 100644 --- a/Services/Thoughts.WebAPI/Controllers/v1/ShortUrlManagerController.cs +++ b/Services/Thoughts.WebAPI/Controllers/v1/ShortUrlManagerController.cs @@ -11,10 +11,12 @@ namespace Thoughts.WebAPI.Controllers.v1 public class ShortUrlManagerController : ControllerBase { private readonly IShortUrlManager _shortUrlManager; + private readonly IConfiguration _configuration; - public ShortUrlManagerController(IShortUrlManager ShortUrlManager) + public ShortUrlManagerController(IShortUrlManager ShortUrlManager, IConfiguration Configuration) { _shortUrlManager = ShortUrlManager; + _configuration = Configuration; } // GET: api/v1/url?Alias=... @@ -39,11 +41,19 @@ public async Task> GetUrlById(int Id) return result; } - //GET: api/v1/url/alias/10 + //GET: api/v1/url/alias/10?Length=10 [HttpGet("alias/{Id}")] - public async Task> GetAliasById(int Id) + public async Task> GetAliasById(int Id, int Length) { - var result = await _shortUrlManager.GetAliasByIdAsync(Id); + string dd = _configuration["ShortUrlMaxLength"]; + string result;// + if (Length > 0) + result = await _shortUrlManager.GetAliasByIdAsync(Id, Length); + else if (int.TryParse(_configuration["ShortUrlMaxLength"], out int lengthFromConfig)) + result = await _shortUrlManager.GetAliasByIdAsync(Id, lengthFromConfig); + else + result = await _shortUrlManager.GetAliasByIdAsync(Id); + if (string.IsNullOrEmpty(result)) return NotFound(); diff --git a/Services/Thoughts.WebAPI/Program.cs b/Services/Thoughts.WebAPI/Program.cs index 33efce8..95e66d0 100644 --- a/Services/Thoughts.WebAPI/Program.cs +++ b/Services/Thoughts.WebAPI/Program.cs @@ -38,7 +38,7 @@ switch (db_type) { - default: throw new InvalidOperationException($"Òèï ÁÄ {db_type} íå ïîääåðæèâàåòñÿ"); + default: throw new InvalidOperationException($"Тип БД {db_type} не поддерживается"); case "Sqlite": services.AddThoughtsDbSqlite(configuration.GetConnectionString("Sqlite")); diff --git a/Services/Thoughts.WebAPI/appsettings.json b/Services/Thoughts.WebAPI/appsettings.json index 798c019..b60c27e 100644 --- a/Services/Thoughts.WebAPI/appsettings.json +++ b/Services/Thoughts.WebAPI/appsettings.json @@ -14,5 +14,6 @@ "Microsoft.AspNetCore": "Warning" } }, - "AllowedHosts": "*" + "AllowedHosts": "*", + "ShortUrlMaxLength": 5 } diff --git a/UI/Thoughts.UI.MVC/Controllers/ShortUrlController.cs b/UI/Thoughts.UI.MVC/Controllers/ShortUrlController.cs index e410491..6f8780d 100644 --- a/UI/Thoughts.UI.MVC/Controllers/ShortUrlController.cs +++ b/UI/Thoughts.UI.MVC/Controllers/ShortUrlController.cs @@ -35,16 +35,18 @@ public async Task GetUrlById(int Id) return View(url); } - // GET -> https://localhost:5010/url/GetAliasById/10 + // GET -> https://localhost:5010/url/GetAliasById/10?Length=6 [Route("url/GetAliasById/{Id}")] [HttpGet] - public async Task GetAliasById(int Id) + public async Task GetAliasById(int Id, int Length) { - var alias = await _shortUrlManager.GetAliasByIdAsync(Id); + var alias = Length > 0 + ? await _shortUrlManager.GetAliasByIdAsync(Id, Length) + : await _shortUrlManager.GetAliasByIdAsync(Id); if (String.IsNullOrEmpty(alias)) return NotFound(); - var shortUrl= Url.ActionLink(action: nameof(RedirectByAlias), controller: "url", values: new { Alias = alias }); - return View(model:shortUrl); + var shortUrl = Url.ActionLink(action: nameof(RedirectByAlias), controller: "url", values: new { Alias = alias }); + return View(model: shortUrl); } // POST -> https://localhost:5010/url/ @@ -55,14 +57,14 @@ public async Task AddUrl(string url) var result = await _shortUrlManager.AddUrlAsync(url); if (result == 0) return BadRequest(); - var getUrl = Url.ActionLink(action:nameof(GetUrlById), controller:"url", values: new { Id=result }); - var getAlias = Url.ActionLink(action: nameof(GetAliasById),controller: "url",values: new { Id = result }); + var getUrl = Url.ActionLink(action: nameof(GetUrlById), controller: "url", values: new { Id = result }); + var getAlias = Url.ActionLink(action: nameof(GetAliasById), controller: "url", values: new { Id = result }); ShortUrlWebModel shortUrlWebModel = new() { Id = result, OriginalUrl = url, - GetUrl= getUrl!, - GetAlias=getAlias! + GetUrl = getUrl!, + GetAlias = getAlias! }; return View(shortUrlWebModel); } From 75e79b85c3f8cb0e801bfe7e6c32a4285864ec66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B0=D1=81=D0=B8=D0=BB=D0=B8=D0=B9?= Date: Wed, 9 Nov 2022 20:42:21 +0500 Subject: [PATCH 11/12] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D1=81=D1=82=D0=B0=D1=82=D0=B8=D1=81=D1=82?= =?UTF-8?q?=D0=B8=D0=BA=D0=B0=20=D0=BF=D0=BE=20=D0=B8=D1=81=D0=BF=D0=BE?= =?UTF-8?q?=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D1=8E=20=D0=BA?= =?UTF-8?q?=D0=BE=D1=80=D0=BE=D1=82=D0=BA=D0=B8=D1=85=20=D1=81=D1=81=D1=8B?= =?UTF-8?q?=D0=BB=D0=BE=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Thoughts.Domain.Base/Entities/ShortUrl.cs | 28 ++ Data/Thoughts.DAL.Entities/ShortUrl.cs | 8 + ...221108160559_ShortUrlStatistic.Designer.cs | 364 ++++++++++++++++++ .../20221108160559_ShortUrlStatistic.cs | 38 ++ .../Migrations/ThoughtsDBModelSnapshot.cs | 6 + Data/Thoughts.DAL.SqlServer/Registrator.cs | 2 +- ...221108160716_ShortUrlStatistic.Designer.cs | 346 +++++++++++++++++ .../20221108160716_ShortUrlStatistic.cs | 38 ++ .../Migrations/ThoughtsDBModelSnapshot.cs | 6 + .../IShortUrlManager.cs | 20 +- .../InSQL/SqlShortUrlManagerService.cs | 87 ++++- .../ShortUrl/ShortUrlClient.cs | 14 + .../v1/ShortUrlManagerController.cs | 37 +- Services/Thoughts.WebAPI/Program.cs | 1 - ...ShortUrlController.cs => UrlController.cs} | 34 +- UI/Thoughts.UI.MVC/Program.cs | 1 - .../Shared/Components/LeftMenu/Default.cshtml | 7 + UI/Thoughts.UI.MVC/Views/Url/AddUrl.cshtml | 2 +- .../Url/{Test.cshtml => CreateUrl.cshtml} | 0 UI/Thoughts.UI.MVC/Views/Url/Statistic.cshtml | 41 ++ .../WebModels/ShortUrlStatisticWebModel.cs | 11 + 21 files changed, 1075 insertions(+), 16 deletions(-) create mode 100644 Common/Thoughts.Domain.Base/Entities/ShortUrl.cs create mode 100644 Data/Thoughts.DAL.SqlServer/Migrations/20221108160559_ShortUrlStatistic.Designer.cs create mode 100644 Data/Thoughts.DAL.SqlServer/Migrations/20221108160559_ShortUrlStatistic.cs create mode 100644 Data/Thoughts.DAL.Sqlite/Migrations/20221108160716_ShortUrlStatistic.Designer.cs create mode 100644 Data/Thoughts.DAL.Sqlite/Migrations/20221108160716_ShortUrlStatistic.cs rename Services/{Thoughts.Interfaces.Base => Thoughts.Interfaces}/IShortUrlManager.cs (69%) rename UI/Thoughts.UI.MVC/Controllers/{ShortUrlController.cs => UrlController.cs} (70%) rename UI/Thoughts.UI.MVC/Views/Url/{Test.cshtml => CreateUrl.cshtml} (100%) create mode 100644 UI/Thoughts.UI.MVC/Views/Url/Statistic.cshtml create mode 100644 UI/Thoughts.UI.MVC/WebModels/ShortUrlStatisticWebModel.cs diff --git a/Common/Thoughts.Domain.Base/Entities/ShortUrl.cs b/Common/Thoughts.Domain.Base/Entities/ShortUrl.cs new file mode 100644 index 0000000..82305a2 --- /dev/null +++ b/Common/Thoughts.Domain.Base/Entities/ShortUrl.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Thoughts.Domain.Base.Entities +{ + public class ShortUrl : EntityModel + { + /// Оригинальный URL + [Required] + public Uri OriginalUrl { get; set; } + + /// Псевдоним ссылки + [Required] + public string Alias { get; set; } + + /// Количество запросов ссылки + [Required] + public int Statistic { get; set; } + + /// Дата и время последего сброса статистики + [Required] + public DateTimeOffset LastReset { get; set; } + } +} diff --git a/Data/Thoughts.DAL.Entities/ShortUrl.cs b/Data/Thoughts.DAL.Entities/ShortUrl.cs index da9e13b..84fc589 100644 --- a/Data/Thoughts.DAL.Entities/ShortUrl.cs +++ b/Data/Thoughts.DAL.Entities/ShortUrl.cs @@ -17,5 +17,13 @@ public class ShortUrl:Entity /// Псевдоним ссылки [Required] public string Alias { get; set; } + + /// Количество запросов ссылки + [Required] + public int Statistic { get; set; } + + /// Дата и время последего сброса статистики + [Required] + public DateTimeOffset LastReset { get; set; } } } diff --git a/Data/Thoughts.DAL.SqlServer/Migrations/20221108160559_ShortUrlStatistic.Designer.cs b/Data/Thoughts.DAL.SqlServer/Migrations/20221108160559_ShortUrlStatistic.Designer.cs new file mode 100644 index 0000000..260830f --- /dev/null +++ b/Data/Thoughts.DAL.SqlServer/Migrations/20221108160559_ShortUrlStatistic.Designer.cs @@ -0,0 +1,364 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Thoughts.DAL; + +#nullable disable + +namespace Thoughts.DAL.SqlServer.Migrations +{ + [DbContext(typeof(ThoughtsDB))] + [Migration("20221108160559_ShortUrlStatistic")] + partial class ShortUrlStatistic + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.6") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); + + modelBuilder.Entity("PostTag", b => + { + b.Property("PostsId") + .HasColumnType("int"); + + b.Property("TagsId") + .HasColumnType("int"); + + b.HasKey("PostsId", "TagsId"); + + b.HasIndex("TagsId"); + + b.ToTable("PostTag"); + }); + + modelBuilder.Entity("RoleUser", b => + { + b.Property("RolesId") + .HasColumnType("int"); + + b.Property("UsersId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("RolesId", "UsersId"); + + b.HasIndex("UsersId"); + + b.ToTable("RoleUser"); + }); + + modelBuilder.Entity("Thoughts.DAL.Entities.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex(new[] { "Name" }, "NameIndex") + .IsUnique(); + + b.ToTable("Categories"); + }); + + modelBuilder.Entity("Thoughts.DAL.Entities.Comment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("Body") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Date") + .HasColumnType("datetimeoffset"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("ParentCommentId") + .HasColumnType("int"); + + b.Property("PostId") + .HasColumnType("int"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("ParentCommentId"); + + b.HasIndex("PostId"); + + b.HasIndex("UserId"); + + b.ToTable("Comments"); + }); + + modelBuilder.Entity("Thoughts.DAL.Entities.Post", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("Body") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CategoryId") + .HasColumnType("int"); + + b.Property("Date") + .HasColumnType("datetimeoffset"); + + b.Property("PublicationDate") + .HasColumnType("datetimeoffset"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("UserId"); + + b.ToTable("Posts"); + }); + + modelBuilder.Entity("Thoughts.DAL.Entities.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex(new[] { "Name" }, "NameIndex") + .IsUnique() + .HasDatabaseName("NameIndex1"); + + b.ToTable("Roles"); + }); + + modelBuilder.Entity("Thoughts.DAL.Entities.ShortUrl", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("Alias") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("LastReset") + .HasColumnType("datetimeoffset"); + + b.Property("OriginalUrl") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Statistic") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("ShortUrls"); + }); + + modelBuilder.Entity("Thoughts.DAL.Entities.Tag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex(new[] { "Name" }, "NameIndex") + .IsUnique() + .HasDatabaseName("NameIndex2"); + + b.ToTable("Tags"); + }); + + modelBuilder.Entity("Thoughts.DAL.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(450)"); + + b.Property("Birthday") + .HasColumnType("date"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("NickName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Patronymic") + .HasColumnType("nvarchar(450)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex(new[] { "LastName", "FirstName", "Patronymic" }, "NameIndex") + .IsUnique() + .HasDatabaseName("NameIndex3") + .HasFilter("[Patronymic] IS NOT NULL"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("PostTag", b => + { + b.HasOne("Thoughts.DAL.Entities.Post", null) + .WithMany() + .HasForeignKey("PostsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Thoughts.DAL.Entities.Tag", null) + .WithMany() + .HasForeignKey("TagsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("RoleUser", b => + { + b.HasOne("Thoughts.DAL.Entities.Role", null) + .WithMany() + .HasForeignKey("RolesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Thoughts.DAL.Entities.User", null) + .WithMany() + .HasForeignKey("UsersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Thoughts.DAL.Entities.Comment", b => + { + b.HasOne("Thoughts.DAL.Entities.Comment", "ParentComment") + .WithMany("ChildrenComment") + .HasForeignKey("ParentCommentId"); + + b.HasOne("Thoughts.DAL.Entities.Post", "Post") + .WithMany("Comments") + .HasForeignKey("PostId") + .OnDelete(DeleteBehavior.ClientNoAction) + .IsRequired(); + + b.HasOne("Thoughts.DAL.Entities.User", "User") + .WithMany("Comments") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.ClientNoAction) + .IsRequired(); + + b.Navigation("ParentComment"); + + b.Navigation("Post"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Thoughts.DAL.Entities.Post", b => + { + b.HasOne("Thoughts.DAL.Entities.Category", "Category") + .WithMany("Posts") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Thoughts.DAL.Entities.User", "User") + .WithMany("Posts") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Thoughts.DAL.Entities.Category", b => + { + b.Navigation("Posts"); + }); + + modelBuilder.Entity("Thoughts.DAL.Entities.Comment", b => + { + b.Navigation("ChildrenComment"); + }); + + modelBuilder.Entity("Thoughts.DAL.Entities.Post", b => + { + b.Navigation("Comments"); + }); + + modelBuilder.Entity("Thoughts.DAL.Entities.User", b => + { + b.Navigation("Comments"); + + b.Navigation("Posts"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Data/Thoughts.DAL.SqlServer/Migrations/20221108160559_ShortUrlStatistic.cs b/Data/Thoughts.DAL.SqlServer/Migrations/20221108160559_ShortUrlStatistic.cs new file mode 100644 index 0000000..389617e --- /dev/null +++ b/Data/Thoughts.DAL.SqlServer/Migrations/20221108160559_ShortUrlStatistic.cs @@ -0,0 +1,38 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Thoughts.DAL.SqlServer.Migrations +{ + public partial class ShortUrlStatistic : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "LastReset", + table: "ShortUrls", + type: "datetimeoffset", + nullable: false, + defaultValue: new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0))); + + migrationBuilder.AddColumn( + name: "Statistic", + table: "ShortUrls", + type: "int", + nullable: false, + defaultValue: 0); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "LastReset", + table: "ShortUrls"); + + migrationBuilder.DropColumn( + name: "Statistic", + table: "ShortUrls"); + } + } +} diff --git a/Data/Thoughts.DAL.SqlServer/Migrations/ThoughtsDBModelSnapshot.cs b/Data/Thoughts.DAL.SqlServer/Migrations/ThoughtsDBModelSnapshot.cs index ffdf0fe..0f4bbcb 100644 --- a/Data/Thoughts.DAL.SqlServer/Migrations/ThoughtsDBModelSnapshot.cs +++ b/Data/Thoughts.DAL.SqlServer/Migrations/ThoughtsDBModelSnapshot.cs @@ -188,10 +188,16 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("nvarchar(max)"); + b.Property("LastReset") + .HasColumnType("datetimeoffset"); + b.Property("OriginalUrl") .IsRequired() .HasColumnType("nvarchar(max)"); + b.Property("Statistic") + .HasColumnType("int"); + b.HasKey("Id"); b.ToTable("ShortUrls"); diff --git a/Data/Thoughts.DAL.SqlServer/Registrator.cs b/Data/Thoughts.DAL.SqlServer/Registrator.cs index 0701398..21d1355 100644 --- a/Data/Thoughts.DAL.SqlServer/Registrator.cs +++ b/Data/Thoughts.DAL.SqlServer/Registrator.cs @@ -9,7 +9,7 @@ public static IServiceCollection AddThoughtsDbSqlServer(this IServiceCollection { services.AddDbContext(opt => opt .UseSqlServer( - ConnectionString, + ConnectionString, o => o.MigrationsAssembly(typeof(Registrator).Assembly.FullName))); return services; diff --git a/Data/Thoughts.DAL.Sqlite/Migrations/20221108160716_ShortUrlStatistic.Designer.cs b/Data/Thoughts.DAL.Sqlite/Migrations/20221108160716_ShortUrlStatistic.Designer.cs new file mode 100644 index 0000000..42cda4d --- /dev/null +++ b/Data/Thoughts.DAL.Sqlite/Migrations/20221108160716_ShortUrlStatistic.Designer.cs @@ -0,0 +1,346 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Thoughts.DAL; + +#nullable disable + +namespace Thoughts.DAL.Sqlite.Migrations +{ + [DbContext(typeof(ThoughtsDB))] + [Migration("20221108160716_ShortUrlStatistic")] + partial class ShortUrlStatistic + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "6.0.6"); + + modelBuilder.Entity("PostTag", b => + { + b.Property("PostsId") + .HasColumnType("INTEGER"); + + b.Property("TagsId") + .HasColumnType("INTEGER"); + + b.HasKey("PostsId", "TagsId"); + + b.HasIndex("TagsId"); + + b.ToTable("PostTag"); + }); + + modelBuilder.Entity("RoleUser", b => + { + b.Property("RolesId") + .HasColumnType("INTEGER"); + + b.Property("UsersId") + .HasColumnType("TEXT"); + + b.HasKey("RolesId", "UsersId"); + + b.HasIndex("UsersId"); + + b.ToTable("RoleUser"); + }); + + modelBuilder.Entity("Thoughts.DAL.Entities.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex(new[] { "Name" }, "NameIndex") + .IsUnique(); + + b.ToTable("Categories"); + }); + + modelBuilder.Entity("Thoughts.DAL.Entities.Comment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Body") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.Property("ParentCommentId") + .HasColumnType("INTEGER"); + + b.Property("PostId") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ParentCommentId"); + + b.HasIndex("PostId"); + + b.HasIndex("UserId"); + + b.ToTable("Comments"); + }); + + modelBuilder.Entity("Thoughts.DAL.Entities.Post", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Body") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CategoryId") + .HasColumnType("INTEGER"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("PublicationDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("UserId"); + + b.ToTable("Posts"); + }); + + modelBuilder.Entity("Thoughts.DAL.Entities.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex(new[] { "Name" }, "NameIndex") + .IsUnique() + .HasDatabaseName("NameIndex1"); + + b.ToTable("Roles"); + }); + + modelBuilder.Entity("Thoughts.DAL.Entities.ShortUrl", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Alias") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("LastReset") + .HasColumnType("TEXT"); + + b.Property("OriginalUrl") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Statistic") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("ShortUrls"); + }); + + modelBuilder.Entity("Thoughts.DAL.Entities.Tag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex(new[] { "Name" }, "NameIndex") + .IsUnique() + .HasDatabaseName("NameIndex2"); + + b.ToTable("Tags"); + }); + + modelBuilder.Entity("Thoughts.DAL.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Birthday") + .HasColumnType("date"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("NickName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Patronymic") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex(new[] { "LastName", "FirstName", "Patronymic" }, "NameIndex") + .IsUnique() + .HasDatabaseName("NameIndex3"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("PostTag", b => + { + b.HasOne("Thoughts.DAL.Entities.Post", null) + .WithMany() + .HasForeignKey("PostsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Thoughts.DAL.Entities.Tag", null) + .WithMany() + .HasForeignKey("TagsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("RoleUser", b => + { + b.HasOne("Thoughts.DAL.Entities.Role", null) + .WithMany() + .HasForeignKey("RolesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Thoughts.DAL.Entities.User", null) + .WithMany() + .HasForeignKey("UsersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Thoughts.DAL.Entities.Comment", b => + { + b.HasOne("Thoughts.DAL.Entities.Comment", "ParentComment") + .WithMany("ChildrenComment") + .HasForeignKey("ParentCommentId"); + + b.HasOne("Thoughts.DAL.Entities.Post", "Post") + .WithMany("Comments") + .HasForeignKey("PostId") + .OnDelete(DeleteBehavior.ClientNoAction) + .IsRequired(); + + b.HasOne("Thoughts.DAL.Entities.User", "User") + .WithMany("Comments") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.ClientNoAction) + .IsRequired(); + + b.Navigation("ParentComment"); + + b.Navigation("Post"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Thoughts.DAL.Entities.Post", b => + { + b.HasOne("Thoughts.DAL.Entities.Category", "Category") + .WithMany("Posts") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Thoughts.DAL.Entities.User", "User") + .WithMany("Posts") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Thoughts.DAL.Entities.Category", b => + { + b.Navigation("Posts"); + }); + + modelBuilder.Entity("Thoughts.DAL.Entities.Comment", b => + { + b.Navigation("ChildrenComment"); + }); + + modelBuilder.Entity("Thoughts.DAL.Entities.Post", b => + { + b.Navigation("Comments"); + }); + + modelBuilder.Entity("Thoughts.DAL.Entities.User", b => + { + b.Navigation("Comments"); + + b.Navigation("Posts"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Data/Thoughts.DAL.Sqlite/Migrations/20221108160716_ShortUrlStatistic.cs b/Data/Thoughts.DAL.Sqlite/Migrations/20221108160716_ShortUrlStatistic.cs new file mode 100644 index 0000000..1164e3c --- /dev/null +++ b/Data/Thoughts.DAL.Sqlite/Migrations/20221108160716_ShortUrlStatistic.cs @@ -0,0 +1,38 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Thoughts.DAL.Sqlite.Migrations +{ + public partial class ShortUrlStatistic : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "LastReset", + table: "ShortUrls", + type: "TEXT", + nullable: false, + defaultValue: new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0))); + + migrationBuilder.AddColumn( + name: "Statistic", + table: "ShortUrls", + type: "INTEGER", + nullable: false, + defaultValue: 0); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "LastReset", + table: "ShortUrls"); + + migrationBuilder.DropColumn( + name: "Statistic", + table: "ShortUrls"); + } + } +} diff --git a/Data/Thoughts.DAL.Sqlite/Migrations/ThoughtsDBModelSnapshot.cs b/Data/Thoughts.DAL.Sqlite/Migrations/ThoughtsDBModelSnapshot.cs index 6856974..c2e93b8 100644 --- a/Data/Thoughts.DAL.Sqlite/Migrations/ThoughtsDBModelSnapshot.cs +++ b/Data/Thoughts.DAL.Sqlite/Migrations/ThoughtsDBModelSnapshot.cs @@ -173,10 +173,16 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("TEXT"); + b.Property("LastReset") + .HasColumnType("TEXT"); + b.Property("OriginalUrl") .IsRequired() .HasColumnType("TEXT"); + b.Property("Statistic") + .HasColumnType("INTEGER"); + b.HasKey("Id"); b.ToTable("ShortUrls"); diff --git a/Services/Thoughts.Interfaces.Base/IShortUrlManager.cs b/Services/Thoughts.Interfaces/IShortUrlManager.cs similarity index 69% rename from Services/Thoughts.Interfaces.Base/IShortUrlManager.cs rename to Services/Thoughts.Interfaces/IShortUrlManager.cs index 9343bb7..6179e6b 100644 --- a/Services/Thoughts.Interfaces.Base/IShortUrlManager.cs +++ b/Services/Thoughts.Interfaces/IShortUrlManager.cs @@ -4,7 +4,9 @@ using System.Text; using System.Threading.Tasks; -namespace Thoughts.Interfaces.Base +using Thoughts.Domain.Base.Entities; + +namespace Thoughts.Interfaces { public interface IShortUrlManager { @@ -57,5 +59,21 @@ public interface IShortUrlManager /// Токен отмены /// Результат обновления Task UpdateUrlAsync(int Id, string Url, CancellationToken Cancel = default); + + /// + /// Сброс статистики использования коротких ссылок + /// + /// Идентификатор короткой ссылки для которой сбрасывается статистика. + /// Если идентификатор 0, то статистика сбрасывается для всех ссылок + /// Результат сброса статистики + Task ResetStatistic(int Id = 0, CancellationToken Cancel = default); + + /// + /// Получение статистики использования коротких ссылок + /// + /// Идентификатор короткой ссылки для которой запрашивается статистика. + /// Если идентификатор 0, то статистика запрашивается для всех ссылок + /// Перечисление коротких ссылок + Task> GetStatistic(int Id = 0, int Length = 0, CancellationToken Cancel = default); } } diff --git a/Services/Thoughts.Services/InSQL/SqlShortUrlManagerService.cs b/Services/Thoughts.Services/InSQL/SqlShortUrlManagerService.cs index 2c30c74..0c4ae74 100644 --- a/Services/Thoughts.Services/InSQL/SqlShortUrlManagerService.cs +++ b/Services/Thoughts.Services/InSQL/SqlShortUrlManagerService.cs @@ -4,13 +4,15 @@ using System.Linq; using System.Security.Cryptography; using System.Text; +using System.Threading.Channels; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Thoughts.DAL; -using Thoughts.Interfaces.Base; +using Thoughts.Domain.Base.Entities; +using Thoughts.Interfaces; namespace Thoughts.Services.InSQL { @@ -52,7 +54,9 @@ public async Task AddUrlAsync(string UrlString, CancellationToken Cancel = shortUrl = new() { OriginalUrl = url, - Alias = GenerateAlias(url.OriginalString) + Alias = GenerateAlias(url.OriginalString), + Statistic = 0, + LastReset = DateTime.UtcNow }; await _db.ShortUrls.AddAsync(shortUrl, Cancel).ConfigureAwait(false); await _db.SaveChangesAsync(Cancel).ConfigureAwait(false); @@ -108,6 +112,9 @@ public async Task DeleteUrlAsync(int Id, CancellationToken Cancel = defaul if (result is null) return null; + result.Statistic++; + await _db.SaveChangesAsync(Cancel).ConfigureAwait(false); + return result.OriginalUrl; } public async Task GetUrlByIdAsync(int Id, CancellationToken Cancel = default) @@ -135,7 +142,7 @@ public async Task GetAliasByIdAsync(int Id, int Length, CancellationToke if (result is null) return null; - if (Length>0) + if (Length > 0) return result.Alias.Substring(0, result.Alias.Length < Length ? result.Alias.Length : Length); return result.Alias; @@ -184,6 +191,80 @@ public async Task UpdateUrlAsync(int Id, string UrlString, CancellationTok return true; } + public async Task ResetStatistic(int Id = 0, CancellationToken Cancel = default) + { + //Если Id==0, сбрасываем статистику для всех коротких ссылок + if (Id == 0) + { + await _db.ShortUrls.ForEachAsync(s => + { + s.Statistic = 0; + s.LastReset = DateTime.UtcNow; + }); + } + else + { + var shortUrl = await _db.ShortUrls.FirstOrDefaultAsync(s => s.Id == Id).ConfigureAwait(false); + if (shortUrl is null) + return false; + shortUrl.Statistic = 0; + shortUrl.LastReset = DateTime.UtcNow; + } + + try + { + await _db.SaveChangesAsync(Cancel).ConfigureAwait(false); + } + catch (DbUpdateException e) + { + _logger.LogError($"Обновление статистики вызвало исключение DbUpdateException: {e.ToString()}"); + return false; + } + catch (OperationCanceledException e) + { + _logger.LogError($"Обновление статистики вызвало исключение OperationCanceledException: {e.ToString()}"); + return false; + } + + return true; + } + public async Task> GetStatistic(int Id = 0, int Length=0, CancellationToken Cancel = default) + { + if (Id == 0) + { + return _db.ShortUrls.Select(s => new ShortUrl + { + Id = s.Id, + Alias = Length == 0 + ? s.Alias + : s.Alias.Substring(0, s.Alias.Length < Length + ? s.Alias.Length + : Length), + OriginalUrl = s.OriginalUrl, + LastReset = s.LastReset, + Statistic = s.Statistic + }); + } + + var shortUrl = await _db.ShortUrls.FirstOrDefaultAsync(s => s.Id == Id); + if (shortUrl is null) + return null; + + return Enumerable.Repeat(new ShortUrl + { + Id = shortUrl.Id, + Alias = Length == 0 + ? shortUrl.Alias : + shortUrl.Alias.Substring(0, shortUrl.Alias.Length < Length + ? shortUrl.Alias.Length + : Length), + OriginalUrl = shortUrl.OriginalUrl, + LastReset = shortUrl.LastReset, + Statistic = shortUrl.Statistic + }, 1); + } + + /// /// Генерирование псевдонима ссылки (хеш MD5) /// diff --git a/Services/Thoughts.WebAPI.Clients/ShortUrl/ShortUrlClient.cs b/Services/Thoughts.WebAPI.Clients/ShortUrl/ShortUrlClient.cs index 103fe81..cd526a0 100644 --- a/Services/Thoughts.WebAPI.Clients/ShortUrl/ShortUrlClient.cs +++ b/Services/Thoughts.WebAPI.Clients/ShortUrl/ShortUrlClient.cs @@ -6,8 +6,10 @@ using Microsoft.Extensions.Configuration; +using Thoughts.Interfaces; using Thoughts.Interfaces.Base; using Thoughts.WebAPI.Clients.Base; +using baseEntities = Thoughts.Domain.Base.Entities; namespace Thoughts.WebAPI.Clients.ShortUrl { @@ -36,6 +38,12 @@ public async Task GetAliasByIdAsync(int Id, int Length, CancellationToke return response; } + public async Task> GetStatistic(int Id = 0, int Length=0, CancellationToken Cancel = default) + { + var response = await GetAsync>($"{WebApiControllersPath.ShortUrlV1}/getstat/{Id}?Length={Length}"); + return response; + } + public async Task GetUrlAsync(string Alias, CancellationToken Cancel = default) { var response = await GetAsync($"{WebApiControllersPath.ShortUrlV1}?Alias={Alias}"); @@ -48,6 +56,12 @@ public async Task GetAliasByIdAsync(int Id, int Length, CancellationToke return response; } + public async Task ResetStatistic(int Id = 0, CancellationToken Cancel = default) + { + var response = await GetAsync($"{WebApiControllersPath.ShortUrlV1}/resetstat/{Id}"); + return response; + } + public async Task UpdateUrlAsync(int Id, string Url, CancellationToken Cancel = default) { var response = await PostAsync($"{WebApiControllersPath.ShortUrlV1}/{Id}", Url); diff --git a/Services/Thoughts.WebAPI/Controllers/v1/ShortUrlManagerController.cs b/Services/Thoughts.WebAPI/Controllers/v1/ShortUrlManagerController.cs index 715ac92..6651a3f 100644 --- a/Services/Thoughts.WebAPI/Controllers/v1/ShortUrlManagerController.cs +++ b/Services/Thoughts.WebAPI/Controllers/v1/ShortUrlManagerController.cs @@ -1,6 +1,10 @@ -using Microsoft.AspNetCore.Mvc; +using System.Collections; + +using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Routing; +using Thoughts.Domain.Base.Entities; +using Thoughts.Interfaces; using Thoughts.Interfaces.Base; namespace Thoughts.WebAPI.Controllers.v1 @@ -45,8 +49,7 @@ public async Task> GetUrlById(int Id) [HttpGet("alias/{Id}")] public async Task> GetAliasById(int Id, int Length) { - string dd = _configuration["ShortUrlMaxLength"]; - string result;// + string result; if (Length > 0) result = await _shortUrlManager.GetAliasByIdAsync(Id, Length); else if (int.TryParse(_configuration["ShortUrlMaxLength"], out int lengthFromConfig)) @@ -87,5 +90,33 @@ public async Task> UpdateUrl(int Id, [FromBody] string Url) var result = await _shortUrlManager.UpdateUrlAsync(Id, Url); return result ? result : NotFound(); } + + // GET api/v1/url/resetstat/10 + [HttpGet("resetstat/{Id}")] + public async Task> ResetStatistic(int Id) + { + var result = await _shortUrlManager.ResetStatistic(Id); + return result ? result : NotFound(); + } + + //GET: api/v1/url/getstat/10 + [HttpGet("getstat/{Id}")] + public async Task>> GetStatisticById(int Id,int Length) + { + IEnumerable result; + + if (Length > 0) + result = await _shortUrlManager.GetStatistic(Id,Length); + else if (int.TryParse(_configuration["ShortUrlMaxLength"], out int lengthFromConfig)) + result = await _shortUrlManager.GetStatistic(Id, lengthFromConfig); + else + result = await _shortUrlManager.GetStatistic(Id); + + + if (result is null || !result.Any()) + return NotFound(); + + return result.ToList(); + } } } diff --git a/Services/Thoughts.WebAPI/Program.cs b/Services/Thoughts.WebAPI/Program.cs index 95e66d0..ccc4733 100644 --- a/Services/Thoughts.WebAPI/Program.cs +++ b/Services/Thoughts.WebAPI/Program.cs @@ -6,7 +6,6 @@ using Thoughts.DAL.Sqlite; using Thoughts.DAL.SqlServer; using Thoughts.Interfaces; -using Thoughts.Interfaces.Base; using Thoughts.Services.InSQL; using Thoughts.WebAPI; using Thoughts.WebAPI.Infrastructure.Extensions; diff --git a/UI/Thoughts.UI.MVC/Controllers/ShortUrlController.cs b/UI/Thoughts.UI.MVC/Controllers/UrlController.cs similarity index 70% rename from UI/Thoughts.UI.MVC/Controllers/ShortUrlController.cs rename to UI/Thoughts.UI.MVC/Controllers/UrlController.cs index 6f8780d..e6cac0f 100644 --- a/UI/Thoughts.UI.MVC/Controllers/ShortUrlController.cs +++ b/UI/Thoughts.UI.MVC/Controllers/UrlController.cs @@ -1,6 +1,6 @@ -using Microsoft.AspNetCore.Mvc; +using System; -using Thoughts.Interfaces.Base; +using Microsoft.AspNetCore.Mvc; namespace Thoughts.UI.MVC.Controllers { @@ -69,11 +69,35 @@ public async Task AddUrl(string url) return View(shortUrlWebModel); } - // GET -> https://localhost:5010/url/Test - [Route("url/Test")] - public IActionResult Test() + // GET -> https://localhost:5010/url/CreateUrl + [Route("url/CreateUrl")] + public async Task CreateUrl() { return View(); } + + // GET -> https://localhost:5010/url/Statistic + [Route("url/Statistic")] + public async Task Statistic(int Length ) + { + var result = await _shortUrlManager.GetStatistic(Length: Length); + if (result is null || !result.Any()) + return BadRequest(); + + foreach (var item in result) + { + item.Alias = Url.ActionLink(action: nameof(RedirectByAlias), controller: "url", values: new { Alias = item.Alias }); + } + return View(result); + } + + // GET -> https://localhost:5010/url/ResetStatistic/10 + [Route("url/ResetStatistic/{Id}")] + public async Task ResetStatistic(int Id=0) + { + await _shortUrlManager.ResetStatistic(Id); + + return RedirectToAction("Statistic"); + } } } diff --git a/UI/Thoughts.UI.MVC/Program.cs b/UI/Thoughts.UI.MVC/Program.cs index 0e3753e..88dd2a1 100644 --- a/UI/Thoughts.UI.MVC/Program.cs +++ b/UI/Thoughts.UI.MVC/Program.cs @@ -1,4 +1,3 @@ -using Thoughts.Interfaces.Base; using Thoughts.Interfaces.Base.Repositories; using Thoughts.Services.Mapping; using Thoughts.WebAPI.Clients.ShortUrl; diff --git a/UI/Thoughts.UI.MVC/Views/Shared/Components/LeftMenu/Default.cshtml b/UI/Thoughts.UI.MVC/Views/Shared/Components/LeftMenu/Default.cshtml index 5429ae0..0002f02 100644 --- a/UI/Thoughts.UI.MVC/Views/Shared/Components/LeftMenu/Default.cshtml +++ b/UI/Thoughts.UI.MVC/Views/Shared/Components/LeftMenu/Default.cshtml @@ -5,6 +5,13 @@
  • Блог
  • Лента
  • +
  • + Короткие ссылки + +
  • Generic
  • Elements
  • diff --git a/UI/Thoughts.UI.MVC/Views/Url/AddUrl.cshtml b/UI/Thoughts.UI.MVC/Views/Url/AddUrl.cshtml index 1185da1..3702e1f 100644 --- a/UI/Thoughts.UI.MVC/Views/Url/AddUrl.cshtml +++ b/UI/Thoughts.UI.MVC/Views/Url/AddUrl.cshtml @@ -1,7 +1,7 @@ @model ShortUrlWebModel @{ - ViewBag.Title = "Переход по ссылке"; + ViewBag.Title = "Создание короткой ссылки"; }
    diff --git a/UI/Thoughts.UI.MVC/Views/Url/Test.cshtml b/UI/Thoughts.UI.MVC/Views/Url/CreateUrl.cshtml similarity index 100% rename from UI/Thoughts.UI.MVC/Views/Url/Test.cshtml rename to UI/Thoughts.UI.MVC/Views/Url/CreateUrl.cshtml diff --git a/UI/Thoughts.UI.MVC/Views/Url/Statistic.cshtml b/UI/Thoughts.UI.MVC/Views/Url/Statistic.cshtml new file mode 100644 index 0000000..469da54 --- /dev/null +++ b/UI/Thoughts.UI.MVC/Views/Url/Statistic.cshtml @@ -0,0 +1,41 @@ +@model IEnumerable +@{ + ViewBag.Title = "Статистика использования коротких ссылок"; +} + +
    + +

    Статистика использования коротких ссылок

    + +
    + + + + + + + + + + + @foreach (var shortUrl in Model) + { + + + + + + } + +
    IdДанные ссылки
    @shortUrl.Id + ОбновитьОригинальная ссылка: @shortUrl.OriginalUrl
    + Короткая ссылка @shortUrl.Alias
    + Колчество вызовов: @shortUrl.Statistic
    + Дата последнего сброса статистики: @shortUrl.LastReset +
    + + Обновить всю статистику + + +
    + diff --git a/UI/Thoughts.UI.MVC/WebModels/ShortUrlStatisticWebModel.cs b/UI/Thoughts.UI.MVC/WebModels/ShortUrlStatisticWebModel.cs new file mode 100644 index 0000000..6629237 --- /dev/null +++ b/UI/Thoughts.UI.MVC/WebModels/ShortUrlStatisticWebModel.cs @@ -0,0 +1,11 @@ +namespace Thoughts.UI.MVC.WebModels +{ + public class ShortUrlStatisticWebModel + { + public int Id { get; set; } + public string OriginalUrl { get; set; } + public string AliasUrl { get; set; } + public int Statistic { get; set; } + public DateTimeOffset LastReset { get; set; } + } +} From 504901f7f272c1c73c67160ff4b871135baf88e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B0=D1=81=D0=B8=D0=BB=D0=B8=D0=B9?= Date: Wed, 9 Nov 2022 21:41:41 +0500 Subject: [PATCH 12/12] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D1=8B=20=D0=BF=D0=BE=D0=B4=D0=BF=D0=B8=D1=81?= =?UTF-8?q?=D0=B8.=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B0=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D0=B0=20?= =?UTF-8?q?=D0=B8=20=D0=B4=D0=BE=D0=BF=D0=BE=D0=BB=D0=BD=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20Url-=D0=B0=D0=B4=D1=80=D0=B5=D1=81=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Thoughts.Services/InSQL/SqlShortUrlManagerService.cs | 6 +++++- .../Thoughts.WebAPI.Clients/ShortUrl/ShortUrlClient.cs | 4 ++++ .../Controllers/v1/ShortUrlManagerController.cs | 7 ++++++- UI/Thoughts.UI.MVC/Controllers/UrlController.cs | 5 ++++- UI/Thoughts.UI.MVC/Views/Url/Statistic.cshtml | 4 ++-- 5 files changed, 21 insertions(+), 5 deletions(-) diff --git a/Services/Thoughts.Services/InSQL/SqlShortUrlManagerService.cs b/Services/Thoughts.Services/InSQL/SqlShortUrlManagerService.cs index 0c4ae74..a0bb476 100644 --- a/Services/Thoughts.Services/InSQL/SqlShortUrlManagerService.cs +++ b/Services/Thoughts.Services/InSQL/SqlShortUrlManagerService.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Security.Cryptography; using System.Text; +using System.Text.RegularExpressions; using System.Threading.Channels; using System.Threading.Tasks; @@ -28,9 +29,12 @@ public SqlShortUrlManagerService(ThoughtsDB Db, ILogger Logg } public async Task AddUrlAsync(string UrlString, CancellationToken Cancel = default) - { + { _logger.LogInformation($"Создание короткой ссылки для Url:{UrlString}"); + if (!Regex.IsMatch(UrlString, @"^https?://")) + throw new FormatException("Строка адреса не имеет схемы"); + var url = CreateUrl(UrlString); if (url is null) diff --git a/Services/Thoughts.WebAPI.Clients/ShortUrl/ShortUrlClient.cs b/Services/Thoughts.WebAPI.Clients/ShortUrl/ShortUrlClient.cs index cd526a0..5a22b50 100644 --- a/Services/Thoughts.WebAPI.Clients/ShortUrl/ShortUrlClient.cs +++ b/Services/Thoughts.WebAPI.Clients/ShortUrl/ShortUrlClient.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Text.RegularExpressions; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; @@ -22,6 +23,9 @@ public ShortUrlClient(IConfiguration Configuration):base(Configuration,WebApiCon public async Task AddUrlAsync(string Url, CancellationToken Cancel = default) { + if (!Regex.IsMatch(Url, @"^https?://")) + throw new FormatException("Строка адреса не имеет схемы"); + var response = await PostAsync($"{WebApiControllersPath.ShortUrlV1}", Url); return await response.Content.ReadAsAsync(); } diff --git a/Services/Thoughts.WebAPI/Controllers/v1/ShortUrlManagerController.cs b/Services/Thoughts.WebAPI/Controllers/v1/ShortUrlManagerController.cs index 6651a3f..ce1c530 100644 --- a/Services/Thoughts.WebAPI/Controllers/v1/ShortUrlManagerController.cs +++ b/Services/Thoughts.WebAPI/Controllers/v1/ShortUrlManagerController.cs @@ -1,4 +1,6 @@ -using System.Collections; +using System; +using System.Collections; +using System.Text.RegularExpressions; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Routing; @@ -68,6 +70,9 @@ public async Task> GetAliasById(int Id, int Length) [HttpPost] public async Task> AddUrl([FromBody] string Url) { + if (!Regex.IsMatch(Url, @"^https?://")) + Url = "http://" + Url; + var result = await _shortUrlManager.AddUrlAsync(Url); if (result == 0) return BadRequest(); diff --git a/UI/Thoughts.UI.MVC/Controllers/UrlController.cs b/UI/Thoughts.UI.MVC/Controllers/UrlController.cs index e6cac0f..a408b8a 100644 --- a/UI/Thoughts.UI.MVC/Controllers/UrlController.cs +++ b/UI/Thoughts.UI.MVC/Controllers/UrlController.cs @@ -54,6 +54,9 @@ public async Task GetAliasById(int Id, int Length) [HttpPost] public async Task AddUrl(string url) { + if (!Regex.IsMatch(url, @"^https?://")) + url = "http://" + url; + var result = await _shortUrlManager.AddUrlAsync(url); if (result == 0) return BadRequest(); @@ -76,7 +79,7 @@ public async Task CreateUrl() return View(); } - // GET -> https://localhost:5010/url/Statistic + // GET -> https://localhost:5010/url/Statistic?Length=9 [Route("url/Statistic")] public async Task Statistic(int Length ) { diff --git a/UI/Thoughts.UI.MVC/Views/Url/Statistic.cshtml b/UI/Thoughts.UI.MVC/Views/Url/Statistic.cshtml index 469da54..222416b 100644 --- a/UI/Thoughts.UI.MVC/Views/Url/Statistic.cshtml +++ b/UI/Thoughts.UI.MVC/Views/Url/Statistic.cshtml @@ -23,7 +23,7 @@ @shortUrl.Id - Обновить + Сбросить Оригинальная ссылка: @shortUrl.OriginalUrl
    Короткая ссылка @shortUrl.Alias
    Колчество вызовов: @shortUrl.Statistic
    @@ -34,7 +34,7 @@ - Обновить всю статистику + Сбросить всю статистику