diff --git a/URLShortener.Data/DBContext/URLShortenerDBContext.cs b/URLShortener.Data/DBContext/URLShortenerDBContext.cs index 2b19169..a4186e7 100644 --- a/URLShortener.Data/DBContext/URLShortenerDBContext.cs +++ b/URLShortener.Data/DBContext/URLShortenerDBContext.cs @@ -11,11 +11,7 @@ public URLShortenerDBContext (DbContextOptions options) : protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity() - .HasIndex(x => x.HashedURL); - - modelBuilder.Entity() - .HasIndex(x => new { x.URL, x.HashedURL }).IsUnique(); - + .HasIndex(x => x.HashedURL).IsUnique(); } public DbSet URLInfos { get; set; } diff --git a/URLShortener.Data/Migrations/20191213094430_InitialMigration.Designer.cs b/URLShortener.Data/Migrations/20200106074624_InitialMigration.Designer.cs similarity index 92% rename from URLShortener.Data/Migrations/20191213094430_InitialMigration.Designer.cs rename to URLShortener.Data/Migrations/20200106074624_InitialMigration.Designer.cs index 8688342..eef3c43 100644 --- a/URLShortener.Data/Migrations/20191213094430_InitialMigration.Designer.cs +++ b/URLShortener.Data/Migrations/20200106074624_InitialMigration.Designer.cs @@ -10,7 +10,7 @@ namespace URLShortener.Data.Migrations { [DbContext(typeof(URLShortenerDBContext))] - [Migration("20191213094430_InitialMigration")] + [Migration("20200106074624_InitialMigration")] partial class InitialMigration { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -43,9 +43,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasKey("Id"); - b.HasIndex("HashedURL"); - - b.HasIndex("URL", "HashedURL") + b.HasIndex("HashedURL") .IsUnique(); b.ToTable("URLInfos"); diff --git a/URLShortener.Data/Migrations/20191213094430_InitialMigration.cs b/URLShortener.Data/Migrations/20200106074624_InitialMigration.cs similarity index 84% rename from URLShortener.Data/Migrations/20191213094430_InitialMigration.cs rename to URLShortener.Data/Migrations/20200106074624_InitialMigration.cs index 9e0096c..ea3b2bc 100644 --- a/URLShortener.Data/Migrations/20191213094430_InitialMigration.cs +++ b/URLShortener.Data/Migrations/20200106074624_InitialMigration.cs @@ -25,12 +25,7 @@ protected override void Up(MigrationBuilder migrationBuilder) migrationBuilder.CreateIndex( name: "IX_URLInfos_HashedURL", table: "URLInfos", - column: "HashedURL"); - - migrationBuilder.CreateIndex( - name: "IX_URLInfos_URL_HashedURL", - table: "URLInfos", - columns: new[] { "URL", "HashedURL" }, + column: "HashedURL", unique: true); } diff --git a/URLShortener.Data/Migrations/URLShortenerDBContextModelSnapshot.cs b/URLShortener.Data/Migrations/URLShortenerDBContextModelSnapshot.cs index 3fcf405..77d10bb 100644 --- a/URLShortener.Data/Migrations/URLShortenerDBContextModelSnapshot.cs +++ b/URLShortener.Data/Migrations/URLShortenerDBContextModelSnapshot.cs @@ -41,9 +41,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); - b.HasIndex("HashedURL"); - - b.HasIndex("URL", "HashedURL") + b.HasIndex("HashedURL") .IsUnique(); b.ToTable("URLInfos"); diff --git a/URLShortener/Controllers/ShortURLController.cs b/URLShortener/Controllers/ShortURLController.cs index 3e7967a..c1cfbd0 100644 --- a/URLShortener/Controllers/ShortURLController.cs +++ b/URLShortener/Controllers/ShortURLController.cs @@ -1,6 +1,10 @@ using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; +using System; using System.Threading.Tasks; +using URLShortener.Response; +using URLShortener.Services; namespace URLShortener.Controllers { @@ -9,10 +13,15 @@ namespace URLShortener.Controllers public class ShortURLController : ControllerBase { private readonly ILogger _logger; + private readonly IConfiguration _configuration; + private readonly IURLShortenerService _urlShortenerService; + private const string ServiceURL = "ServiceURL"; - public ShortURLController(ILogger logger) + public ShortURLController(ILogger logger, IURLShortenerService urlShortenerService, IConfiguration configuration) { _logger = logger; + _configuration = configuration; + _urlShortenerService = urlShortenerService; } @@ -26,10 +35,43 @@ public ShortURLController(ILogger logger) /// With short url /// Unauthorized. [HttpPost] - public async Task Post() + public async Task Post([FromQuery]string url) { - await Task.Delay(10); - return Ok("tiny url"); + bool isValidUrl = Uri.TryCreate(url, UriKind.Absolute, out Uri uriResult) + && (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps); + + if(!isValidUrl) + { + return BadRequest(); + } + + var hashedURL = await _urlShortenerService.SaveURL(url); + var serviceURL = _configuration.GetValue(ServiceURL); + var shortURL = String.Concat(serviceURL,"/shorturl/",hashedURL); + var response = new ShortURLResponse() { ShortURL = shortURL, Code = hashedURL }; + return Ok(response); + } + + /// + /// Redirect to actual URL on providing shirtURL + /// + /// + /// ### Usage Notes ### + /// 1. A valid User with jwt + /// + /// With short url + /// Unauthorized. + [HttpGet] + [Route("{shortURL}")] + [ApiExplorerSettings(IgnoreApi = true)] + public async Task Get([FromRoute]string shortURL) + { + var url = await _urlShortenerService.GetURL(shortURL); + if(url == null) + { + return NotFound(); + } + return Redirect(url); } } } diff --git a/URLShortener/Response/ShortURLResponse.cs b/URLShortener/Response/ShortURLResponse.cs new file mode 100644 index 0000000..ec0f8a6 --- /dev/null +++ b/URLShortener/Response/ShortURLResponse.cs @@ -0,0 +1,8 @@ +namespace URLShortener.Response +{ + public class ShortURLResponse + { + public string ShortURL { get; set; } + public string Code { get; set; } + } +} diff --git a/URLShortener/Services/URLShortenerService.cs b/URLShortener/Services/URLShortenerService.cs new file mode 100644 index 0000000..528f5c2 --- /dev/null +++ b/URLShortener/Services/URLShortenerService.cs @@ -0,0 +1,52 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using URLShortener.Data.DBContext; +using URLShortener.Data.Entities; +using URLShortener.Utils; + +namespace URLShortener.Services +{ + public class URLShortenerService : IURLShortenerService + { + private readonly ILogger _logger; + private readonly Func _dbContextProvider; + + public URLShortenerService(Func dbContextProvider, ILogger logger) + { + _dbContextProvider = dbContextProvider; + _logger = logger; + } + + + public async Task GetURL(string hashedURL) + { + using(var dbContext = _dbContextProvider()) + { + var urlInfo = await dbContext.URLInfos.AsNoTracking() + .SingleOrDefaultAsync(x => x.HashedURL == hashedURL); + return urlInfo.URL; + } + } + + public async Task SaveURL(string url) + { + using (var dbContext = _dbContextProvider()) + { + var hashedURL = HashGenerator.Generate_SHA256_Hash(url); + var urlInfo = await dbContext.URLInfos.SingleOrDefaultAsync(x => x.HashedURL == hashedURL); + if(urlInfo == null) + { + urlInfo = new URLInfo() { URL = url, CreatedOn = DateTime.UtcNow }; + urlInfo.HashedURL = hashedURL; + dbContext.URLInfos.Add(urlInfo); + await dbContext.SaveChangesAsync(); + } + return urlInfo.HashedURL; + } + } + } +} diff --git a/URLShortener/Services/interfaces/IURLShortenerService.cs b/URLShortener/Services/interfaces/IURLShortenerService.cs new file mode 100644 index 0000000..083e256 --- /dev/null +++ b/URLShortener/Services/interfaces/IURLShortenerService.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; + +namespace URLShortener.Services +{ + public interface IURLShortenerService + { + Task SaveURL(string url); + Task GetURL(string hashedURL); + } +} diff --git a/URLShortener/Startup.cs b/URLShortener/Startup.cs index 7c23a46..c9fd80c 100644 --- a/URLShortener/Startup.cs +++ b/URLShortener/Startup.cs @@ -14,6 +14,7 @@ using Microsoft.Extensions.Logging; using Microsoft.OpenApi.Models; using URLShortener.Data.DBContext; +using URLShortener.Services; namespace URLShortener { @@ -35,6 +36,7 @@ public void ConfigureServices(IServiceCollection services) var opt = new DbContextOptionsBuilder() .UseSqlServer(Configuration.GetConnectionString("URLShortenerDatabase")) .Options; + services.AddSingleton(); services.AddSingleton> ( () => new URLShortenerDBContext(opt) diff --git a/URLShortener/Utils/HashGenerator.cs b/URLShortener/Utils/HashGenerator.cs new file mode 100644 index 0000000..286bd09 --- /dev/null +++ b/URLShortener/Utils/HashGenerator.cs @@ -0,0 +1,22 @@ +using System.Security.Cryptography; +using System.Text; + +namespace URLShortener.Utils +{ + public static class HashGenerator + { + public static string Generate_SHA256_Hash(string value) + { + StringBuilder Sb = new StringBuilder(); + + using (var hash = SHA256.Create()) + { + Encoding enc = Encoding.UTF8; + foreach (byte b in hash.ComputeHash(enc.GetBytes(value))) + Sb.Append(b.ToString("x2")); + } + + return Sb.ToString(); + } + } +} diff --git a/URLShortener/appsettings.json b/URLShortener/appsettings.json index ac46b00..e282806 100644 --- a/URLShortener/appsettings.json +++ b/URLShortener/appsettings.json @@ -6,6 +6,7 @@ "Microsoft.Hosting.Lifetime": "Information" } }, + "ServiceURL": "http://localhost:5011", "ConnectionStrings": { "URLShortenerDatabase": "Server=(LocalDb)\\MSSQLLocalDB;Database=URLShortener;Trusted_Connection=True;" },